Repeating Same Ionic 4 Page in History

November 8th 2019 Ionic 4 Angular

In Ionic 3, there was no need to pay any attention which pages you navigate to and how. They were always correctly placed in the history and the back navigation worked as expected. This made it easy to create pages for navigating hierarchical structure, e.g. a catalog.

The core code of such a page could be really simple:

@IonicPage()
@Component({
  selector: "page-catalog",
  templateUrl: "catalog.html"
})
export class CatalogPage {
  id: number;
  items: number[];

  constructor(public navCtrl: NavController, public navParams: NavParams) {
    // pass current node as a parameter
    this.id = navParams.get("id");
    // simulate loading catalog data for current node
    this.items = Array.apply(null, Array(this.random(10))).map(() =>
      this.random(100)
    );
  }

  private random(max: number): number {
    return Math.floor(Math.random() * max + 1);
  }

  openCatalog(id: number) {
    // navigate to the same page for the selected node
    this.navCtrl.push("CatalogPage", { id });
  }
}

Navigation in Ionic 4 is based on Angular router. Therefore, it behaves a lot different even if you still use Ionic NavController to take care of page transitions. Most importantly, the same route can't repeat in the history stack.

To successfully implement navigating a hierarchical structure using a single page, the current node must be included in the route. This will ensure that the routes will be unique as long as the catalog is really a tree and doesn't include cycles.

The page code doesn't need to be much different:

@Component({
  selector: "app-catalog",
  templateUrl: "./catalog.page.html",
  styleUrls: ["./catalog.page.scss"]
})
export class CatalogPage implements OnInit, OnDestroy {
  id: number;
  items: number[];
  private sub: Subscription;

  constructor(private route: ActivatedRoute, private navCtrl: NavController) {}

  ngOnInit() {
    this.sub = this.route.params.subscribe(params => {
      // pass current node as a route parameter
      this.id = params.id;
      // simulate loading catalog data for current node
      this.items = Array.apply(null, Array(this.random(10))).map(() =>
        this.random(100)
      );
    });
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }

  private random(max: number): number {
    return Math.floor(Math.random() * max + 1);
  }

  openCatalog(id: number) {
    // navigate to the same page for the selected node
    this.navCtrl.navigateForward(`/catalog/${id}`);
  }
}

For this to work, don't forget to include the route parameter in the route configuration:

{
  path: 'catalog/:id',
  loadChildren: () =>
    import('./catalog/catalog.module').then(m => m.CatalogPageModule)
}

If instead of with route parameters you try to pass the node via query parameters or the state as I suggested in a previous post then the existing instance of the page will be reused. In case of query parameters, a new value will be emitted by the route.queryParams observable so that you can react to it. When using the state, not even that is possible. But even with query parameters, there won't be any transition animations. Also, since no new entry will be added to the history stack, it won't be possible to navigate back to the previous node of the catalog tree.

Copyright
Creative Commons License