[BUG] NavController.getActive() doesn't work as expected

I want to simply print the name of the current page whenever the current page changes. I could print out the name manually using ionViewDidEnter() on each page but it would be much better to simply hook onto an event that applies to all pages.

So, in the home page of my app, I have a subscription to the navCtrl.viewDidEnter event to print out navCtrl.getActive().name but the results are different to what ionViewDidEnter prints out (source code at the bottom).

Here is an example log that I get with comments on what I’m doing:

// app loaded defaulting to homepage
ionViewDidEnter HomePage
active nav: HomePage
//navCtrl.push('Page2Page')
ionViewDidEnter Page2Page
active nav: Page2Page
//up to now this is all expected behaviour
//navCtrl.pop()
ionViewDidEnter HomePage
active nav: Page2Page
//this should not be the case!
//why is the active nav Page2Page when the current page is HomePage???

Why does this happen? Have I misunderstood what navCtrl.getActive() actually does?

This is the gist of my code.

HomePage

export class HomePage {

  constructor(public navCtrl: NavController) {
      this.navCtrl.viewDidEnter.subscribe(()=> {
          let n = this.navCtrl.getActive().name;
          console.log('active nav: ' + n);
      });
  }
  ionViewDidEnter() {
    console.log('ionViewDidEnter HomePage');
  }
}

Page2Page

export class Page2Page {

  constructor(public navCtrl: NavController, public navParams: NavParams) {
  }

  ionViewDidEnter() {
    console.log('ionViewDidEnter Page2Page');
  }
}

Ionic version: 3.12.0
Cordova version: 7.0.1

What is the use case here?

I ask because you’re proposing a couple rather unusual things, namely printing the name of the active page (normally indicative of code that is way too interested in what other code is doing) and having 100+ pages, which just seems a little unnecessary.

1 Like

I don’t have 100 pages. It was just the point of saying that if I could add a hook to NavController.viewDidEnter and simply print NavController.getActive().name it would be much better than adding code to each individual page.

The use case is tracking the progress as a user navigates through a series of pages. By being able to listen to the changes to what is the current page, it is possible to follow along with how the user interacts with the app and also restore the last page that the user was on if necessary.

That’s what pop() is for. I think your attempt to micromanage navigation is going to result in nothing but pain. Specifically, any code that relies on class names is going to break in production mode.

1 Like

A user is navigating through an app which has 10 pages. The user force quits the app for whatever reason, or the phone powers off for whatever reason. On re-opening the app, the user should be able to continue from where they last were. Please elaborate on how pop() is going to help with identifying the last page the user was on.

Then I would recommend having page lifecycle events such as ionViewDidLoad() send heartbeat events to a service provider that logs some sort of string that you define into ionic-storage representing where you want to resume. In the app component you can load this “lastPage” string and renavigate to it. If you are using lazy loading, this will probably be easier, because all of your interaction with the navigation system will be using strings.

That is exactly what I’m doing. Except rather than attaching an individual heartbeat event to each page’s ionViewDidEnter() I want to add a single listener to navCtrl.viewDidEnter which will apply on every single page. (Note: ViewDidLoad like you suggested is only run once on creation, so viewDidEnter is needed) But the issue is navCtrl.getActive() does not appear to do what I expect it to do and is printing the wrong page name which is why I am posting this thread in the first place.

Well, I can’t think of any way around the fact that you can’t rely on page names unless you disable minification in the build process.

This issue is not about page names but the return value of NavController.getActive(). If my understanding of NavController.getActive() is correct and it is indeed meant to return the name of current active page then the issue described above is a bug with Ionic and should be fixed.

I don’t think it is. It returns a ViewController, and the problem arises when you reference its name property. Do you only run into problems in production mode? If so, I still think this is minification-related.

It’s interesting, the way that ViewControllers are quietly available in NavController. I use ViewController (imported) quite a bit, but stacking and popping ViewControllers as a base form of navigating or flow, presenting UIView’s etc. is stuff I do with Visual Studio/Xamarin/Native iOS stuff.

Is the availability of VC’s in NavConrol a generally overlooked asset in Ionic, or simply a facet that’s not really necessary to use based on Ionic’s nature?

I just started noticing the amount of NavControl options that are available over the last few days, but are rarely explored, at least on the forums.

Digging deeper: Exercise in futility for Ionic purposes?

For the purpose of the root of this chat, yes, navCtrl.getActive() is getting away from standard Ionic navigation fare and dabbling with ViewControllers which is a different creature to a degree.

Or is it the same concept wrapped in different packaging?

Okay. Let’s completely ignore the name property. What is the relationship between a ViewController and the currently active page i.e. the page that the user is actually looking at? Why would NavController.getActive() return the ViewController of a page that is neither visible, nor active, but instead the page that was just popped off the stack?

I know I’m butting in, but, I don’t think the relationship between NavControl, ViewControllers is as straightforward as it seems. ViewControllers (at least in other languages) can and often are responsible for multiple Views, potentially many pages, and even other ViewControllers.

So, if a ViewController presents a ViewController, the VC that did the presenting may be the active controller, or parent controller. I think that’s at least somewhat accurate / representative, yes?

Whereby you start running into parent / child controller relationships. Whereby things start getting confusing

Use the viewController emitted by the viewDidEnter Observable instead of using this.navCtrl.getActive() to get the correct active ViewController, like so:

this.navCtrl.viewDidEnter.subscribe(item=> {
          const viewController = item as ViewController;
          const n = viewController.name;
          console.log('active nav: ' + n);
      });

i.e. this.navCtrl.getActive() does seem to be buggy, because it returns the wrong ViewController if .setRoot was just used or if .pop was just used, whereas this.navCtrl.getActive() seems to return the correct ViewController if .push was used.

I have tested this inside the viewDidEnter subscription, don’t know about other lifecycle events …

5 Likes

This works well! Thanks a lot for your help!

I do have an issue that give me the impression that getActive() does not return the right value in combination of platform.registerBackButtonAction() .

I have a page (T) containing ion-tabs. One tab has a page (A) that at some point gets a push of another page ( P ) on top. The expected stack would be something like (T)->(A)->( P ) .

When I have ( P ) showing I press the Android back button and in the platform.registerBackButtonAction callback I am calling getActive() expecting to find a View corresponding to ( P ) since is the view in the top of everything and did not get dismissed. Instead I am getting (T). Not even (A) which is suppose to be the view in between (T) and ( P ).

So I cannot place any logic to recognize the views being pushed on top of tab pages inside the registerBackButtonAction() callback because I just get the tabs page itself and non of the other pages being piled on top.

My dirty temporary solution:

let activeChildNav = this.nav.getActiveChildNav();

if(activeChildNav._tabs){
          let activeTab = null;
          activeChildNav._tabs.forEach(_tab=>{
            if(_tab.isSelected){
              if(_tab._views.length > 0){
                _tab._views.forEach(_view=>{
                  if(_view._isHidden === false){
                    //found our top view  -->( P )
                    if(_view.name == "ProfilePage"){
                      _view.instance.leave(); //this call this.view.pop() inside the ProfilePage instance
                      return;
                    }
                  }
                });
              }
            }
          });
        }

In production mode (–prod) you have to use viewController.id instead of viewController.name.