From 383f23b57801bfe14f5a2f5cddf84d2071fdaa0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Fri, 4 Nov 2016 15:15:27 -0700 Subject: [PATCH] fix(animations): always trigger animations after the change detection check (#12713) This patch ensures that animations are run outside of change detection thus allowing for start and done callbacks to modify application data without causing a cycle loop. Closes #12713 --- .../core/src/animation/animation_queue.ts | 9 +++++ .../animation/animation_integration_spec.ts | 36 +++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/modules/@angular/core/src/animation/animation_queue.ts b/modules/@angular/core/src/animation/animation_queue.ts index 8c9b119874..260b828c50 100644 --- a/modules/@angular/core/src/animation/animation_queue.ts +++ b/modules/@angular/core/src/animation/animation_queue.ts @@ -17,6 +17,15 @@ export function queueAnimation(player: AnimationPlayer) { /** @internal */ export function triggerQueuedAnimations() { + // this code is wrapped into a single promise such that the + // onStart and onDone player callbacks are triggered outside + // of the digest cycle of animations + if (_queuedAnimations.length) { + Promise.resolve(null).then(_triggerAnimations); + } +} + +function _triggerAnimations() { for (var i = 0; i < _queuedAnimations.length; i++) { var player = _queuedAnimations[i]; player.play(); diff --git a/modules/@angular/core/test/animation/animation_integration_spec.ts b/modules/@angular/core/test/animation/animation_integration_spec.ts index f6c594c620..21cf07093e 100644 --- a/modules/@angular/core/test/animation/animation_integration_spec.ts +++ b/modules/@angular/core/test/animation/animation_integration_spec.ts @@ -1151,7 +1151,7 @@ function declareTests({useJit}: {useJit: boolean}) { describe('animation output events', () => { it('should fire the associated animation output expression when the animation starts even if no animation is fired', - () => { + fakeAsync(() => { TestBed.overrideComponent(DummyIfCmp, { set: { template: ` @@ -1175,16 +1175,18 @@ function declareTests({useJit}: {useJit: boolean}) { cmp.exp = 'one'; fixture.detectChanges(); + flushMicrotasks(); expect(calls).toEqual(1); expect(isAnimationRunning).toEqual(false); cmp.exp = 'two'; fixture.detectChanges(); + flushMicrotasks(); expect(calls).toEqual(2); expect(isAnimationRunning).toEqual(true); - }); + })); it('should fire the associated animation output expression when the animation ends even if no animation is fired', fakeAsync(() => { @@ -1296,6 +1298,36 @@ function declareTests({useJit}: {useJit: boolean}) { expect(eventData2.totalTime).toEqual(0); })); + it('should successfully update the component view when an animation start callback is fired', + fakeAsync(() => { + TestBed.overrideComponent(DummyIfCmp, { + set: { + template: ` +
{{ exp2 }}
+ `, + animations: [ + trigger( + 'trigger', + [transition( + '* => *', + [animate('1s 750ms', style({})), animate('2000ms 0ms', style({}))])]), + ] + } + }); + + let fixture = TestBed.createComponent(DummyIfCmp); + var cmp = fixture.componentInstance; + cmp.exp2 = 'ignore me'; + cmp.exp = 'one'; + + fixture.detectChanges(); + flushMicrotasks(); + fixture.detectChanges(); + + var container = fixture.nativeElement; + expect(getDOM().getInnerHTML(container)).toContain('look at me'); + })); + it('should throw an error if an animation output is referenced is not defined within the component', () => { TestBed.overrideComponent(DummyIfCmp, {