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.
This commit is contained in:
Matias Niemelä 2017-07-11 15:41:12 -04:00 committed by Alex Rickabaugh
parent 8e56c3cb30
commit 01a2688848
3 changed files with 86 additions and 22 deletions

View File

@ -67,20 +67,17 @@ export class AnimationEngine {
this._transitionEngine.removeNode(namespaceId, element, context); this._transitionEngine.removeNode(namespaceId, element, context);
} }
process(namespaceId: string, element: any, property: string, value: any): boolean { disableAnimations(element: any, disable: boolean) {
switch (property.charAt(0)) { this._transitionEngine.markElementAsDisabled(element, disable);
case '.':
if (property == '.disabled') {
this._transitionEngine.markElementAsDisabled(element, !!value);
} }
return false;
case '@': process(namespaceId: string, element: any, property: string, value: any) {
if (property.charAt(0) == '@') {
const [id, action] = parseTimelineCommand(property); const [id, action] = parseTimelineCommand(property);
const args = value as any[]; const args = value as any[];
this._timelineEngine.command(id, element, action, args); this._timelineEngine.command(id, element, action, args);
return false; } else {
default: this._transitionEngine.trigger(namespaceId, element, property, value);
return this._transitionEngine.trigger(namespaceId, element, property, value);
} }
} }

View File

@ -2153,6 +2153,57 @@ export function main() {
expect(cmp.startEvent.totalTime).toEqual(9876); expect(cmp.startEvent.totalTime).toEqual(9876);
// the done event isn't fired because it's an actual animation // 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: `
<div [@.disabled]="disableExp">
<child-cmp #child></child-cmp>
</div>
`
})
class ParentCmp {
@ViewChild('child') public child: ChildCmp|null = null;
disableExp = false;
}
@Component({
selector: 'child-cmp',
template: `
<div [@myAnimation]="exp"></div>
`,
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);
});
}); });
}); });

View File

@ -9,6 +9,9 @@ import {AnimationTriggerMetadata} from '@angular/animations';
import {ɵAnimationEngine as AnimationEngine} from '@angular/animations/browser'; import {ɵAnimationEngine as AnimationEngine} from '@angular/animations/browser';
import {Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '@angular/core'; import {Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '@angular/core';
const ANIMATION_PREFIX = '@';
const DISABLE_ANIMATIONS_FLAG = '@.disabled';
@Injectable() @Injectable()
export class AnimationRendererFactory implements RendererFactory2 { export class AnimationRendererFactory implements RendererFactory2 {
private _currentId: number = 0; private _currentId: number = 0;
@ -166,14 +169,22 @@ export class BaseAnimationRenderer implements Renderer2 {
} }
setProperty(el: any, name: string, value: any): void { setProperty(el: any, name: string, value: any): void {
if (name.charAt(0) == ANIMATION_PREFIX && name == DISABLE_ANIMATIONS_FLAG) {
this.disableAnimations(el, !!value);
} else {
this.delegate.setProperty(el, name, value); this.delegate.setProperty(el, name, value);
} }
}
setValue(node: any, value: string): void { this.delegate.setValue(node, value); } setValue(node: any, value: string): void { this.delegate.setValue(node, value); }
listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void { listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void {
return this.delegate.listen(target, eventName, callback); 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 { 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 { setProperty(el: any, name: string, value: any): void {
if (name.charAt(0) == '@') { if (name.charAt(0) == ANIMATION_PREFIX) {
name = name.substr(1); if (name.charAt(1) == '.' && name == DISABLE_ANIMATIONS_FLAG) {
this.engine.process(this.namespaceId, el, name, value); this.disableAnimations(el, !!value);
} else {
this.engine.process(this.namespaceId, el, name.substr(1), value);
}
} else { } else {
this.delegate.setProperty(el, name, value); 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): listen(target: 'window'|'document'|'body'|any, eventName: string, callback: (event: any) => any):
() => void { () => void {
if (eventName.charAt(0) == '@') { if (eventName.charAt(0) == ANIMATION_PREFIX) {
const element = resolveElementFromTarget(target); const element = resolveElementFromTarget(target);
let name = eventName.substr(1); let name = eventName.substr(1);
let phase = ''; 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); [name, phase] = parseTriggerCallbackName(name);
} }
return this.engine.listen(this.namespaceId, element, name, phase, event => { return this.engine.listen(this.namespaceId, element, name, phase, event => {