MCPcopy
hub / github.com/angular/angular / cancelLeavingNodes

Function cancelLeavingNodes

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

Source from the content-addressed store, hash-verified

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

Used in the wild real call sites across dependent graphs

searching dependent graphs…