Skip to content

bug: StackController transition crashes when router outlet is destroyed mid-transition #31296

Closed

Description

Prerequisites

Ionic Framework Version

v8.x (reproduced on 8.8.13; the unguarded code is still present on main and in 8.8.16-nightly.20260724)

Current Behavior

StackController.destroy() sets this.containerEl = undefined! (stack-controller.ts#L240). A transition that is still queued (via the wait() serialization) can resolve after the ion-router-outlet was destroyed — e.g. an interrupted, re-entrant, or cancelled navigation during fast back-navigation. When it does, transition() dereferences the now-undefined element:

const containerEl = this.containerEl;            // undefined after destroy()
if ((containerEl as any).commit) {               // TypeError

which throws:

TypeError: undefined is not an object (evaluating 'containerEl.commit')
    at transition

We see this at meaningful scale in a production Ionic Angular + Capacitor app (Datadog RUM): several hundred crash events per week from a single app build, concentrated on fast back-navigation between nested router outlets (message-thread → channel list). The minified symbol varies per build (d.commit, Fn.commit, Tt.commit, Vn.commit), but all decode to this same line.

Expected Behavior

A transition that resolves after its outlet was destroyed should be a no-op (resolve false, the method's existing fallback) rather than throwing an unhandled TypeError.

Steps to Reproduce

The bug is a race between a queued transition and outlet destruction, so it is timing-dependent rather than deterministically reproducible in a minimal app:

  1. Navigate forward into a page hosted by a nested ion-router-outlet.
  2. Trigger back-navigation twice in quick succession (double-tap back / rapid NavController.back() calls) so that a second transition queues behind the first via StackController.wait().
  3. If the first navigation destroys the nested outlet (route leaves the outlet's subtree), the queued transition resolves against the destroyed stack controller and throws.

The code path makes the defect visible without a live repro: destroy() intentionally nulls containerEl (with a lint-suppressed non-null assertion), and transition() has no corresponding guard.

Code Reproduction URL

https://github.com/ionic-team/ionic-framework/blob/main/packages/angular/common/src/directives/navigation/stack-controller.ts#L240 (destroy) and https://github.com/ionic-team/ionic-framework/blob/main/packages/angular/common/src/directives/navigation/stack-controller.ts#L278 (unguarded dereference). A deterministic minimal repro is impractical for this race; production stack traces and the code path above demonstrate it. Happy to provide additional RUM data if useful.

Ionic Info

Ionic:

   Ionic CLI                     : 7.2.1
   Ionic Framework               : @ionic/angular 8.8.13
   @angular-devkit/build-angular : 21.2.19
   @angular-devkit/schematics    : 21.2.19
   @angular/cli                  : 21.2.19
   @ionic/angular-toolkit        : 12.3.0

Capacitor:

   Capacitor CLI      : 8.4.1
   @capacitor/android : 8.4.1
   @capacitor/core    : 8.4.1
   @capacitor/ios     : 8.4.1

System:

   NodeJS : v24.18.0
   npm    : 11.16.0
   OS     : Windows 10

Additional Information

We have been shipping the one-line guard (if ((containerEl as any)?.commit)) via patch-package in production; the crash class disappears in patched builds with no observed side effects. PR with the fix incoming.

Activity

  1. added a commit that references this issue on Jul 28, 2026
    fdef61c
  2. ionitron-bot commented on Aug 28, 2026

    @ionitron-bot

    Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.

  3. locked and limited conversation to collaborators on Aug 28, 2026
  4. added a commit that references this issue on Oct 3, 2026
    5a8bacd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: buga confirmed bug report

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions