Popping to Specific Ionic Page by Name

July 13th 2018 Ionic 2/3

Surprisingly, there's no easy to use functionality built into Ionic to find a specific page in the navigation stack by its name and pop as many pages as necessary to reach it. Such functionality can be very helpful when we cannot fully predict the order of pages on the navigation stack because the user is allowed some freedom in navigation.

Fortunately it's not too difficult to implement this on our own, thanks to the undocumented popTo method in NavController. Based on its type definition, one would assume that it does exactly what I described - pops pages until it reaches one matching the name passed as the method's first first parameter:

NavController.popTo(
    page: string | Page | ViewController,
    opts?: NavOptions,
    done?: TransitionDoneFn): Promise<any>

However, the method doesn't work as expected when invoked with a string argument containing the page name. The following call will always pop a single page from the stack, no matter where in the navigation stack is an instance of the page named FirstPage:

this.navCtrl.popTo('FirstPage');

If we call the method with an instance of ViewController instead, it will work correctly - it will pop all pages up to the page corresponding to the given ViewController instance. Hence, we only need to find the ViewController instance in the navigation stack which belongs to a page with a specific name (this.rootNav contains a reference to the application's root NavController as described in a previous blog post):

popTo(pageName: string) {
  let page = this.rootNav.getViews().find(view => view.id == pageName);
  if (page) {
    this.rootNav.popTo(page);
  }
}

In the above code we are checking the undocumented id property. The ViewController also has a name property which seems to contain the same value. But unlike id, the value contained in name will be minified in the production version of our application built with the --prod switch, e.g.:

ionic cordova build android --prod

The ViewControllers returned by the getViews() method will be ordered from the root page to the current page, i.e. the current page will be the last one in the array. Because of that, the method above will pop to the matching page instance closest to the root when there are multiple pages with the same name on the navigation stack.

If we want to pop to the one closest to the current page, we need to search the views in reverse order:

popToLast(pageName: string) {
  let page = this.rootNav.getViews()
    .map(view => view).reverse()
    .find(view => view.id == pageName);
  if (page) {
    this.rootNav.popTo(page);
  }
}

I'm relying on Array's reverse() method to reverse the order of views. Notice how I'm calling map() beforehand because reverse() changes the order of items in the array in-place, i.e. it modifies the original array. Since getViews() returns the internal array of ViewControllers, we don't want to change the order of items in it as this would also change the order of pages in the navigation stack.

With current implementation, the method won't do anything if there's no instance of the page with the given name in the navigation stack. We could easily push a new instance of the page onto the stack in such a case:

popToWithFallback(pageName: string) {
  let page = this.rootNav.getViews()
    .map(view => view).reverse()
    .find(view => view.id == pageName);
  if (page) {
    this.rootNav.popTo(page);
  } else {
    this.rootNav.push(pageName);
  }
}

Of course, if the page required any parameters when instantiated, we could pass those through our method as well.

Copyright
Creative Commons License