fix(animations): make sure @.disabled respects disabled parent/sub animation sequences (#18715)
Prior to this fix if @parent and @child animations ran at the same time within a disabled region then there was a chance that a @child sub animation would never complete. This would cause *directives to never close a removal when a @child trigger was placed on them. This patch fixes this issue. PR Close #18715
This commit is contained in:
parent
791c7efe29
commit
e25f05ae7c
|
@ -1016,11 +1016,20 @@ export class TransitionAnimationEngine {
|
||||||
} else {
|
} else {
|
||||||
eraseStyles(element, instruction.fromStyles);
|
eraseStyles(element, instruction.fromStyles);
|
||||||
player.onDestroy(() => setStyles(element, instruction.toStyles));
|
player.onDestroy(() => setStyles(element, instruction.toStyles));
|
||||||
|
// there still might be a ancestor player animating this
|
||||||
|
// element therefore we will still add it as a sub player
|
||||||
|
// even if its animation may be disabled
|
||||||
subPlayers.push(player);
|
subPlayers.push(player);
|
||||||
|
if (disabledElementsSet.has(element)) {
|
||||||
|
skippedPlayers.push(player);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// find all of the sub players' corresponding inner animation player
|
||||||
subPlayers.forEach(player => {
|
subPlayers.forEach(player => {
|
||||||
|
// even if any players are not found for a sub animation then it
|
||||||
|
// will still complete itself after the next tick since it's Noop
|
||||||
const playersForElement = skippedPlayersMap.get(player.element);
|
const playersForElement = skippedPlayersMap.get(player.element);
|
||||||
if (playersForElement && playersForElement.length) {
|
if (playersForElement && playersForElement.length) {
|
||||||
const innerPlayer = optimizeGroupPlayer(playersForElement);
|
const innerPlayer = optimizeGroupPlayer(playersForElement);
|
||||||
|
@ -1052,7 +1061,7 @@ export class TransitionAnimationEngine {
|
||||||
// until that animation is over (or the parent queried animation)
|
// until that animation is over (or the parent queried animation)
|
||||||
if (details && details.hasAnimation) continue;
|
if (details && details.hasAnimation) continue;
|
||||||
|
|
||||||
let players: AnimationPlayer[] = [];
|
let players: TransitionAnimationPlayer[] = [];
|
||||||
|
|
||||||
// if this element is queried or if it contains queried children
|
// if this element is queried or if it contains queried children
|
||||||
// then we want for the element not to be removed from the page
|
// then we want for the element not to be removed from the page
|
||||||
|
@ -1071,8 +1080,10 @@ export class TransitionAnimationEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (players.length) {
|
|
||||||
removeNodesAfterAnimationDone(this, element, players);
|
const activePlayers = players.filter(p => !p.destroyed);
|
||||||
|
if (activePlayers.length) {
|
||||||
|
removeNodesAfterAnimationDone(this, element, activePlayers);
|
||||||
} else {
|
} else {
|
||||||
this.processLeaveNode(element);
|
this.processLeaveNode(element);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2727,6 +2727,76 @@ export function main() {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(getLog().length).toEqual(0);
|
expect(getLog().length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should respect parent/sub animations when the respective area in the DOM is disabled',
|
||||||
|
fakeAsync(() => {
|
||||||
|
@Component({
|
||||||
|
selector: 'parent-cmp',
|
||||||
|
animations: [
|
||||||
|
trigger(
|
||||||
|
'parent',
|
||||||
|
[
|
||||||
|
transition(
|
||||||
|
'* => empty',
|
||||||
|
[
|
||||||
|
style({opacity: 0}),
|
||||||
|
query(
|
||||||
|
'@child',
|
||||||
|
[
|
||||||
|
animateChild(),
|
||||||
|
]),
|
||||||
|
animate('1s', style({opacity: 1})),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
trigger(
|
||||||
|
'child',
|
||||||
|
[
|
||||||
|
transition(
|
||||||
|
':leave',
|
||||||
|
[
|
||||||
|
animate('1s', style({opacity: 0})),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<div [@.disabled]="disableExp" #container>
|
||||||
|
<div [@parent]="exp" (@parent.done)="onDone($event)">
|
||||||
|
<div class="item" *ngFor="let item of items" @child (@child.done)="onDone($event)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class Cmp {
|
||||||
|
@ViewChild('container') public container: any;
|
||||||
|
|
||||||
|
disableExp = false;
|
||||||
|
exp = '';
|
||||||
|
items: any[] = [];
|
||||||
|
doneLog: any[] = [];
|
||||||
|
|
||||||
|
onDone(event: any) { this.doneLog.push(event); }
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||||
|
const engine = TestBed.get(ɵAnimationEngine);
|
||||||
|
const fixture = TestBed.createComponent(Cmp);
|
||||||
|
const cmp = fixture.componentInstance;
|
||||||
|
cmp.disableExp = true;
|
||||||
|
cmp.items = [0, 1, 2, 3, 4];
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
cmp.exp = 'empty';
|
||||||
|
cmp.items = [];
|
||||||
|
cmp.doneLog = [];
|
||||||
|
fixture.detectChanges();
|
||||||
|
flushMicrotasks();
|
||||||
|
|
||||||
|
const elms = cmp.container.nativeElement.querySelectorAll('.item');
|
||||||
|
expect(elms.length).toEqual(0);
|
||||||
|
|
||||||
|
expect(cmp.doneLog.length).toEqual(6);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue