Pop Multiple Pages in Ionic 4
In Ionic 3, navigation was stack based. NavController
was used for controlling the navigation stack (i.e. for pushing pages onto the stack and popping them of the stack).
In Ionic 4, it's recommended to use Angular routing for navigation. While this might be fine for new applications, completely changing the approach to navigation adds another layer of complexity when upgrading existing Ionic 3 applications to Ionic 4.
Fortunately, there's still a NavController
in Ionic 4. It's built on top of Angular routing and makes navigation more similar to how it worked in Ionic 3. Although it isn't even listed in the documentation, the public methods are well documented in type definitions. Basic pushing and popping of pages is similar enough to how it was done in Ionic 3:
// Ionic 3
constructor(private navCtrl: NavController) {}
pushPage() {
this.navCtrl.push("FirstPage");
}
popPage() {
this.navCtrl.pop();
}
// Ionic 4
constructor(private navCtrl: NavController) {}
pushPage() {
this.navCtrl.navigateForward('/first');
}
popPage() {
this.navCtrl.pop();
}
However, not all methods from NavController
in Ionic 3 are still available in Ionic 4. For example, there's no equivalent to the popToRoot
method which popped all pages except the root one as a single action.
Looking at the code, the pop
method delegates the call to IonRouterOutlet
. Its pop
method supports popping of multiple pages with a single call.
There's still the matter of determining how many pages to pop. This information is not exposed by IonRouterOutlet
. It's kept in the underlying StackController
. Unlike IonRouterOutlet
, StackController
is private and completely undocumented. Still, I think that without it, it's currently not possible to implement popToRoot
functionality in Ionic 4.
Let's convert the finding above into code:
@Injectable({
providedIn: 'root'
})
export class NavHelperService {
routerOutlet: IonRouterOutlet;
constructor() {}
async popToRoot() {
if (!this.routerOutlet) {
throw new Error('IonRouterOutlet not initialized.');
}
const stackCtrl = (this.routerOutlet as any).stackCtrl;
const stackId = this.routerOutlet.getActiveStackId();
const depth = stackCtrl.getStack(stackId).length;
if (depth > 1) {
this.routerOutlet.pop(depth - 1, stackId);
}
}
}
As you can see, I'm accessing the private stackCtrl
field in IonRouterOutlet
and calling the getStack
method of that StackController
. This poses a risk that the code above will break in a future update to Ionic 4. If that happens, I'll have to come up with a different solution to my problem.
There's still one final detail to explain. The routerOutlet
field in the service above must be initialized before the method can be called in a page. It's best to do it in AppComponent
because ionRouterOutlet
is declared in its template:
@ViewChild(IonRouterOutlet, { static: true })
routerOutlet: IonRouterOutlet;
ngOnInit(): void {
this.navHelper.routerOutlet = this.routerOutlet;
}
It's also stored in NavController
's private field but I didn't want to rely again on internal implementation.