fix(animations): ensure multi-level route leave animations are queryable (#20787)

Closes #19807

PR Close #20787
This commit is contained in:
Matias Niemelä 2017-12-04 16:00:05 -08:00 committed by Jason Aden
parent d098cf5a8b
commit 13e663c232
2 changed files with 89 additions and 4 deletions

View File

@ -312,9 +312,13 @@ export class AnimationTransitionNamespace {
// If there are no animations found for any of the nodes then clear the cache
// for the element.
this._engine.driver.query(rootElement, NG_TRIGGER_SELECTOR, true).forEach(elm => {
// this means that an inner remove() operation has already kicked off
// the animation on this element...
if (elm[REMOVAL_FLAG]) return;
const namespaces = this._engine.fetchNamespacesByElement(elm);
if (namespaces.size) {
namespaces.forEach(ns => { ns.triggerLeaveAnimation(elm, context, false, true); });
namespaces.forEach(ns => ns.triggerLeaveAnimation(elm, context, false, true));
} else {
this.clearElementCache(elm);
}

View File

@ -5,13 +5,13 @@
* 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 {animate, animateChild, query, style, transition, trigger, ɵAnimationGroupPlayer as AnimationGroupPlayer} from '@angular/animations';
import {animate, animateChild, group, query, sequence, style, transition, trigger, ɵAnimationGroupPlayer as AnimationGroupPlayer} from '@angular/animations';
import {AnimationDriver, ɵAnimationEngine} from '@angular/animations/browser';
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing';
import {Component, HostBinding} from '@angular/core';
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
import {TestBed, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {Router, RouterOutlet} from '@angular/router';
import {ActivatedRoute, Router, RouterOutlet} from '@angular/router';
import {RouterTestingModule} from '@angular/router/testing';
export function main() {
@ -19,7 +19,14 @@ export function main() {
if (typeof Element == 'undefined') return;
describe('Animation Router Tests', function() {
function getLog(): MockAnimationPlayer[] {
return MockAnimationDriver.log as MockAnimationPlayer[];
}
function resetLog() { MockAnimationDriver.log = []; }
beforeEach(() => {
resetLog();
TestBed.configureTestingModule({
imports: [RouterTestingModule, BrowserAnimationsModule],
providers: [{provide: AnimationDriver, useClass: MockAnimationDriver}]
@ -437,6 +444,80 @@ export function main() {
expect(ip1.element.innerText).toEqual('page1');
expect(ip2.element.innerText).toEqual('page2');
}));
it('should allow a recursive set of :leave animations to occur for nested routes',
fakeAsync(() => {
@Component({selector: 'ani-cmp', template: '<router-outlet name="recur"></router-outlet>'})
class ContainerCmp {
constructor(private _router: Router) {}
log: string[] = [];
enter() { this._router.navigateByUrl('/(recur:recur/nested)'); }
leave() { this._router.navigateByUrl('/'); }
}
@Component({
selector: 'recur-page',
template: 'Depth: {{ depth }} \n <router-outlet></router-outlet>',
animations: [
trigger(
'pageAnimations',
[
transition(':leave', [group([
sequence([style({opacity: 1}), animate('1s', style({opacity: 0}))]),
query('@*', animateChild(), {optional: true})
])]),
]),
]
})
class RecurPageCmp {
@HostBinding('@pageAnimations') public animatePage = true;
@HostBinding('attr.data-depth') public depth = 0;
constructor(private container: ContainerCmp, private route: ActivatedRoute) {
this.route.data.subscribe(data => {
this.container.log.push(`DEPTH ${data.depth}`);
this.depth = data.depth;
});
}
}
TestBed.configureTestingModule({
declarations: [ContainerCmp, RecurPageCmp],
imports: [RouterTestingModule.withRoutes([{
path: 'recur',
component: RecurPageCmp,
outlet: 'recur',
data: {depth: 0},
children: [{path: 'nested', component: RecurPageCmp, data: {depth: 1}}]
}])]
});
const fixture = TestBed.createComponent(ContainerCmp);
const cmp = fixture.componentInstance;
cmp.enter();
tick();
fixture.detectChanges();
flushMicrotasks();
expect(cmp.log).toEqual([
'DEPTH 0',
'DEPTH 1',
]);
cmp.leave();
tick();
fixture.detectChanges();
const players = getLog();
expect(players.length).toEqual(2);
const [p1, p2] = players;
expect(p1.element.getAttribute('data-depth')).toEqual('0');
expect(p2.element.getAttribute('data-depth')).toEqual('1');
}));
});
}