Pop Multiple Pages in Ionic 4

December 20th 2019 Ionic 4/5

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.

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License