Skip to content

Commit a26e054

Browse files
matskovicb
authored andcommitted
fix(animations): always cleanup players after they have finished internally (#13334)
Closes #13333 Closes #13334
1 parent c0b001a commit a26e054

File tree

5 files changed

+115
-14
lines changed

5 files changed

+115
-14
lines changed

modules/@angular/compiler/src/animation/animation_compiler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,9 @@ class _AnimationBuilder implements AnimationAstVisitor {
199199
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
200200
'getAnimationPlayers',
201201
[
202-
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
202+
_ANIMATION_FACTORY_ELEMENT_VAR,
203203
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
204+
.conditional(o.NULL_EXPR, o.literal(this.animationName))
204205
]))
205206
.toDeclStmt());
206207

modules/@angular/core/src/animation/view_animation_map.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,18 @@ export class ViewAnimationMap {
4444

4545
getAllPlayers(): AnimationPlayer[] { return this._allPlayers; }
4646

47-
remove(element: any, animationName: string): void {
47+
remove(element: any, animationName: string, targetPlayer: AnimationPlayer = null): void {
4848
const playersByAnimation = this._map.get(element);
4949
if (playersByAnimation) {
5050
const player = playersByAnimation[animationName];
51-
delete playersByAnimation[animationName];
52-
const index = this._allPlayers.indexOf(player);
53-
this._allPlayers.splice(index, 1);
54-
55-
if (Object.keys(playersByAnimation).length === 0) {
56-
this._map.delete(element);
51+
if (!targetPlayer || player === targetPlayer) {
52+
delete playersByAnimation[animationName];
53+
const index = this._allPlayers.indexOf(player);
54+
this._allPlayers.splice(index, 1);
55+
56+
if (Object.keys(playersByAnimation).length === 0) {
57+
this._map.delete(element);
58+
}
5759
}
5860
}
5961
}

modules/@angular/core/src/linker/animation_view_context.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,19 @@ export class AnimationViewContext {
2929
queueAnimation(element: any, animationName: string, player: AnimationPlayer): void {
3030
queueAnimationGlobally(player);
3131
this._players.set(element, animationName, player);
32+
player.onDone(() => this._players.remove(element, animationName, player));
3233
}
3334

34-
getAnimationPlayers(element: any, animationName: string, removeAllAnimations: boolean = false):
35-
AnimationPlayer[] {
35+
getAnimationPlayers(element: any, animationName: string = null): AnimationPlayer[] {
3636
const players: AnimationPlayer[] = [];
37-
if (removeAllAnimations) {
38-
this._players.findAllPlayersByElement(element).forEach(
39-
player => { _recursePlayers(player, players); });
40-
} else {
37+
if (animationName) {
4138
const currentPlayer = this._players.find(element, animationName);
4239
if (currentPlayer) {
4340
_recursePlayers(currentPlayer, players);
4441
}
42+
} else {
43+
this._players.findAllPlayersByElement(element).forEach(
44+
player => _recursePlayers(player, players));
4545
}
4646
return players;
4747
}

modules/@angular/core/test/animation/animation_integration_spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,6 +2121,43 @@ function declareTests({useJit}: {useJit: boolean}) {
21212121
expect(kf[1]).toEqual([1, {'height': '333px', 'opacity': AUTO_STYLE, 'width': '200px'}]);
21222122
});
21232123
});
2124+
2125+
it('should not use the previous animation\'s styling if the previous animation has already finished',
2126+
fakeAsync(() => {
2127+
TestBed.overrideComponent(DummyIfCmp, {
2128+
set: {
2129+
template: `
2130+
<div [@myAnimation]="exp"></div>
2131+
`,
2132+
animations: [trigger(
2133+
'myAnimation',
2134+
[
2135+
state('a', style({color: 'red'})), state('b', style({color: 'red'})),
2136+
transition('* => *', animate(1000))
2137+
])]
2138+
}
2139+
});
2140+
2141+
const driver = TestBed.get(AnimationDriver) as MockAnimationDriver;
2142+
const fixture = TestBed.createComponent(DummyIfCmp);
2143+
const cmp = fixture.componentInstance;
2144+
2145+
cmp.exp = 'a';
2146+
fixture.detectChanges();
2147+
flushMicrotasks();
2148+
2149+
const animation1 = driver.log.shift();
2150+
expect(animation1['previousStyles']).toEqual({});
2151+
2152+
animation1['player'].finish();
2153+
2154+
cmp.exp = 'b';
2155+
fixture.detectChanges();
2156+
flushMicrotasks();
2157+
2158+
const animation2 = driver.log.shift();
2159+
expect(animation2['previousStyles']).toEqual({});
2160+
}));
21242161
});
21252162

21262163
describe('full animation integration tests', () => {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {el} from '@angular/platform-browser/testing/browser_util';
10+
11+
import {NoOpAnimationPlayer} from '../../src/animation/animation_player';
12+
import {AnimationViewContext} from '../../src/linker/animation_view_context';
13+
import {fakeAsync, flushMicrotasks} from '../../testing';
14+
import {describe, expect, iit, it} from '../../testing/testing_internal';
15+
16+
export function main() {
17+
describe('AnimationViewContext', function() {
18+
let viewContext: AnimationViewContext;
19+
let elm: any;
20+
beforeEach(() => {
21+
viewContext = new AnimationViewContext();
22+
elm = el('<div></div>');
23+
});
24+
25+
function getPlayers() { return viewContext.getAnimationPlayers(elm); }
26+
27+
it('should remove the player from the registry once the animation is complete',
28+
fakeAsync(() => {
29+
const player = new NoOpAnimationPlayer();
30+
31+
expect(getPlayers().length).toEqual(0);
32+
viewContext.queueAnimation(elm, 'someAnimation', player);
33+
expect(getPlayers().length).toEqual(1);
34+
player.finish();
35+
expect(getPlayers().length).toEqual(0);
36+
}));
37+
38+
it('should not remove a follow-up player from the registry if another player is queued',
39+
fakeAsync(() => {
40+
const player1 = new NoOpAnimationPlayer();
41+
const player2 = new NoOpAnimationPlayer();
42+
43+
viewContext.queueAnimation(elm, 'someAnimation', player1);
44+
expect(getPlayers().length).toBe(1);
45+
expect(getPlayers()[0]).toBe(player1);
46+
47+
viewContext.queueAnimation(elm, 'someAnimation', player2);
48+
expect(getPlayers().length).toBe(1);
49+
expect(getPlayers()[0]).toBe(player2);
50+
51+
player1.finish();
52+
53+
expect(getPlayers().length).toBe(1);
54+
expect(getPlayers()[0]).toBe(player2);
55+
56+
player2.finish();
57+
58+
expect(getPlayers().length).toBe(0);
59+
}));
60+
});
61+
}

0 commit comments

Comments
 (0)