diff --git a/packages/core/test/animation/animation_integration_spec.ts b/packages/core/test/animation/animation_integration_spec.ts
index dbaee6ecd9..7e089d48fc 100644
--- a/packages/core/test/animation/animation_integration_spec.ts
+++ b/packages/core/test/animation/animation_integration_spec.ts
@@ -8,7 +8,7 @@
import {AUTO_STYLE, AnimationEvent, AnimationOptions, animate, animateChild, group, keyframes, query, state, style, transition, trigger, ɵPRE_STYLE as PRE_STYLE} from '@angular/animations';
import {AnimationDriver, ɵAnimationEngine, ɵNoopAnimationDriver} from '@angular/animations/browser';
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing';
-import {Component, HostBinding, HostListener, RendererFactory2, ViewChild} from '@angular/core';
+import {ChangeDetectorRef, Component, HostBinding, HostListener, RendererFactory2, ViewChild} from '@angular/core';
import {ɵDomRendererFactory2} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@@ -1482,6 +1482,65 @@ export function main() {
]);
});
+ it('should not flush animations twice when an inner component runs change detection', () => {
+ @Component({
+ selector: 'outer-cmp',
+ template: `
+
+
+ `,
+ animations: [trigger(
+ 'outer',
+ [transition(':enter', [style({opacity: 0}), animate('1s', style({opacity: 1}))])])]
+ })
+ class OuterCmp {
+ @ViewChild('inner') public inner: any;
+ public exp: any = null;
+
+ update() { this.exp = 'go'; }
+
+ ngDoCheck() {
+ if (this.exp == 'go') {
+ this.inner.update();
+ }
+ }
+ }
+
+ @Component({
+ selector: 'inner-cmp',
+ template: `
+
+ `,
+ animations: [trigger('inner', [transition(
+ ':enter',
+ [
+ style({opacity: 0}),
+ animate('1s', style({opacity: 1})),
+ ])])]
+ })
+ class InnerCmp {
+ public exp: any;
+ constructor(private _ref: ChangeDetectorRef) {}
+ update() {
+ this.exp = 'go';
+ this._ref.detectChanges();
+ }
+ }
+
+ TestBed.configureTestingModule({declarations: [OuterCmp, InnerCmp]});
+
+ const engine = TestBed.get(ɵAnimationEngine);
+ const fixture = TestBed.createComponent(OuterCmp);
+ const cmp = fixture.componentInstance;
+ fixture.detectChanges();
+ expect(getLog()).toEqual([]);
+
+ cmp.update();
+ fixture.detectChanges();
+
+ const players = getLog();
+ expect(players.length).toEqual(2);
+ });
});
describe('animation listeners', () => {
diff --git a/packages/platform-browser/animations/src/animation_renderer.ts b/packages/platform-browser/animations/src/animation_renderer.ts
index 3f57a5b9ab..4d3adaf24d 100644
--- a/packages/platform-browser/animations/src/animation_renderer.ts
+++ b/packages/platform-browser/animations/src/animation_renderer.ts
@@ -15,6 +15,7 @@ export class AnimationRendererFactory implements RendererFactory2 {
private _microtaskId: number = 1;
private _animationCallbacksBuffer: [(e: any) => any, any][] = [];
private _rendererCache = new Map();
+ private _cdRecurDepth = 0;
constructor(
private delegate: RendererFactory2, private engine: AnimationEngine, private _zone: NgZone) {
@@ -58,6 +59,7 @@ export class AnimationRendererFactory implements RendererFactory2 {
}
begin() {
+ this._cdRecurDepth++;
if (this.delegate.begin) {
this.delegate.begin();
}
@@ -90,10 +92,16 @@ export class AnimationRendererFactory implements RendererFactory2 {
}
end() {
- this._zone.runOutsideAngular(() => {
- this._scheduleCountTask();
- this.engine.flush(this._microtaskId);
- });
+ this._cdRecurDepth--;
+
+ // this is to prevent animations from running twice when an inner
+ // component does CD when a parent component insted has inserted it
+ if (this._cdRecurDepth == 0) {
+ this._zone.runOutsideAngular(() => {
+ this._scheduleCountTask();
+ this.engine.flush(this._microtaskId);
+ });
+ }
if (this.delegate.end) {
this.delegate.end();
}