From 01a2688848759dfc60c478f7641ca05338241c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 11 Jul 2017 15:41:12 -0400 Subject: [PATCH] fix(animations): make sure @.disabled works in non-animation components Note 4.3 only! Prior to this fix when [@.disabled] was used in a component that contained zero animation code it wouldn't register properly because the renderer associated with that component was not an animation renderer. This patch ensures that it gets registered even when there are no animations set. --- .../src/render/animation_engine_next.ts | 25 ++++----- .../animation/animation_integration_spec.ts | 55 ++++++++++++++++++- .../animations/src/animation_renderer.ts | 28 ++++++++-- 3 files changed, 86 insertions(+), 22 deletions(-) diff --git a/packages/animations/browser/src/render/animation_engine_next.ts b/packages/animations/browser/src/render/animation_engine_next.ts index 7f1fef4b03..4aa25fc0db 100644 --- a/packages/animations/browser/src/render/animation_engine_next.ts +++ b/packages/animations/browser/src/render/animation_engine_next.ts @@ -67,20 +67,17 @@ export class AnimationEngine { this._transitionEngine.removeNode(namespaceId, element, context); } - process(namespaceId: string, element: any, property: string, value: any): boolean { - switch (property.charAt(0)) { - case '.': - if (property == '.disabled') { - this._transitionEngine.markElementAsDisabled(element, !!value); - } - return false; - case '@': - const [id, action] = parseTimelineCommand(property); - const args = value as any[]; - this._timelineEngine.command(id, element, action, args); - return false; - default: - return this._transitionEngine.trigger(namespaceId, element, property, value); + disableAnimations(element: any, disable: boolean) { + this._transitionEngine.markElementAsDisabled(element, disable); + } + + process(namespaceId: string, element: any, property: string, value: any) { + if (property.charAt(0) == '@') { + const [id, action] = parseTimelineCommand(property); + const args = value as any[]; + this._timelineEngine.command(id, element, action, args); + } else { + this._transitionEngine.trigger(namespaceId, element, property, value); } } diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts index 52acb25d8a..32e779eaed 100644 --- a/packages/core/test/animation/animation_integration_spec.ts +++ b/packages/core/test/animation/animation_integration_spec.ts @@ -1815,12 +1815,12 @@ export function main() { selector: 'my-cmp', template: `
-
- {{ item }} + {{ item }}
`, @@ -2153,6 +2153,57 @@ export function main() { expect(cmp.startEvent.totalTime).toEqual(9876); // the done event isn't fired because it's an actual animation })); + + it('should work when there are no animations on the component handling the disable/enable flag', + () => { + @Component({ + selector: 'parent-cmp', + template: ` +
+ +
+ ` + }) + class ParentCmp { + @ViewChild('child') public child: ChildCmp|null = null; + disableExp = false; + } + + @Component({ + selector: 'child-cmp', + template: ` +
+ `, + animations: [trigger( + 'myAnimation', + [transition( + '* => go, * => goAgain', + [style({opacity: 0}), animate('1s', style({opacity: 1}))])])] + }) + class ChildCmp { + public exp = ''; + } + + TestBed.configureTestingModule({declarations: [ParentCmp, ChildCmp]}); + + const fixture = TestBed.createComponent(ParentCmp); + const cmp = fixture.componentInstance; + cmp.disableExp = true; + fixture.detectChanges(); + resetLog(); + + const child = cmp.child !; + child.exp = 'go'; + fixture.detectChanges(); + + expect(getLog().length).toEqual(0); + resetLog(); + + cmp.disableExp = false; + child.exp = 'goAgain'; + fixture.detectChanges(); + expect(getLog().length).toEqual(1); + }); }); }); diff --git a/packages/platform-browser/animations/src/animation_renderer.ts b/packages/platform-browser/animations/src/animation_renderer.ts index 3f57a5b9ab..4b551a96ec 100644 --- a/packages/platform-browser/animations/src/animation_renderer.ts +++ b/packages/platform-browser/animations/src/animation_renderer.ts @@ -9,6 +9,9 @@ import {AnimationTriggerMetadata} from '@angular/animations'; import {ɵAnimationEngine as AnimationEngine} from '@angular/animations/browser'; import {Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '@angular/core'; +const ANIMATION_PREFIX = '@'; +const DISABLE_ANIMATIONS_FLAG = '@.disabled'; + @Injectable() export class AnimationRendererFactory implements RendererFactory2 { private _currentId: number = 0; @@ -166,7 +169,11 @@ export class BaseAnimationRenderer implements Renderer2 { } setProperty(el: any, name: string, value: any): void { - this.delegate.setProperty(el, name, value); + if (name.charAt(0) == ANIMATION_PREFIX && name == DISABLE_ANIMATIONS_FLAG) { + this.disableAnimations(el, !!value); + } else { + this.delegate.setProperty(el, name, value); + } } setValue(node: any, value: string): void { this.delegate.setValue(node, value); } @@ -174,6 +181,10 @@ export class BaseAnimationRenderer implements Renderer2 { listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void { return this.delegate.listen(target, eventName, callback); } + + protected disableAnimations(element: any, value: boolean) { + this.engine.disableAnimations(element, value); + } } export class AnimationRenderer extends BaseAnimationRenderer implements Renderer2 { @@ -185,9 +196,12 @@ export class AnimationRenderer extends BaseAnimationRenderer implements Renderer } setProperty(el: any, name: string, value: any): void { - if (name.charAt(0) == '@') { - name = name.substr(1); - this.engine.process(this.namespaceId, el, name, value); + if (name.charAt(0) == ANIMATION_PREFIX) { + if (name.charAt(1) == '.' && name == DISABLE_ANIMATIONS_FLAG) { + this.disableAnimations(el, !!value); + } else { + this.engine.process(this.namespaceId, el, name.substr(1), value); + } } else { this.delegate.setProperty(el, name, value); } @@ -195,11 +209,13 @@ export class AnimationRenderer extends BaseAnimationRenderer implements Renderer listen(target: 'window'|'document'|'body'|any, eventName: string, callback: (event: any) => any): () => void { - if (eventName.charAt(0) == '@') { + if (eventName.charAt(0) == ANIMATION_PREFIX) { const element = resolveElementFromTarget(target); let name = eventName.substr(1); let phase = ''; - if (name.charAt(0) != '@') { // transition-specific + // @listener.phase is for trigger animation callbacks + // @@listener is for animation builder callbacks + if (name.charAt(0) != ANIMATION_PREFIX) { [name, phase] = parseTriggerCallbackName(name); } return this.engine.listen(this.namespaceId, element, name, phase, event => {