fix(animations): avoid infinite loop with multiple blocked sub triggers (#21119)

This patch fixes animations so that if multiple sub @triggers are used
and are blocked by a parent animation then the engine will not lead
itself into an infinite loop.

PR Close #21119
This commit is contained in:
Matias Niemelä 2017-12-20 15:56:35 -08:00 committed by Igor Minar
parent 5ba1cf1063
commit 86a36eaadd
2 changed files with 94 additions and 4 deletions

View File

@ -33,17 +33,17 @@ export class AnimationGroupPlayer implements AnimationPlayer {
} else { } else {
this.players.forEach(player => { this.players.forEach(player => {
player.onDone(() => { player.onDone(() => {
if (++doneCount >= total) { if (++doneCount == total) {
this._onFinish(); this._onFinish();
} }
}); });
player.onDestroy(() => { player.onDestroy(() => {
if (++destroyCount >= total) { if (++destroyCount == total) {
this._onDestroy(); this._onDestroy();
} }
}); });
player.onStart(() => { player.onStart(() => {
if (++startCount >= total) { if (++startCount == total) {
this._onStart(); this._onStart();
} }
}); });
@ -67,9 +67,9 @@ export class AnimationGroupPlayer implements AnimationPlayer {
private _onStart() { private _onStart() {
if (!this.hasStarted()) { if (!this.hasStarted()) {
this._started = true;
this._onStartFns.forEach(fn => fn()); this._onStartFns.forEach(fn => fn());
this._onStartFns = []; this._onStartFns = [];
this._started = true;
} }
} }

View File

@ -2823,6 +2823,96 @@ export function main() {
expect(child.log).toEqual(['child-start', 'child-done']); expect(child.log).toEqual(['child-start', 'child-done']);
})); }));
it('should fire and synchronize the start/done callbacks on multiple blocked sub triggers',
fakeAsync(() => {
@Component({
selector: 'cmp',
animations: [
trigger(
'parent1',
[
transition(
'* => go, * => go-again',
[
style({opacity: 0}),
animate('1s', style({opacity: 1})),
]),
]),
trigger(
'parent2',
[
transition(
'* => go, * => go-again',
[
style({lineHeight: '0px'}),
animate('1s', style({lineHeight: '10px'})),
]),
]),
trigger(
'child1',
[
transition(
'* => go, * => go-again',
[
style({width: '0px'}),
animate('1s', style({width: '100px'})),
]),
]),
trigger(
'child2',
[
transition(
'* => go, * => go-again',
[
style({height: '0px'}),
animate('1s', style({height: '100px'})),
]),
]),
],
template: `
<div [@parent1]="parent1Exp" (@parent1.start)="track($event)"
[@parent2]="parent2Exp" (@parent2.start)="track($event)">
<div [@child1]="child1Exp" (@child1.start)="track($event)"
[@child2]="child2Exp" (@child2.start)="track($event)"></div>
</div>
`
})
class Cmp {
public parent1Exp = '';
public parent2Exp = '';
public child1Exp = '';
public child2Exp = '';
public log: string[] = [];
track(event: any) { this.log.push(`${event.triggerName}-${event.phaseName}`); }
}
TestBed.configureTestingModule({declarations: [Cmp]});
const engine = TestBed.get(ɵAnimationEngine);
const fixture = TestBed.createComponent(Cmp);
fixture.detectChanges();
flushMicrotasks();
const cmp = fixture.componentInstance;
cmp.log = [];
cmp.parent1Exp = 'go';
cmp.parent2Exp = 'go';
cmp.child1Exp = 'go';
cmp.child2Exp = 'go';
fixture.detectChanges();
flushMicrotasks();
expect(cmp.log).toEqual(
['parent1-start', 'parent2-start', 'child1-start', 'child2-start']);
cmp.parent1Exp = 'go-again';
cmp.parent2Exp = 'go-again';
cmp.child1Exp = 'go-again';
cmp.child2Exp = 'go-again';
fixture.detectChanges();
flushMicrotasks();
}));
it('should stretch the starting keyframe of a child animation queries are issued by the parent', it('should stretch the starting keyframe of a child animation queries are issued by the parent',
() => { () => {
@Component({ @Component({