You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug: IonTabs.outlet is undefined when the first ionTabsWillChange fires (standalone Angular) #31674
In the standalone IonTabs, outlet is typed IonRouterOutlet, but it is a non-static @ViewChild('outlet') on an ion-router-outlet rendered under *ngIf="tabs.length === 0". When a tabs page loads, the initial tab's activation emits stackWillChange with tabSwitch: true (there is no leaving view), so ionTabsWillChange fires during IonTabs' first change detection, before that query resolves. A handler that reads outlet throws TypeError: Cannot read properties of undefined (reading 'nativeEl') on every load of the tabs page.
Expected behavior
outlet is set whenever ionTabsWillChange fires, as its non-optional type says.
Hi team, I traced #31674 in packages/angular/src/standalone/navigation/tabs.ts and packages/angular/src/common/directives/navigation/tabs.ts.
Root Cause
In packages/angular/src/standalone/navigation/tabs.ts:60, @ViewChild('outlet', { read: IonRouterOutlet, static: false }) outlet: IonRouterOutlet; is resolved after child view initialization because <ion-router-outlet #outlet> is inside *ngIf="tabs.length === 0". On initial route activation, <ion-router-outlet> emits (stackWillChange) before Angular assigns the @ViewChild property, so tabs.outlet is undefined inside the first ionTabsWillChange callback.
Proposed Fix
Pass the template reference #outlet directly to (stackWillChange)="onStackWillChange($event, outlet)" in tabs.ts and assign if (outlet && !this.outlet) { this.outlet = outlet; } in IonTabs.onStackWillChange prior to emitting this.ionTabsWillChange.
I have this fix ready on my fork branch and would be happy to open a PR!
Verified on 9.0.10. This happens when ion-tabs renders after the router has already activated the tab route, for example when ion-tabs sits behind an @if, or when the tabs page is rendered by Angular's <router-outlet> instead of ion-router-outlet. In that case the inner outlet activates the first tab while IonTabs is still in its first change detection, so ionTabsWillChange fires before IonTabs has its outlet reference. Anything that reads outlet in that first handler throws, and so does getSelected(), which uses it internally.
Workarounds, both verified on 9.0.10:
If you don't need the "before" timing, do the work in ionTabsDidChange, where outlet is set from the first event onwards.
If it has to run on ionTabsWillChange, defer the read by a microtask:
If you'd like to open a PR for it, we'd be happy to review it. One approach that worked in our testing is to pass the #outlet template ref into onStackWillChange from the ion-router-outlet binding in the IonTabs template, and set outlet from it before emitting. The standalone and lazy IonTabs share the same template, so both need the change.
Activity
duongynhi000005-oss commentedon Oct 30, 2026
Hi team, I traced #31674 in
packages/angular/src/standalone/navigation/tabs.tsandpackages/angular/src/common/directives/navigation/tabs.ts.Root Cause
In
packages/angular/src/standalone/navigation/tabs.ts:60,@ViewChild('outlet', { read: IonRouterOutlet, static: false }) outlet: IonRouterOutlet;is resolved after child view initialization because<ion-router-outlet #outlet>is inside*ngIf="tabs.length === 0". On initial route activation,<ion-router-outlet>emits(stackWillChange)before Angular assigns the@ViewChildproperty, sotabs.outletisundefinedinside the firstionTabsWillChangecallback.Proposed Fix
Pass the template reference
#outletdirectly to(stackWillChange)="onStackWillChange($event, outlet)"intabs.tsand assignif (outlet && !this.outlet) { this.outlet = outlet; }inIonTabs.onStackWillChangeprior to emittingthis.ionTabsWillChange.I have this fix ready on my fork branch and would be happy to open a PR!
thetaPC commentedon Oct 30, 2026
Thanks for the report!
Verified on 9.0.10. This happens when
ion-tabsrenders after the router has already activated the tab route, for example whenion-tabssits behind an@if, or when the tabs page is rendered by Angular's<router-outlet>instead ofion-router-outlet. In that case the inner outlet activates the first tab whileIonTabsis still in its first change detection, soionTabsWillChangefires beforeIonTabshas itsoutletreference. Anything that readsoutletin that first handler throws, and so doesgetSelected(), which uses it internally.Workarounds, both verified on 9.0.10:
ionTabsDidChange, whereoutletis set from the first event onwards.ionTabsWillChange, defer the read by a microtask:If you'd like to open a PR for it, we'd be happy to review it. One approach that worked in our testing is to pass the
#outlettemplate ref intoonStackWillChangefrom theion-router-outletbinding in theIonTabstemplate, and setoutletfrom it before emitting. The standalone and lazyIonTabsshare the same template, so both need the change.Tracked as a bug in
@ionic/angular.