Cross-platform navigation: why NavController defeated angular / router in Ionic 4





Hello! My name is Nikita Zhigamovsky, a programmer at KitApp, and I want to talk about my experience in building navigation in Ionic 4: the problem I encountered and its solution.



I have been developing cross-platform solutions for mobile applications since 2018. I used to work on Ionic 3rd version, but, as time goes on, the functionality is developing, I decided to switch to a newer version, and the annoying moments and bugs of the previous model in Ionic 4 seem to have already been fixed.



It would seem that what could go wrong. Finally, we have the normal Angular routing in the functional, and not the old NavController with all its shortcomings. Even on the official Ionic website, the routing guide indicates that programmatically navigating through pages is worth using the angular / router methods. But there was something that made me go back to the old NavController.



The essence of the problem



An interesting bug was noticed. Suppose you have a side menu, you made it using ion-split-pane. You also have separate pages from the menu, and you want to go from them to other pages that are on the menu. Navigate using Router.navigateByUrl ('/ menu / ...'). Next, we call menu page A, and a page separate from the menu - B. But there is one BUT!



Suppose, on page A, a certain logic is triggered in the ngOnInit event. You navigate to page B using Router and notice that the menu page is still active - it has not been deleted. Accordingly, if you go back to page A, the ngOnInit event will not work, since the ngOnDestroy event of this page did not work. It would seem that everything is logical. At such moments, they usually resort to one of the life cycle methods, not the angular, but the ionic - ionViewWillEnter. It fires when you go to a page as soon as it becomes active. Everything seems to be fine, it fits perfectly, but there is a certain number of conventions.



None of the options for an adequate action on page A will work when going to it, if this transition is not from pages that are in the menu. You will not be able to track the transition to this page, because, I repeat, it is still open and functions quietly under other pages, for example, under page B.



Some illustrative examples:



ionViewWillEnter will work if you have the following page structure:



1) Separate pages



- page1

- page2

- page3



In this example, when I go to each page, ionViewWillEnter will work perfectly. (page1 => page2, page2 => page3, etc.)



2) Menu / Tabs



- menu

- menuPage1

- menuPage2

- menuPage3

In this example, everything will also be fine: the ionViewWillEnter method will fire every time you go to any of the pages (menuPage1 => menuPage2, menuPage1 => menuPage3, etc.).



But in the example below, everything is more complicated:



- menu

- menuPage1

- menuPage2

- menuPage3

- loginPage

- signupPage



This is where the problems of standard Angular routing begin. When navigating inside menu pages (menuPage1 => menuPage2 => menuPage3) - the ionViewWillEnter method will work as usual, in the same way when navigating between individual pages (loginPage => signupPage). But as soon as we start moving between separate pages and menu pages (loginPage => menu / menuPage1 or menu / menuPage3 => signupPage), neither the ngOnInit method nor ionViewWillEnter works. ngOnInit will not work because the page has not been corrupted, which is logical. But why did not ionViewWillEnter work?



Based on the documentation, ionViewWillEnter works inside separate routing stacks (the keyword “individual”) or between individual pages, or inside menus / tabs. But not in the mixed structure of individual pages and menus / tabs. Strange, but this is considered normal behavior. At the same time, this is not exactly the behavior that users expect, especially considering the name of the lifecycle hooks :).



So how to solve this problem?



Having visited many forums, but not having seen a normal solution, and having seen some dubious life hacks that do not always work, it becomes clear that something else is needed. Something that will change the transition functionality between pages of any type.



What to do in this case? Of course, throw the Router to hell and forget about it, because there is still our previously hated and now so good NavController.



The main difference between the NavController.navigateRoot () method is that after switching to another page, the previous one is automatically destroyed! And when you switch to it again, both the ngOnInit method and ionViewWillEnter will work! In fact - this is the perfect solution - without crutches and dubious self-written functions.



The coolest thing is that it works with any page structure: even between normal, even inside the menu, even mixed type, as from the last example.



Summarize the positive aspects:



  1. NavController deletes the previous page from the stack, respectively, when you go back to it - it is updated, the ionViewWillEnter and ngOnInit methods work, and you can call the logic in them again and update the information on the pages, for example.
  2. Forget the old push (), setRoot (), and pop () methods, as well as navigating through class elements. After all, this was what created a lot of problems. Now navCtrl has updated methods, which are passed the same path as in the methods of Router.


There is one caveat, where do without “BUT” :)



If we add an event handler to the hardware “back” button on the android and in this handler we try to go somewhere using Router or navController, then we get the following error in the console: 'Navigation triggered outside Angular zone'.



Yes, the navigation will work, the page will open, but nothing will work on it - neither the initialization of the properties, nor the life cycle methods. Unfortunately, navigation by pressing the back button is triggered outside the Angular zone and, in fact, only opens the template: without binding variables to the template, without functions, hooks, life cycle methods - without anything.



The solution is very simple, actually. We simply explicitly force navigation inside the Angular zone.



Example:



import { Component, NgZone } from '@angular/core'; import { NavController } from '@ionic/angular'; @Component({ selector: 'app-root', templateUrl: 'app.component.html' }) export class AppComponent { constructor(private navCtrl: NavController, private ngZone: NgZone){} this.ngZone.run(() => this.navCtrl.navigateForward('menu')).then();  this.ngZone.run(() => this.router.navigateByUrl('/menu/my-orders')).then(); }
      
      





And now everything works fine!



There are many interesting articles about ngZone, I advise you to read. Good luck!



A little bit about navController's methods:







Suppose we are now on menu / page1,



image



and we have a separate menu stack and after going from menu / page1 to the login page, we need to delete the menu / page1 page so that after switching to it again, we will have some kind of logic working on ngOnInit or ionViewWillEnter. If we use router.navigateByUrl ('login) for the transition, then after it we will be on the login page, but we will also have a menu page,







accordingly, after switching from login to menu / page1, neither ngOnInit nor ionViewWillEnter will work.



If you use our navCtrl.navigateRoot ('login') to navigate, then after opening the login page, the previous page is deleted. And the ngOnInit and ionViewWillEnter methods will work.







This is the beauty of using navController - the expected behavior is fully consistent with the current .



All Articles