feat(core): re-introduce nested leave animations scoped to component boundaries#67647
Conversation
f39855a to
67c38af
Compare
…boundaries This commit re-introduces support for nested leave animations with a critical adjustment to prevent cross-component blocking. Wait for nested inner `animate.leave` transitions natively only when they exist within the same component's view or its embedded tracking structures (like `@if` and `@for`). This resolves the issue where route navigations and parental destruction would excessively stall by traversing down into child component architectures to wait for their distinct leaf animations. BREAKING CHANGE: Leave animations are no longer limited to the element being removed. Fixes angular#67633
c6d85c8 to
72b6269
Compare
atscott
left a comment
There was a problem hiding this comment.
Review of PR 67647: Nested leave animations
This PR re-introduces support for nested leave animations but stops traversal at component boundaries to prevent excessive blocking during route navigations and parental destruction.
Here is an analysis of how this PR interacts with content projection and transplanted views, and whether the behavior makes sense:
1. Content Projection (<ng-content>)
Behavior:
Projected nodes physically reside within the child component's DOM, but their TNodes and logical state belong to the parent component's LView (where the content was declared).
Because the PR explicitly avoids traversing into component views (isComponentHost check was removed) and instead only traverses LContainers within the current view hierarchy:
- If the child component destroys the
<ng-content>projection insertion point (e.g., via an@ifinside the child component), the child component will not wait for leave animations on the projected nodes. It has no visibility into the parent'sLViewwhere those animations are registered. - If the parent component hides the
<child-component>(e.g., via an@ifaround the child), the parent'sLViewexecutes leave animations. The projected nodes' leave animations will be collected and waited for, because they are tracked directly in the parent'sLView'sanimations.leavemap.
Conclusion: Makes Sense 👍
This behavior is consistent with Angular's mental model. Animations defined in a component's template belong to that component. If a component is destroyed, it should wait for its own animations. If a child component simply hides projected content, it shouldn't be blocked by animations defined in the parent's context without the parent's knowledge.
2. Transplanted Views (e.g., *ngTemplateOutlet or structural directives)
Behavior:
Transplanted views are created from a TemplateRef defined in one component but inserted into an LContainer within another component.
The PR implements collectNestedViewAnimations as follows:
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;
if (subView[TVIEW].type === TViewType.Embedded) { // <-- The key check
collectAllViewLeaveAnimations(subView, collectedPromises);
}
}
}
}Because transplanted views are instances of TViewType.Embedded residing inside an LContainer, they will be traversed and their leave animations will be collected and waited for by the component that is hosting the insertion point.
Interestingly, if you use ViewContainerRef.createComponent() to dynamically insert a full component into an LContainer, its subView[TVIEW].type is TViewType.Root (not Embedded). Therefore, dynamically inserted components will not be waited for, which perfectly aligns with the PR's goal of not blocking on component boundaries!
Conclusion: Makes Sense 👍
If a component hosts a dynamic embedded view (even if the template was defined elsewhere), that view is acting as a granular piece of UI within the host component. Waiting for its leave animations before destruction is the correct and expected behavior. At the same time, maintaining the boundary for dynamically inserted components keeps the performance and blocking characteristics consistent with statically declared components.
Summary
The logic in aggregateDescendantAnimations and collectNestedViewAnimations thoughtfully bounds the wait time to the current component's logical DOM structures (including embedded views and transplanted templates) while successfully avoiding the trap of waiting for child component architectures. The behavior for both content projection and transplanted views is logically sound.
|
Caretaker note: Presubmit is green after deflake. Safe to merge. |
|
This PR was merged into the repository. The changes were merged into the following branches:
|
This commit re-introduces support for nested leave animations with a critical adjustment to prevent cross-component blocking. Wait for nested inner
animate.leavetransitions natively only when they exist within the same component's view or its embedded tracking structures (like@ifand@for).This resolves the issue where route navigations and parental destruction would excessively stall by traversing down into child component architectures to wait for their distinct leaf animations.
BREAKING CHANGE: Leave animations are no longer limited to the element being removed.
Fixes #67633