diff --git a/packages/angular/common/src/directives/navigation/router-outlet.ts b/packages/angular/common/src/directives/navigation/router-outlet.ts index d0d6bb600c..556e49d606 100644 --- a/packages/angular/common/src/directives/navigation/router-outlet.ts +++ b/packages/angular/common/src/directives/navigation/router-outlet.ts @@ -116,6 +116,7 @@ export class IonRouterOutlet implements OnDestroy, OnInit { router: Router, zone: NgZone, activatedRoute: ActivatedRoute, + protected outletContent: ViewContainerRef, @SkipSelf() @Optional() readonly parentOutlet?: IonRouterOutlet ) { this.nativeEl = elementRef.nativeElement; @@ -276,8 +277,16 @@ export class IonRouterOutlet implements OnDestroy, OnInit { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const component = snapshot.routeConfig!.component ?? snapshot.component; - cmpRef = this.activated = this.location.createComponent(component, { - index: this.location.length, + /** + * View components need to be added as a child of ion-router-outlet + * for page transitions and swipe to go back. + * However, createComponent mounts components as siblings of the + * ViewContainerRef. As a result, outletContent must reference + * an ng-container inside of ion-router-outlet and not + * ion-router-outlet itself. + */ + cmpRef = this.activated = this.outletContent.createComponent(component, { + index: this.outletContent.length, injector, environmentInjector: environmentInjector ?? this.environmentInjector, }); diff --git a/packages/angular/common/src/directives/navigation/stack-controller.ts b/packages/angular/common/src/directives/navigation/stack-controller.ts index 8241afa864..0593219d3f 100644 --- a/packages/angular/common/src/directives/navigation/stack-controller.ts +++ b/packages/angular/common/src/directives/navigation/stack-controller.ts @@ -274,9 +274,6 @@ export class StackController { if (enteringEl && enteringEl !== leavingEl) { enteringEl.classList.add('ion-page'); enteringEl.classList.add('ion-page-invisible'); - if (enteringEl.parentElement !== containerEl) { - containerEl.appendChild(enteringEl); - } if ((containerEl as any).commit) { return containerEl.commit(enteringEl, leavingEl, { diff --git a/packages/angular/src/directives/navigation/ion-router-outlet.ts b/packages/angular/src/directives/navigation/ion-router-outlet.ts index c84f5ce23b..52cc2ebe62 100644 --- a/packages/angular/src/directives/navigation/ion-router-outlet.ts +++ b/packages/angular/src/directives/navigation/ion-router-outlet.ts @@ -1,13 +1,32 @@ import { Location } from '@angular/common'; -import { Directive, Attribute, Optional, SkipSelf, ElementRef, NgZone } from '@angular/core'; +import { + ViewChild, + ViewContainerRef, + Component, + Attribute, + Optional, + SkipSelf, + ElementRef, + NgZone, +} from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { IonRouterOutlet as IonRouterOutletBase } from '@ionic/angular/common'; -@Directive({ +@Component({ selector: 'ion-router-outlet', + template: '', }) // eslint-disable-next-line @angular-eslint/directive-class-suffix export class IonRouterOutlet extends IonRouterOutletBase { + /** + * `static: true` must be set so the query results are resolved + * before change detection runs. Otherwise, the view container + * ref will be ion-router-outlet instead of ng-container, and + * the first view will be added as a sibling of ion-router-outlet + * instead of a child. + */ + @ViewChild('outletContent', { read: ViewContainerRef, static: true }) outletContent: ViewContainerRef; + /** * We need to pass in the correct instance of IonRouterOutlet * otherwise parentOutlet will be null in a nested outlet context. @@ -22,8 +41,9 @@ export class IonRouterOutlet extends IonRouterOutletBase { router: Router, zone: NgZone, activatedRoute: ActivatedRoute, + outletContent: ViewContainerRef, @SkipSelf() @Optional() readonly parentOutlet?: IonRouterOutlet ) { - super(name, tabs, commonLocation, elementRef, router, zone, activatedRoute, parentOutlet); + super(name, tabs, commonLocation, elementRef, router, zone, activatedRoute, outletContent, parentOutlet); } } diff --git a/packages/angular/standalone/src/navigation/router-outlet.ts b/packages/angular/standalone/src/navigation/router-outlet.ts index d5e6becab2..5919462b82 100644 --- a/packages/angular/standalone/src/navigation/router-outlet.ts +++ b/packages/angular/standalone/src/navigation/router-outlet.ts @@ -1,5 +1,14 @@ import { Location } from '@angular/common'; -import { Directive, Attribute, Optional, SkipSelf, ElementRef, NgZone } from '@angular/core'; +import { + ViewChild, + ViewContainerRef, + Component, + Attribute, + Optional, + SkipSelf, + ElementRef, + NgZone, +} from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { IonRouterOutlet as IonRouterOutletBase, ProxyCmp } from '@ionic/angular/common'; import { defineCustomElement } from '@ionic/core/components/ion-router-outlet.js'; @@ -7,12 +16,22 @@ import { defineCustomElement } from '@ionic/core/components/ion-router-outlet.js @ProxyCmp({ defineCustomElementFn: defineCustomElement, }) -@Directive({ +@Component({ selector: 'ion-router-outlet', standalone: true, + template: '', }) // eslint-disable-next-line @angular-eslint/directive-class-suffix export class IonRouterOutlet extends IonRouterOutletBase { + /** + * `static: true` must be set so the query results are resolved + * before change detection runs. Otherwise, the view container + * ref will be ion-router-outlet instead of ng-container, and + * the first view will be added as a sibling of ion-router-outlet + * instead of a child. + */ + @ViewChild('outletContent', { read: ViewContainerRef, static: true }) outletContent: ViewContainerRef; + /** * We need to pass in the correct instance of IonRouterOutlet * otherwise parentOutlet will be null in a nested outlet context. @@ -27,8 +46,9 @@ export class IonRouterOutlet extends IonRouterOutletBase { router: Router, zone: NgZone, activatedRoute: ActivatedRoute, + outletContent: ViewContainerRef, @SkipSelf() @Optional() readonly parentOutlet?: IonRouterOutlet ) { - super(name, tabs, commonLocation, elementRef, router, zone, activatedRoute, parentOutlet); + super(name, tabs, commonLocation, elementRef, router, zone, activatedRoute, outletContent, parentOutlet); } }