How can I detect navigation returning to a parent page?

Greetings,

I’m building an Ionic 4 Angular app that has tabs as the initial view, where the main tab provides the means to navigate to a component inside a lazy-loaded module. I would like to run some custom logic inside this initial main tab component whenever it becomes the currently active page, which I’ve got working for all scenarios except when the app navigates back from the “Detail” component.

For reference, the routes defined in my main app-routing.module.ts are as follows:

[
  { path: '', redirectTo: '/tabs/home', pathMatch: 'full' },
  {
    path: 'tabs',
    component: TabsPage,
    children: [
      { path: 'home', component: HomePage }
    ]
  },
  { path: 'detail', loadChildren: './detail/detail.module#DetailPageModule' }
]

…and I’m currently navigating from HomePage to DetailPage (obviously, within DetailPageModule) using a button that calls the following function:

viewDetail(data: CustomData) {
  this.navCtrl.navigateForward('detail', {
    state: data
  });
}

The data: CustomData part doesn’t affect my current issue directly, but I’m leaving it in the snippet to make it clear that I need to pass data along when navigating to DetailPage

As a quick test, I implemented all of the Ionic 4 and Angular 8 lifecycle hooks in both the HomePage and DetailPage components with a simple console.log() call so I could inspect the behaviour while I navigated around my app, but I cannot find anything that triggers inside my HomePage component when I navigate back from the DetailPage component…

I’ve searched for posts from other people with the same issue, found a few that are really close, but I couldn’t glean any knowledge from those posts/issues/articles that helped me in any way.

I’ve also tried to make use of a static method in HomePage that I can call from a lifecycle hook in DetailPage, but this doesn’t give me access to any of the methods I’ve defined in HomePage, so that doesn’t work for me.

Is there anything I can do with the current app structure to achieve this? Do I perhaps need to structure my app components differently to create a scenario in which I can detect this navigation? If so, what would you suggest?

I would like to first take a run at convincing you that this is a bad idea.

Whenever I’ve felt the need to do something like this, when I thought deeply about it, what I really wanted to do was to ensure that whenever such a page became active, it was showing fresh and not stale information. While it initially seemed simplest to then do whatever refreshing needed to be done in a lifecycle hook called when “this page is going to become current”, I eventually decided that a truer trigger was “whenever the information has become stale”. So as far as the user is concerned, new information gets displayed each time the later of two things happened:

A. new data came in somehow
B. this page is or became visible

By updating on every A, I solve one previously undiscussed problem for free and create a new one. Solved: what happens if new data comes in while the page is sitting there being active. New: fretting about the inefficiency of doing multiple A updates between Bs. I decided that this wasn’t something I wanted to worry about, especially because the framework and/or browser can optimize it away by noticing that the page is hidden in the DOM.

So, yes, if I were in your shoes I would redesign things so that I don’t use lifecycle events for data refreshing proxy timing.

But, if you’re not convinced, here’s a way I think you could solve the proximate issue you have:

type VJEntry = [any, BehaviorSubject<"clean" | "dirty">];
class ViewJanitor {
  private entries: VJEntry[] = [];
  watch(comp: any): Observable<"clean" | "dirty"> {
    let entry: VJEntry = this.entries.find(e => e[0] === comp);
    if (!entry) {
      entry = [comp, new BehaviorSubject<"clean" | "dirty">("clean")];
      this.entries.push(entry);
    }
    return entry[1];
  }
  mark(comp: any, state: "clean" | "dirty"): void {
    let entry: VJEntry = this.entries.find(e => e[0] === comp);
    if (!entry) {
      entry = [comp, new BehaviorSubject<"clean" | "dirty">(state)];
      this.entries.push(entry);
    } else {
      entry[1].next(state);
    }
  }
}

This effectively allows DetailPage to mark HomePage as dirty before navigating to it, in roughly the same way as you were trying to do with the static function. (sidebar: you could pass an arrow function where you are doing the static function in order to allow access to the right this, but IMHO that’s even uglier than the ViewJanitor).

Implementation detail note: yes, this could also be done with an associative array that eliminates the need to loop through the whole list on lookup, but I think if there are enough views in this hierarchy that would make that a win, then the app is simply too big for its own good anyway.

Thanks for the detailed reply, there’s some good food for thought there… For what it’s worth, in this case the information that becomes “stale” after a while is a list of proprietary BLE devices. So the particular action that I’m hoping to run when returning to HomePage is to run the defined devices scan again.

Unfortunately the firmware on these devices isn’t set up in a way that allows me to pair them with a phone/tablet, so I need to explicitly scan for and list them each time. With that said, I did have an idea just now to move the device scan logic to a new page, which would give me an opportunity to mimic the workflow of pairing with a Bluetooth device… For example:

  • HomePage, which would list the “saved” (aka: mock paired) Bluetooth devices
  • ScanPage, which would start scanning automatically on load, with the ability to save details a device
  • DetailPage, which would remain as my current interaction page while connected to a device

The auto scanning part was a request by my boss, so to appease him I’ll probably have to implement this one either way, but perhaps I’ll just set up a demo for both ideas and see which he decides is “right”.

Also, I’m only just starting to wrap my mind around Observables at the moment, so I really do appreciate the code snippet. I’m not at a point (yet) where I would’ve come up with a solution quite like that, but at least I am able to follow what that class does. (:

I’ll have a crack at this when I get back to the office in the morning (GMT+2 over here), so I’ll post another update back here after that.

I fear the bolded part just means the can has been kicked down the road a bit, because isn’t the current problem that we can’t reliably do this on HomePage? I can’t imagine it would be any easier for ScanPage.

I would push back a bit on the timing of this. If I was your boss, and was presented with the question “what is there about changing pages in the app that makes the list of “paired” devices go stale?”, I couldn’t honestly give a coherent answer.

Maybe that conversation would lead to a more reasonable choice for when to rescan, such as giving HomePage a manual rescan button, rescanning at a periodic interval, or combining those two concepts by having the rescan button become more prominently styled if it has been more than N minutes since the last scan? Any of those would seem to me to make more sense for the user, as well as getting you off of lifecycle event dependency.

Not exactly, no. Because this would in itself be a “child” of the landing page, so I can make the BLE plugin start looking for active devices on ionViewDidEnter and stop it on ionViewWillLeave. The issue that brought me here in the first place was that these lifecycle hooks fired for HomePage when the app launched or when navigating between my 3 tabs, but not if I navigated to a child page from there or back home from a child page.

For all intensive purposes, the idea of using a new child view to handle scanning for our BLE devices is effectively a “manual rescan button” with it’s own view component, so in my mind that seems to be the best of both worlds here.

1 Like

Alrighty, so just to add some final notes to this one: the decision was made just now to move our app’s BLE scanning logic into a child page and store the list of discovered devices in a global service… Nice and simple. :slight_smile:

Just wanted to drop one final note here for anyone else who might find this in a Google search…

My final solution for this “problem” (i.e.: implementing what my boss and his bosses requested) made use of the activate event emitter on Angular’s Router Outlet directive.

So my app.component.html now contains:

<ion-router-outlet (activate)="onActivateRoute($event)"></ion-router-outlet>

Along with the following method in my app.component.ts file:

onActivateRoute(event) {
  // do magic stuff when navigating to specific routes
}

I’m still learning my way around application state management, but the basically this little gem allows me to update whatever I need in the app’s global “state”, which in turn affect other areas of the app UI.