feat(core): add support for nested animations

Now if there's a leave animation nested within a template, those will animate before the parent element is removed. This makes animating leaving elements a bit easier than before and adds a lot more flexibility to how animations can be structured.

fixes: #66476
This commit is contained in:
Jessica Janiuk
2026-01-30 09:59:28 -08:00
committed by Andrew Kushnir
parent 24c0c5a180
commit ea2016a6dc
13 changed files with 733 additions and 79 deletions
+5 -2
View File
@@ -24,11 +24,14 @@ export const ANIMATION_QUEUE = new InjectionToken<AnimationQueue>(
typeof ngDevMode !== 'undefined' && ngDevMode ? 'AnimationQueue' : '',
{
factory: () => {
const injector = inject(EnvironmentInjector);
const queue = new Set<VoidFunction>();
injector.onDestroy(() => queue.clear());
return {
queue: new Set(),
queue,
isScheduled: false,
scheduler: null,
injector: inject(EnvironmentInjector), // should be the root injector
injector,
};
},
},
@@ -0,0 +1,263 @@
/**
* @license
* Copyright Google LLC 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.dev/license
*/
import {
RunLeaveAnimationFn,
LeaveNodeAnimations,
AnimationLViewData,
} from '../animation/interfaces';
import {allLeavingAnimations} from '../animation/longest_animation';
import {
queueEnterAnimations,
addToAnimationQueue,
removeAnimationsFromQueue,
} from '../animation/queue';
import {Injector, INJECTOR} from '../di';
import {CONTAINER_HEADER_OFFSET} from './interfaces/container';
import {TNode, TNodeType} from './interfaces/node';
import {RElement} from './interfaces/renderer_dom';
import {isComponentHost, isLContainer} from './interfaces/type_checks';
import {ANIMATIONS, ID, LView, TVIEW} from './interfaces/view';
import {getComponentLViewByIndex} from './util/view_utils';
export function maybeQueueEnterAnimation(
parentLView: LView | undefined,
parent: RElement | null,
tNode: TNode,
injector: Injector,
): void {
const enterAnimations = parentLView?.[ANIMATIONS]?.enter;
if (parent !== null && enterAnimations && enterAnimations.has(tNode.index)) {
queueEnterAnimations(injector, enterAnimations);
}
}
export function runLeaveAnimationsWithCallback(
lView: LView | undefined,
tNode: TNode,
injector: Injector,
callback: Function,
) {
// It's possible that the AppRef has been destroyed, which would also destroy
// the injector tree. If this happens, we will get an error when we try to
// get the injector, so we catch it here and avoid the error and return
// safely.
try {
injector.get(INJECTOR);
} catch {
return callback(false);
}
const animations = lView?.[ANIMATIONS];
// regarding the TNode index to see if it is the same element.
if (animations?.enter?.has(tNode.index)) {
removeAnimationsFromQueue(injector, animations.enter.get(tNode.index)!.animateFns);
}
// get all nodes in the current view that are descendants of tNode and have leave animations
const nodesWithExitAnimations = aggregateDescendantAnimations(lView, tNode, animations);
if (nodesWithExitAnimations.size === 0) {
let hasNestedAnimations = false;
if (lView) {
const nestedPromises: Promise<unknown>[] = [];
collectNestedViewAnimations(lView, tNode, nestedPromises);
hasNestedAnimations = nestedPromises.length > 0;
}
if (!hasNestedAnimations) {
return callback(false);
}
}
if (lView) allLeavingAnimations.add(lView[ID]);
addToAnimationQueue(
injector,
() =>
executeLeaveAnimations(
lView,
tNode,
animations || undefined,
nodesWithExitAnimations,
callback,
),
animations || undefined,
);
}
// Identifies all elements that are descendants of `tNode` *within the same component view*
// (LView) and have active leave animations. Since `tNode` is being removed, its descendants
// will also be removed. We must execute their leave animations and wait for them to finish
// before physically removing `tNode` from the DOM.
//
// Instead of performing a potentially expensive downward traversal of the
// entire `tNode` subtree to find animated descendants, we iterate over the `leaveAnimations`
// map. This map contains all pending leave animations in the current LView and is typically
// very small.
//
// Note: Animations across LView boundaries (e.g., in child components or embedded views)
// are collected separately via `collectNestedViewAnimations`.
function aggregateDescendantAnimations(
lView: LView | undefined,
tNode: TNode,
animations: AnimationLViewData | null | undefined,
): Map<number, LeaveNodeAnimations> {
const nodesWithExitAnimations = new Map<number, LeaveNodeAnimations>();
const leaveAnimations = animations?.leave;
if (leaveAnimations && leaveAnimations.has(tNode.index)) {
nodesWithExitAnimations.set(tNode.index, leaveAnimations.get(tNode.index)!);
}
if (lView && leaveAnimations) {
for (const [index, animationData] of leaveAnimations) {
if (nodesWithExitAnimations.has(index)) continue;
// Get the tNode for the animation. This node might be a descendant of the tNode we are removing.
// If so, we need to run its leave animation as well.
const nestedTNode = lView[TVIEW].data[index] as TNode;
let parent = nestedTNode.parent;
// Traverse upward to check if `tNode` is an ancestor of `nestedTNode`
// For each animation in the map, we retrieve its corresponding TNode (`nestedTNode`) and
// traverse UP the tree using parent pointers. If we encounter `tNode` during this upward
// traversal, we know the animated element is a descendant, and we add its animation data
// to `nodesWithExitAnimations`.
while (parent) {
if (parent === tNode) {
nodesWithExitAnimations.set(index, animationData);
break;
}
parent = parent.parent;
}
}
}
return nodesWithExitAnimations;
}
function executeLeaveAnimations(
lView: LView | undefined,
tNode: TNode,
animations: AnimationLViewData | undefined,
nodesWithExitAnimations: Map<number, LeaveNodeAnimations>,
callback: Function,
) {
// it's possible that in the time between when the leave animation was
// and the time it was executed, the data structure changed. So we need
// to be safe here.
const runningAnimations: Promise<unknown>[] = [];
if (animations && animations.leave) {
for (const [index] of nodesWithExitAnimations) {
if (!animations.leave.has(index)) continue;
const currentAnimationData = animations.leave.get(index)!;
for (const animationFn of currentAnimationData.animateFns) {
const {promise} = animationFn() as ReturnType<RunLeaveAnimationFn>;
runningAnimations.push(promise);
}
animations.detachedLeaveAnimationFns = undefined;
}
}
// Also add nested view animations
if (lView) {
collectNestedViewAnimations(lView, tNode, runningAnimations);
}
if (runningAnimations.length > 0) {
const currentAnimations = animations || lView?.[ANIMATIONS];
if (currentAnimations) {
const prevRunning = currentAnimations.running;
if (prevRunning) {
runningAnimations.push(prevRunning);
}
currentAnimations.running = Promise.allSettled(runningAnimations);
runAfterLeaveAnimations(lView!, currentAnimations.running, callback);
} else {
Promise.allSettled(runningAnimations).then(() => {
if (lView) allLeavingAnimations.delete(lView[ID]);
callback(true);
});
}
} else {
if (lView) allLeavingAnimations.delete(lView[ID]);
callback(false);
}
}
/**
* Collects leave animations from nested views (components and containers)
* starting from the given TNode's children.
*/
function collectNestedViewAnimations(
lView: LView,
tNode: TNode,
collectedPromises: Promise<unknown>[],
) {
if (isComponentHost(tNode)) {
const componentView = getComponentLViewByIndex(tNode.index, lView);
collectAllViewLeaveAnimations(componentView, collectedPromises);
} else if (tNode.type & TNodeType.AnyContainer) {
const lContainer = lView[tNode.index];
if (isLContainer(lContainer)) {
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
const subView = lContainer[i] as LView;
collectAllViewLeaveAnimations(subView, collectedPromises);
}
}
}
let child = tNode.child;
while (child) {
collectNestedViewAnimations(lView, child, collectedPromises);
child = child.next;
}
}
/**
* Recursively collects all leave animations from a view and its children.
*/
function collectAllViewLeaveAnimations(view: LView, collectedPromises: Promise<unknown>[]) {
const animations = view[ANIMATIONS];
if (animations && animations.leave) {
for (const animationData of animations.leave.values()) {
for (const animationFn of animationData.animateFns) {
// We interpret the animation function to get the promise
const {promise} = animationFn() as ReturnType<RunLeaveAnimationFn>;
collectedPromises.push(promise);
}
}
}
let child = view[TVIEW].firstChild;
while (child) {
collectNestedViewAnimations(view, child, collectedPromises);
child = child.next;
}
}
function runAfterLeaveAnimations(
lView: LView,
runningAnimations: Promise<unknown>,
callback: Function,
) {
runningAnimations.then(() => {
// We only want to clear the running flag and the allLeavingAnimations set if
// the current running animation is the same as the one we just waited for.
// If it's different, it means another animation started while we were waiting,
// and that other animation is now responsible for clearing the flag.
if (lView[ANIMATIONS]?.running === runningAnimations) {
lView[ANIMATIONS]!.running = undefined;
allLeavingAnimations.delete(lView[ID]);
}
callback(true);
});
}
+1 -77
View File
@@ -82,14 +82,8 @@ import {assertTNodeType} from './node_assert';
import {profiler} from './profiler';
import {ProfilerEvent} from '../../primitives/devtools';
import {getLViewParent, getNativeByTNode, unwrapRNode} from './util/view_utils';
import {allLeavingAnimations} from '../animation/longest_animation';
import {Injector} from '../di';
import {
addToAnimationQueue,
queueEnterAnimations,
removeAnimationsFromQueue,
} from '../animation/queue';
import {RunLeaveAnimationFn} from '../animation/interfaces';
import {maybeQueueEnterAnimation, runLeaveAnimationsWithCallback} from './node_animations';
const enum WalkTNodeTreeAction {
/** node create in the native environment. Run on initial creation. */
@@ -108,18 +102,6 @@ const enum WalkTNodeTreeAction {
Destroy = 3,
}
function maybeQueueEnterAnimation(
parentLView: LView | undefined,
parent: RElement | null,
tNode: TNode,
injector: Injector,
): void {
const enterAnimations = parentLView?.[ANIMATIONS]?.enter;
if (parent !== null && enterAnimations && enterAnimations.has(tNode.index)) {
queueEnterAnimations(injector, enterAnimations);
}
}
/**
* NOTE: for performance reasons, the possible actions are inlined within the function instead of
* being passed as an argument.
@@ -382,64 +364,6 @@ function cleanUpView(tView: TView, lView: LView): void {
}
}
function runLeaveAnimationsWithCallback(
lView: LView | undefined,
tNode: TNode,
injector: Injector,
callback: Function,
) {
const animations = lView?.[ANIMATIONS];
if (animations?.enter?.has(tNode.index)) {
removeAnimationsFromQueue(injector, animations.enter.get(tNode.index)!.animateFns);
}
if (animations == null || animations.leave == undefined || !animations.leave.has(tNode.index))
return callback(false);
if (lView) allLeavingAnimations.add(lView[ID]);
addToAnimationQueue(
injector,
() => {
// it's possible that in the time between when the leave animation was
// and the time it was executed, the data structure changed. So we need
// to be safe here.
if (animations.leave && animations.leave.has(tNode.index)) {
const leaveAnimationMap = animations.leave;
const leaveAnimations = leaveAnimationMap.get(tNode.index);
const runningAnimations = [];
if (leaveAnimations) {
for (let index = 0; index < leaveAnimations.animateFns.length; index++) {
const animationFn = leaveAnimations.animateFns[index];
const {promise} = animationFn() as ReturnType<RunLeaveAnimationFn>;
runningAnimations.push(promise);
}
animations.detachedLeaveAnimationFns = undefined;
}
animations.running = Promise.allSettled(runningAnimations);
runAfterLeaveAnimations(lView!, callback);
} else {
if (lView) allLeavingAnimations.delete(lView[ID]);
callback(false);
}
},
animations,
);
}
function runAfterLeaveAnimations(lView: LView, callback: Function) {
const runningAnimations = lView[ANIMATIONS]?.running;
if (runningAnimations) {
runningAnimations.then(() => {
lView[ANIMATIONS]!.running = undefined;
allLeavingAnimations.delete(lView[ID]);
callback(true);
});
return;
}
callback(false);
}
/** Removes listeners and unsubscribes from output subscriptions */
function processCleanups(tView: TView, lView: LView): void {
ngDevMode && assertNotReactive(processCleanups.name);
@@ -652,5 +652,71 @@ describe('signal inputs', () => {
fixture.detectChanges(); // Detect changes after animation completes and element is removed
expect(fixture.nativeElement.querySelector('notification')).toBeNull(); // Verify element is removed
}));
it('should support nested animate.leave with signal inputs via host binding', fakeAsync(() => {
const styles = `
.fade-out {
animation: fade-out 500ms;
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
`;
@Component({
selector: 'child-cmp',
template: '<div class="target">Content</div>',
host: {
'[animate.leave]': 'animClass()',
},
})
class ChildCmp {
animClass = input.required<string>();
}
@Component({
selector: 'test-cmp',
styles: [styles],
imports: [ChildCmp],
template: `
@if (show()) {
<div class="parent">
<child-cmp [animClass]="'fade-out'" class="child-comp" />
</div>
}
`,
encapsulation: ViewEncapsulation.None,
})
class TestCmp {
show = signal(true);
}
TestBed.configureTestingModule({animationsEnabled: true});
const fixture = TestBed.createComponent(TestCmp);
const cmp = fixture.componentInstance;
fixture.detectChanges();
const target = fixture.nativeElement.querySelector('.child-comp');
expect(target).toBeTruthy();
cmp.show.set(false);
fixture.detectChanges();
tickAnimationFrames(1);
const targetAfter = fixture.nativeElement.querySelector('.child-comp');
expect(targetAfter).withContext('Nested component should persist').not.toBeNull();
if (targetAfter) {
expect(targetAfter.classList.contains('fade-out'))
.withContext('Should have animation class from signal input host binding')
.toBeTruthy();
targetAfter.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade-out'}));
tick();
expect(fixture.nativeElement.querySelector('.child-comp')).toBeNull();
}
}));
});
});
@@ -0,0 +1,366 @@
/**
* @license
* Copyright Google LLC 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.dev/license
*/
import {Component, signal, ViewEncapsulation, OnDestroy, destroyPlatform} from '@angular/core';
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
import {BrowserTestingModule, platformBrowserTesting} from '@angular/platform-browser/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {tickAnimationFrames} from '../animation_utils/tick_animation_frames';
describe('Nested animate.leave', () => {
beforeEach(() => {
destroyPlatform();
TestBed.resetTestEnvironment();
TestBed.initTestEnvironment(
[BrowserTestingModule, NoopAnimationsModule],
platformBrowserTesting(),
);
});
it('should wait for nested animations inside @if', fakeAsync(() => {
const styles = `
.fade {
animation: fade-out 500ms;
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
`;
@Component({
selector: 'test-cmp',
template: `
<div>
@if (show()) {
<div class="parent">
<div animate.leave="fade" class="child">Child</div>
</div>
}
</div>
`,
styles: [styles],
encapsulation: ViewEncapsulation.None,
})
class TestCmp {
show = signal(true);
}
TestBed.configureTestingModule({animationsEnabled: true});
const fixture = TestBed.createComponent(TestCmp);
const cmp = fixture.componentInstance;
fixture.detectChanges();
const child = fixture.nativeElement.querySelector('.child');
expect(child).toBeTruthy();
cmp.show.set(false);
fixture.detectChanges();
tickAnimationFrames(1);
// In current Angular, this likely fails immediately because parent is removed, taking child with it.
// We expect child to remain if we support nested animations.
const childAfter = fixture.nativeElement.querySelector('.child');
// This expectation confirms if it works or fails.
// If it works, childAfter is NOT null.
// If it fails (current behavior), childAfter IS null.
// We want to prove it fails currently, so we expect rejection or we just assert strict truth.
// I will use strict truth to demonstrate failure.
expect(childAfter).withContext('Child element should persist during animation').not.toBeNull();
if (childAfter) {
expect(childAfter.classList.contains('fade'))
.withContext('Child should get animation class')
.toBeTruthy();
// Simulate end
childAfter.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade-out'}));
tick();
}
}));
it('should support nested host bindings', fakeAsync(() => {
const styles = `
.fade {
animation: fade-out 500ms;
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
`;
@Component({
selector: 'fade-cmp',
host: {'animate.leave': 'fade'},
template: '<p>I should fade</p>',
encapsulation: ViewEncapsulation.None,
})
class FadeComponent {}
@Component({
selector: 'test-cmp',
styles: [styles],
imports: [FadeComponent],
template: `
<div>
@if (show()) {
<div class="parent">
<fade-cmp class="child" />
</div>
}
</div>
`,
encapsulation: ViewEncapsulation.None,
})
class TestCmp {
show = signal(true);
}
TestBed.configureTestingModule({animationsEnabled: true});
const fixture = TestBed.createComponent(TestCmp);
const cmp = fixture.componentInstance;
fixture.detectChanges();
const child = fixture.nativeElement.querySelector('.child');
expect(child).toBeTruthy();
cmp.show.set(false);
fixture.detectChanges();
tickAnimationFrames(1);
const childAfter = fixture.nativeElement.querySelector('.child');
expect(childAfter).withContext('Child element should persist during animation').not.toBeNull();
if (childAfter) {
expect(childAfter.classList.contains('fade'))
.withContext('Child should get animation class from host binding')
.toBeTruthy();
childAfter.dispatchEvent(new AnimationEvent('animationend', {animationName: 'fade-out'}));
tick();
const childFinal = fixture.nativeElement.querySelector('.child');
expect(childFinal).toBeNull();
}
}));
it('should support nested function syntax', fakeAsync(() => {
let completeFn: Function | undefined;
@Component({
selector: 'test-cmp',
template: `
<div>
@if (show()) {
<div class="parent">
<div (animate.leave)="animateFn($event)" class="child">Child</div>
</div>
}
</div>
`,
encapsulation: ViewEncapsulation.None,
})
class TestCmp {
show = signal(true);
animateFn = (event: any) => {
event.target.classList.add('custom-anim');
completeFn = event.animationComplete;
};
}
TestBed.configureTestingModule({animationsEnabled: true});
const fixture = TestBed.createComponent(TestCmp);
const cmp = fixture.componentInstance;
fixture.detectChanges();
cmp.show.set(false);
fixture.detectChanges();
tickAnimationFrames(1);
const childAfter = fixture.nativeElement.querySelector('.child');
expect(childAfter).withContext('Child element should persist during animation').not.toBeNull();
if (childAfter) {
expect(childAfter.classList.contains('custom-anim'))
.withContext('Child should get class from animation function')
.toBeTruthy();
expect(completeFn).withContext('animationComplete should be captured').toBeDefined();
completeFn!();
tick();
const childFinal = fixture.nativeElement.querySelector('.child');
expect(childFinal).toBeNull();
}
}));
it('should support nested animate.leave with component host binding (GitHub #66476)', fakeAsync(() => {
// Reproduction from https://github.com/angular/angular/issues/66476
// Structure: Host -> @if -> Child -> Popup (with host animation)
const styles = `
.popup-overlay {
animation: fade-in 0.3s;
}
.popup-overlay-leave {
animation: fade-out 0.3s;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
`;
@Component({
selector: 'app-popup',
template: `
<div class="popup-overlay" animate.leave="popup-overlay-leave">
<div class="popup">
<ng-content></ng-content>
</div>
</div>
`,
styles: [styles],
encapsulation: ViewEncapsulation.None,
host: {
'(animate.leave)': 'animateLeave($event)',
},
})
class PopupComponent {
animateLeave(event: any) {
// In the issue, they use setTimeout to call animationComplete.
// We simulate this async completion.
setTimeout(() => event.animationComplete(), 300);
}
}
@Component({
selector: 'app-child',
template: ` <app-popup> Projected content. </app-popup> `,
imports: [PopupComponent],
})
class ChildComponent {}
@Component({
selector: 'app-host',
template: `
@if (showChild()) {
<app-child></app-child>
}
`,
imports: [ChildComponent],
})
class HostComponent {
showChild = signal(true);
}
TestBed.configureTestingModule({animationsEnabled: true});
const fixture = TestBed.createComponent(HostComponent);
const host = fixture.componentInstance;
fixture.detectChanges();
const popup = fixture.nativeElement.querySelector('app-popup');
expect(popup).toBeTruthy();
host.showChild.set(false);
fixture.detectChanges();
tickAnimationFrames(1);
const popupAfter = fixture.nativeElement.querySelector('app-popup');
expect(popupAfter).withContext('Popup should persist due to host animation').not.toBeNull();
// Advance time for the setTimeout in animateLeave
tick(300);
// The nested animation needs to complete as well.
// Query the nested element (popup overlay) that has the animation.
const popupOverlay = fixture.nativeElement.querySelector('.popup-overlay');
if (popupOverlay) {
popupOverlay.dispatchEvent(
new AnimationEvent('animationend', {animationName: 'fade-out', bubbles: true}),
);
tick();
}
// Attempt to verify removal
const popupFinal = fixture.nativeElement.querySelector('app-popup');
expect(popupFinal).toBeFalsy();
}));
it('should not throw "Injector destroyed" when a parent with leave animations is destroyed with nested components', async () => {
@Component({
selector: 'child-comp',
standalone: true,
template: '<div>Child</div>',
})
class ChildComp implements OnDestroy {
ngOnDestroy() {}
}
@Component({
selector: 'parent-comp',
standalone: true,
imports: [ChildComp],
template: `
<div animate.leave="leaving">
<child-comp />
</div>
`,
})
class ParentComp {
leaving = () => ({promise: Promise.resolve()});
}
TestBed.configureTestingModule({});
const fixture = TestBed.createComponent(ParentComp);
fixture.detectChanges();
// Before the fix, the framework would recursively dive into ChildComp's
// internal view to collect animations. During a full destruction of the
// component tree, this could lead to starting animations or async tasks
// that resolve after the component's injector is already gone.
fixture.destroy();
// If we reach here without an exception, the regression is prevented.
});
it('should still support nested animations within the same template', () => {
// This test ensures that while we stopped recursing into component views,
// we still support nested elements within the same view having animations.
@Component({
selector: 'nested-view-comp',
standalone: true,
template: `
<div animate.leave="leaving">
@if (showNested) {
<div animate.leave="leaving">Nested</div>
}
</div>
`,
})
class NestedViewComp {
showNested = true;
leaving = () => ({promise: Promise.resolve()});
}
TestBed.configureTestingModule({});
const fixture = TestBed.createComponent(NestedViewComp);
fixture.detectChanges();
// This should not throw and correctly handle the nested animations.
fixture.destroy();
});
});
@@ -323,6 +323,7 @@
"addServerStyles",
"addToAnimationQueue",
"addToEndOfViewTree",
"aggregateDescendantAnimations",
"allLeavingAnimations",
"allocExpando",
"allocLFrame",
@@ -369,8 +370,10 @@
"cleanUpView",
"cloakAndComputeStyles",
"cloakElement",
"collectAllViewLeaveAnimations",
"collectNativeNodes",
"collectNativeNodesInLContainer",
"collectNestedViewAnimations",
"computeStaticStyling",
"computeStyle",
"concatStringsWithSpace",
@@ -461,6 +464,7 @@
"executeCheckHooks",
"executeContentQueries",
"executeInitAndCheckHooks",
"executeLeaveAnimations",
"executeOnDestroys",
"executeTemplate",
"executeViewQueryFn",
@@ -253,6 +253,7 @@
"addToArray",
"addToEndOfViewTree",
"addViewToDOM",
"aggregateDescendantAnimations",
"allLeavingAnimations",
"allocExpando",
"allocLFrame",
@@ -286,8 +287,10 @@
"captureError",
"checkStable",
"cleanUpView",
"collectAllViewLeaveAnimations",
"collectNativeNodes",
"collectNativeNodesInLContainer",
"collectNestedViewAnimations",
"computeStaticStyling",
"concatStringsWithSpace",
"config",
@@ -369,6 +372,7 @@
"executeCheckHooks",
"executeContentQueries",
"executeInitAndCheckHooks",
"executeLeaveAnimations",
"executeListenerWithErrorHandling",
"executeOnDestroys",
"executeTemplate",
@@ -296,6 +296,7 @@
"addToArray",
"addToEndOfViewTree",
"addViewToDOM",
"aggregateDescendantAnimations",
"allLeavingAnimations",
"allocExpando",
"allocLFrame",
@@ -330,8 +331,10 @@
"checkStable",
"classIndexOf",
"cleanUpView",
"collectAllViewLeaveAnimations",
"collectNativeNodes",
"collectNativeNodesInLContainer",
"collectNestedViewAnimations",
"computeStaticStyling",
"concatStringsWithSpace",
"config",
@@ -410,6 +413,7 @@
"executeCheckHooks",
"executeContentQueries",
"executeInitAndCheckHooks",
"executeLeaveAnimations",
"executeOnDestroys",
"executeTemplate",
"executeViewQueryFn",
@@ -352,6 +352,7 @@
"addToEndOfViewTree",
"addValidators",
"addViewToDOM",
"aggregateDescendantAnimations",
"allLeavingAnimations",
"allocExpando",
"allocLFrame",
@@ -400,8 +401,10 @@
"cleanUpView",
"coerceToAsyncValidator",
"coerceToValidator",
"collectAllViewLeaveAnimations",
"collectNativeNodes",
"collectNativeNodesInLContainer",
"collectNestedViewAnimations",
"collectResidual",
"collectStylingFromDirectives",
"collectStylingFromTAttrs",
@@ -507,6 +510,7 @@
"executeCheckHooks",
"executeContentQueries",
"executeInitAndCheckHooks",
"executeLeaveAnimations",
"executeListenerWithErrorHandling",
"executeOnDestroys",
"executeSchedule",
@@ -356,6 +356,7 @@
"addToEndOfViewTree",
"addValidators",
"addViewToDOM",
"aggregateDescendantAnimations",
"allLeavingAnimations",
"allocExpando",
"allocLFrame",
@@ -402,8 +403,10 @@
"cleanUpView",
"coerceToAsyncValidator",
"coerceToValidator",
"collectAllViewLeaveAnimations",
"collectNativeNodes",
"collectNativeNodesInLContainer",
"collectNestedViewAnimations",
"collectResidual",
"collectStylingFromDirectives",
"collectStylingFromTAttrs",
@@ -509,6 +512,7 @@
"executeCheckHooks",
"executeContentQueries",
"executeInitAndCheckHooks",
"executeLeaveAnimations",
"executeListenerWithErrorHandling",
"executeOnDestroys",
"executeSchedule",
@@ -292,6 +292,7 @@
"addServerStyles",
"addToAnimationQueue",
"addToEndOfViewTree",
"aggregateDescendantAnimations",
"allLeavingAnimations",
"allocExpando",
"allocLFrame",
@@ -334,8 +335,10 @@
"cleanupLView",
"cleanupMatchingDehydratedViews",
"clearElementContents",
"collectAllViewLeaveAnimations",
"collectNativeNodes",
"collectNativeNodesInLContainer",
"collectNestedViewAnimations",
"computeStaticStyling",
"concatStringsWithSpace",
"config",
@@ -425,6 +428,7 @@
"executeCheckHooks",
"executeContentQueries",
"executeInitAndCheckHooks",
"executeLeaveAnimations",
"executeOnDestroys",
"executeSchedule",
"executeTemplate",
@@ -408,6 +408,7 @@
"addViewToDOM",
"advanceActivatedRoute",
"afterNextNavigation",
"aggregateDescendantAnimations",
"allLeavingAnimations",
"allocExpando",
"allocLFrame",
@@ -450,8 +451,10 @@
"checkStable",
"classIndexOf",
"cleanUpView",
"collectAllViewLeaveAnimations",
"collectNativeNodes",
"collectNativeNodesInLContainer",
"collectNestedViewAnimations",
"collectQueryResults",
"combineLatest",
"combineLatestInit",
@@ -601,6 +604,7 @@
"executeCheckHooks",
"executeContentQueries",
"executeInitAndCheckHooks",
"executeLeaveAnimations",
"executeListenerWithErrorHandling",
"executeOnDestroys",
"executeSchedule",
@@ -233,6 +233,7 @@
"addServerStyles",
"addToAnimationQueue",
"addToEndOfViewTree",
"aggregateDescendantAnimations",
"allLeavingAnimations",
"allocExpando",
"allocLFrame",
@@ -265,8 +266,10 @@
"captureError",
"checkStable",
"cleanUpView",
"collectAllViewLeaveAnimations",
"collectNativeNodes",
"collectNativeNodesInLContainer",
"collectNestedViewAnimations",
"computeStaticStyling",
"concatStringsWithSpace",
"config",
@@ -340,6 +343,7 @@
"executeCheckHooks",
"executeContentQueries",
"executeInitAndCheckHooks",
"executeLeaveAnimations",
"executeOnDestroys",
"executeTemplate",
"executeViewQueryFn",