MCPcopy Create free account
hub / github.com/angular/angular / cancelLeavingNodes

Function cancelLeavingNodes

packages/core/src/animation/utils.ts:180–229  ·  view source on GitHub ↗
(tNode: TNode, newElement: HTMLElement, newLView?: LView)

Source from the content-addressed store, hash-verified

178 * `TNode`) from the same logical view being re-rendered into a new DOM parent.
179 */
180export function cancelLeavingNodes(tNode: TNode, newElement: HTMLElement, newLView?: LView): void {
181 const nodes = leavingNodes.get(tNode);
182 if (!nodes || nodes.length === 0) return;
183
184 const newParent = newElement.parentNode;
185 const prevSibling = newElement.previousSibling;
186 const newDeclarationView = getDeclarationView(newLView);
187
188 for (let i = nodes.length - 1; i >= 0; i--) {
189 const {el: leavingEl, declarationView: leavingDeclarationView} = nodes[i];
190 const leavingParent = leavingEl.parentNode;
191 // Cancel if the leaving element is:
192 // - The direct previousSibling of the new element. This is reliable
193 // because Angular inserts new elements at the same position (before
194 // the container anchor) where the leaving element was, making them
195 // always adjacent. Covers @if toggling and same-VCR toggling.
196 // - The leaving element IS the new element. This happens when a node is moved
197 // (e.g., drag-and-drop reordering). We must cancel its pending leave animation
198 // and ensure it's not physically removed from the DOM by marking it as reused.
199 if (leavingEl === newElement) {
200 nodes.splice(i, 1);
201 reusedNodes.add(leavingEl);
202 leavingEl.dispatchEvent(new CustomEvent('animationend', {detail: {cancel: true}}));
203 } else if (prevSibling && leavingEl === prevSibling) {
204 nodes.splice(i, 1);
205 leavingEl.dispatchEvent(new CustomEvent('animationend', {detail: {cancel: true}}));
206 leavingEl.parentNode?.removeChild(leavingEl);
207 } else if (leavingParent && newParent && leavingParent !== newParent) {
208 // The leaving element is in a different DOM parent than the entering one.
209 // This is ambiguous: it can be the same logical view re-rendered into a new
210 // container (e.g. a dynamic component re-created in a fresh CDK overlay
211 // pane), which must be de-duplicated by removing it immediately; or it can
212 // be a *distinct* instance of the same template (accordions, exclusive-
213 // expansion menus, master/detail nav) that merely shares this `TNode` and
214 // is legitimately leaving in its own parent. Only force-remove when the
215 // entering element belongs to the SAME declaration view as the leaving one
216 // — i.e. a true re-render of the same logical view. Distinct instances are
217 // left alone so their `animate.leave` runs to completion.
218 const sameLogicalView =
219 newDeclarationView === null ||
220 leavingDeclarationView === null ||
221 newDeclarationView === leavingDeclarationView;
222 if (sameLogicalView) {
223 nodes.splice(i, 1);
224 leavingEl.dispatchEvent(new CustomEvent('animationend', {detail: {cancel: true}}));
225 leavingEl.parentNode?.removeChild(leavingEl);
226 }
227 }
228 }
229}
230
231/**
232 * Tracks the nodes list of nodes that are leaving the DOM so we can cancel any leave animations

Callers 1

Calls 5

getDeclarationViewFunction · 0.85
getMethod · 0.65
addMethod · 0.65
removeChildMethod · 0.65
dispatchEventMethod · 0.45

Tested by

no test coverage detected