fix(animations): always cleanup players after they have finished internally (#13334)
Closes #13333 Closes #13334
This commit is contained in:
parent
b5c4bf1c59
commit
f0b0762f4a
|
@ -199,8 +199,9 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||||
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
|
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
|
||||||
'getAnimationPlayers',
|
'getAnimationPlayers',
|
||||||
[
|
[
|
||||||
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
|
_ANIMATION_FACTORY_ELEMENT_VAR,
|
||||||
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
|
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
|
||||||
|
.conditional(o.NULL_EXPR, o.literal(this.animationName))
|
||||||
]))
|
]))
|
||||||
.toDeclStmt());
|
.toDeclStmt());
|
||||||
|
|
||||||
|
|
|
@ -44,10 +44,11 @@ export class ViewAnimationMap {
|
||||||
|
|
||||||
getAllPlayers(): AnimationPlayer[] { return this._allPlayers; }
|
getAllPlayers(): AnimationPlayer[] { return this._allPlayers; }
|
||||||
|
|
||||||
remove(element: any, animationName: string): void {
|
remove(element: any, animationName: string, targetPlayer: AnimationPlayer = null): void {
|
||||||
const playersByAnimation = this._map.get(element);
|
const playersByAnimation = this._map.get(element);
|
||||||
if (playersByAnimation) {
|
if (playersByAnimation) {
|
||||||
const player = playersByAnimation[animationName];
|
const player = playersByAnimation[animationName];
|
||||||
|
if (!targetPlayer || player === targetPlayer) {
|
||||||
delete playersByAnimation[animationName];
|
delete playersByAnimation[animationName];
|
||||||
const index = this._allPlayers.indexOf(player);
|
const index = this._allPlayers.indexOf(player);
|
||||||
this._allPlayers.splice(index, 1);
|
this._allPlayers.splice(index, 1);
|
||||||
|
@ -57,4 +58,5 @@ export class ViewAnimationMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,19 +29,19 @@ export class AnimationViewContext {
|
||||||
queueAnimation(element: any, animationName: string, player: AnimationPlayer): void {
|
queueAnimation(element: any, animationName: string, player: AnimationPlayer): void {
|
||||||
queueAnimationGlobally(player);
|
queueAnimationGlobally(player);
|
||||||
this._players.set(element, animationName, player);
|
this._players.set(element, animationName, player);
|
||||||
|
player.onDone(() => this._players.remove(element, animationName, player));
|
||||||
}
|
}
|
||||||
|
|
||||||
getAnimationPlayers(element: any, animationName: string, removeAllAnimations: boolean = false):
|
getAnimationPlayers(element: any, animationName: string = null): AnimationPlayer[] {
|
||||||
AnimationPlayer[] {
|
|
||||||
const players: AnimationPlayer[] = [];
|
const players: AnimationPlayer[] = [];
|
||||||
if (removeAllAnimations) {
|
if (animationName) {
|
||||||
this._players.findAllPlayersByElement(element).forEach(
|
|
||||||
player => { _recursePlayers(player, players); });
|
|
||||||
} else {
|
|
||||||
const currentPlayer = this._players.find(element, animationName);
|
const currentPlayer = this._players.find(element, animationName);
|
||||||
if (currentPlayer) {
|
if (currentPlayer) {
|
||||||
_recursePlayers(currentPlayer, players);
|
_recursePlayers(currentPlayer, players);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this._players.findAllPlayersByElement(element).forEach(
|
||||||
|
player => _recursePlayers(player, players));
|
||||||
}
|
}
|
||||||
return players;
|
return players;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2121,6 +2121,43 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||||
expect(kf[1]).toEqual([1, {'height': '333px', 'opacity': AUTO_STYLE, 'width': '200px'}]);
|
expect(kf[1]).toEqual([1, {'height': '333px', 'opacity': AUTO_STYLE, 'width': '200px'}]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not use the previous animation\'s styling if the previous animation has already finished',
|
||||||
|
fakeAsync(() => {
|
||||||
|
TestBed.overrideComponent(DummyIfCmp, {
|
||||||
|
set: {
|
||||||
|
template: `
|
||||||
|
<div [@myAnimation]="exp"></div>
|
||||||
|
`,
|
||||||
|
animations: [trigger(
|
||||||
|
'myAnimation',
|
||||||
|
[
|
||||||
|
state('a', style({color: 'red'})), state('b', style({color: 'red'})),
|
||||||
|
transition('* => *', animate(1000))
|
||||||
|
])]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const driver = TestBed.get(AnimationDriver) as MockAnimationDriver;
|
||||||
|
const fixture = TestBed.createComponent(DummyIfCmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
|
||||||
|
cmp.exp = 'a';
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
const animation1 = driver.log.shift();
|
||||||
|
expect(animation1['previousStyles']).toEqual({});
|
||||||
|
|
||||||
|
animation1['player'].finish();
|
||||||
|
|
||||||
|
cmp.exp = 'b';
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
const animation2 = driver.log.shift();
|
||||||
|
expect(animation2['previousStyles']).toEqual({});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('full animation integration tests', () => {
|
describe('full animation integration tests', () => {
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {el} from '@angular/platform-browser/testing/browser_util';
|
||||||
|
|
||||||
|
import {NoOpAnimationPlayer} from '../../src/animation/animation_player';
|
||||||
|
import {AnimationViewContext} from '../../src/linker/animation_view_context';
|
||||||
|
import {fakeAsync, flushMicrotasks} from '../../testing';
|
||||||
|
import {describe, expect, iit, it} from '../../testing/testing_internal';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('AnimationViewContext', function() {
|
||||||
|
let viewContext: AnimationViewContext;
|
||||||
|
let elm: any;
|
||||||
|
beforeEach(() => {
|
||||||
|
viewContext = new AnimationViewContext();
|
||||||
|
elm = el('<div></div>');
|
||||||
|
});
|
||||||
|
|
||||||
|
function getPlayers() { return viewContext.getAnimationPlayers(elm); }
|
||||||
|
|
||||||
|
it('should remove the player from the registry once the animation is complete',
|
||||||
|
fakeAsync(() => {
|
||||||
|
const player = new NoOpAnimationPlayer();
|
||||||
|
|
||||||
|
expect(getPlayers().length).toEqual(0);
|
||||||
|
viewContext.queueAnimation(elm, 'someAnimation', player);
|
||||||
|
expect(getPlayers().length).toEqual(1);
|
||||||
|
player.finish();
|
||||||
|
expect(getPlayers().length).toEqual(0);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not remove a follow-up player from the registry if another player is queued',
|
||||||
|
fakeAsync(() => {
|
||||||
|
const player1 = new NoOpAnimationPlayer();
|
||||||
|
const player2 = new NoOpAnimationPlayer();
|
||||||
|
|
||||||
|
viewContext.queueAnimation(elm, 'someAnimation', player1);
|
||||||
|
expect(getPlayers().length).toBe(1);
|
||||||
|
expect(getPlayers()[0]).toBe(player1);
|
||||||
|
|
||||||
|
viewContext.queueAnimation(elm, 'someAnimation', player2);
|
||||||
|
expect(getPlayers().length).toBe(1);
|
||||||
|
expect(getPlayers()[0]).toBe(player2);
|
||||||
|
|
||||||
|
player1.finish();
|
||||||
|
|
||||||
|
expect(getPlayers().length).toBe(1);
|
||||||
|
expect(getPlayers()[0]).toBe(player2);
|
||||||
|
|
||||||
|
player2.finish();
|
||||||
|
|
||||||
|
expect(getPlayers().length).toBe(0);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue