Repository: angular/angular
File: packages/core/src/animation/utils.ts (TypeScript)

Code before:
 * in its own overlay pane), we need to end the animation for the former
 * node and remove it right away to prevent duplicate nodes showing up.
 *
 * Leaving elements in the same parent are left alone — their leave
 * animation will complete naturally and remove them from the DOM.
 */
export function cancelLeavingNodes(tNode: TNode, newElement: HTMLElement): void {
  const nodes = leavingNodes.get(tNode);
  if (!nodes || nodes.length === 0) return;

  const newParent = newElement.parentNode;
  const prevSibling = newElement.previousSibling;

  for (let i = nodes.length - 1; i >= 0; i--) {
    const leavingEl = nodes[i];
    const leavingParent = leavingEl.parentNode;
    // Cancel if the leaving element is:
    // - The direct previousSibling of the new element. This is reliable
    //   because Angular inserts new elements at the same position (before
    //   the container anchor) where the leaving element was, making them
    //   always adjacent. Covers @if toggling and same-VCR toggling.
    // - In a different DOM parent (overlay/portal case where each instance
    //   renders in its own container, e.g. CDK Overlay).
    // Leaving elements in the same parent that are NOT the previousSibling
    // are left alone (e.g. @for items animating out at different positions).
    if (leavingEl === prevSibling || (leavingParent && leavingParent !== newParent)) {
      nodes.splice(i, 1);
      leavingEl.dispatchEvent(new CustomEvent('animationend', {detail: {cancel: true}}));
      leavingEl.parentNode?.removeChild(leavingEl);
    }
  }
}

/**
 * Tracks the nodes list of nodes that are leaving the DOM so we can cancel any leave animations
 * and remove the node before adding a new entering instance of the DOM node. This prevents
 * duplicates from showing up on screen mid-animation.
 */
export function trackLeavingNodes(tNode: TNode, el: HTMLElement): void {
  // We need to track this tNode's element just to be sure we don't add
  // a new RNode for this TNode while this one is still animating away.
  // once the animation is complete, we remove this reference.
  const nodes = leavingNodes.get(tNode);
  if (nodes) {
    if (!nodes.includes(el)) {
      nodes.push(el);
    }
  } else {
    leavingNodes.set(tNode, [el]);
  }
}

Review comment (suggestion):
I am a little concerned about this line and the potential for `null === null` in the case that newParent, prevSibling, or leavingEl are null. It's probably worth adding null check guards for safety.

Code after:
 * in its own overlay pane), we need to end the animation for the former
 * node and remove it right away to prevent duplicate nodes showing up.
 *
 * Leaving elements in the same parent are left alone — their leave
 * animation will complete naturally and remove them from the DOM.
 */
export function cancelLeavingNodes(tNode: TNode, newElement: HTMLElement): void {
  const nodes = leavingNodes.get(tNode);
  if (!nodes || nodes.length === 0) return;

  const newParent = newElement.parentNode;
  const prevSibling = newElement.previousSibling;

  for (let i = nodes.length - 1; i >= 0; i--) {
    const leavingEl = nodes[i];
    const leavingParent = leavingEl.parentNode;
    // Cancel if the leaving element is:
    // - The direct previousSibling of the new element. This is reliable
    //   because Angular inserts new elements at the same position (before
    //   the container anchor) where the leaving element was, making them
    //   always adjacent. Covers @if toggling and same-VCR toggling.
    // - In a different DOM parent (overlay/portal case where each instance
    //   renders in its own container, e.g. CDK Overlay).
    // Leaving elements in the same parent that are NOT the previousSibling
    // are left alone (e.g. @for items animating out at different positions).
    if (
      (prevSibling && leavingEl === prevSibling) ||
      (leavingParent && newParent && leavingParent !== newParent)
    ) {
      nodes.splice(i, 1);
      leavingEl.dispatchEvent(new CustomEvent('animationend', {detail: {cancel: true}}));
      leavingEl.parentNode?.removeChild(leavingEl);
    }
  }
}

/**
 * Tracks the nodes list of nodes that are leaving the DOM so we can cancel any leave animations
 * and remove the node before adding a new entering instance of the DOM node. This prevents
 * duplicates from showing up on screen mid-animation.
 */
export function trackLeavingNodes(tNode: TNode, el: HTMLElement): void {
  // We need to track this tNode's element just to be sure we don't add
  // a new RNode for this TNode while this one is still animating away.
  // once the animation is complete, we remove this reference.
  const nodes = leavingNodes.get(tNode);
  if (nodes) {
    if (!nodes.includes(el)) {
      nodes.push(el);
    }
  } else {