WebCake

The v1 docs on the Ionic site specifically tell us that when we navigate between views, our view isn’t destroyed or removed from the DOM. Instead, it’s cached. If we have page load events that we need to trigger when a user navigates to a page, we’ll need to either handle it with an event handler – the built-in $ionicView.enter event – or by explicitly turning off caching. This can be a setting declared on individual views in the UI Router config, or globally. It’s widely considered a better practice, though, to handle the view-enter event instead of disregarding the benefits of caching.

What all this meant was that functions in our controllers and directives that were fired when their corresponding template loaded in a browser weren’t necessarily being called every time we navigated to our page in an Ionic app. The page was never removed from the DOM, so technically neither was the controller that wrapped our functions that fired on load.

As it were, the same is true for Ionic 2. Because we’re not using UI Router, and because we’re not handling things in terms of scopes, we have to do things a bit differently. This post aims to explain how.

Constructors and OnInits

Normally in Angular 2, when we want a function to fire, we either put it in a Class constructor, or – as is generally recommended – use the ngOnInit method supplied by the Angular core module. Our component might look like this:

import { Component, OnInit } from '@angular/core';

import { dataSrvcย } from './data.service';

@Component({
    selector: 'list-comments',
    template:`
        <ul>
            <li *ngFor="#comment of comments">{{comment}}</li>
        </ul>`
})
export class ListComments {
    constructor(private service: dataSrvc) {}

    public comments: string[];

    ngOnInit() {
        return this.service.getComments().then(data => this.comments = data);
    }
}

Here’s what’s happening:

  1. We import the Component and OnInit modules from the Angular core, as well as a DB service that connects to our source of truth.
  2. We define a Component decorator for our ListComments class – selector, template, and providers.
  3. In building the ListComments class, we first give it a constructor that sets the dataSrvc as a class type for a private service property.
  4. We build a comments property that our template will loop through to display each comment.
  5. We define the ngOnInit hook for the Component’s lifecycle, and run the async service.getComments() method to set our comments list.

But there’s a problem. If we’re running in Ionic, the OnInit will only fire when the view is loaded. That means, as mentioned above, it will only fire once. Ionic 2’s navigation module caches views in the DOM the same way Ionic 1 does, so the view is generally only loaded once. A user can continue to travel throughout the app and when they return to this page, the call to retrieve new comments that have been added in their absense won’t be fired.

View Lifecycle Hooks

Fortunately, Ionic packages a set of view lifecycle hooks into the NavController – part of the Ionic module. They follow four patterns of event handlers:

  • ionViewDidLoad works the same way as ngOnInit, fires once when the view is initially loaded into the DOM
  • ionViewWillEnter and ionViewDidEnter are hooks that are available before and after the page in question becomes active
  • ionViewWillLeave and ionViewDidLeave are hooks that are available before and after the page leaves the viewport
  • ionViewWillUnload is available before the page is removed from the DOM

In this case, the one we’re looking for is ionViewWillEnter, which fires every time a page becomes the active view. Studly.

Let’s rewrite the above by pulling it into Ionic, using Ionic modules instead of native Angular, with the ionViewWillEnter hook replacing the ngOnInit:

import { Component } from '@angular/core';

import { NavController } from 'ionic-angular';

import { dataSrvc } from './data.service';

@Component({
  selector: 'list-comments',
  template:`
    <ul>
      <li *ngFor="#comment of comments">{{comment}}</li>
    </ul>`
})
export class ListComments {
    constructor(private service: dataSrvc) {}

    public comments: string[];

    ionViewWillEnter() { // THERE IT IS!!!
        return this.service.getComments().then(data => this.comments = data);
    }
}

It’s not a huge change, but it’s an important one. Without the ionViewWillEnter hook, our call to the service.getComments() method would only fire the first time the DOM loads the page in question, ultimately leading to frustration and less friends.

27 responses to “Page Lifecycle Hooks in Ionic 2”

  1. ghprod says:

    Honestly i didnt knew about onPageWillEnter, etc. So this is really usefull for me ๐Ÿ˜€

    Thanks

    • ghprod says:

      Hi,

      i just noticed it today that onPageLoaded, onPageWillEnter, onPageDidEnter always called twice. My code is simple just nav.setRoot(NewPage), and on that NewPage has that method.

      any idea?

      Thanks

  2. Richard says:

    Hi,
    What is the difference between onPageWillLeave and onPageWillUnload?
    Does it mean that the view will be remove from viewport first and then from DOM finally?

    Thanks

    • Colin says:

      You’re on the right track. onPageWillLeave will fire every time you leave the page, regardless of whether or not the page is still cached in the DOM. So, for example, if your nav stack consists of two pages – meaning you’re on the second page – and you push() another page on using the NavController, when you leave the second page, onPageWillLeave will fire. But it will still be in the DOM, so onPageWillUnload won’t have fired yet. However, if you pop() from the second page back to the first page, then it will be removed from the DOM, and both events will fire because – as you said – you’re leaving the page, and it’s being unloaded. Does that make sense?

  3. Emerson says:

    Hi Colin,

    What I read here will help me a lot, thanks for your explanations! ๐Ÿ™‚

  4. alberto says:

    Hi Colin,
    Nice port. I have a question about this.

    I have to show some datas in a view but I get these datas from an API in the same page so when I try to show them in the view, they are undefined yet. How can I wait to load the view when I have the datas?

    I have tried onPageWillEnter but it does not work.

    thank you in advance.

    • Colin says:

      Right, since it’s async data, it won’t be available when the onPageWillEnter hook gets fired. It’ll show up in its own good time. I’ve written two blog posts about solutions for blocking parts of the DOM with a spinner until you’re sure your data is ready. One uses an inline ngSwitch directive to block elements of the page from rendering until they’re loaded, while the other uses view encapsulation inside a custom component to similarly block elements of the page from rendering until they’re loaded. Thanks for reading!

  5. patmac says:

    Any reason why you have returns in ngOnInit and onPageWillEnter?

    • Colin says:

      Not really. It’s a code style I fell into when I started working on larger teams, where I started to write more verbose code that always includes a return (so long as it’s not an implicit return in an arrow function). But it’s not necessary. If you think it’s confusing I’ll take it out.

  6. lola says:

    I googled “onPageWillEnter” and found nothing on ionic2 docs. If it is still incomplete, how are you getting this info ? Any links would help!
    Even a search within their API docs found nothing.

    Seems like is “onPage” is now “ionView” ? (ref: http://ionicframework.com/docs/v2/api/components/nav/NavController/)

    So if you say yours is ionV2, and their v2 doc states otherwise, and yours still works, which one is correct lol…so confusing

    So until they spruce up their documentation, (so far all very basic, and not yet upto snuff with FB3 and Angular2) we are at the mercy of bloggers and ‘thoughts’ of other developers….any idea when they are gonna go gold with this triad ?
    Everythings’s such a patch-up job till then with total confusion over the cornucopia of tools involved…no doubt its all for the better, just can’t wait ๐Ÿ™‚

    Thanks for your informative links..they are very useful, specially the one to the classic page-nav ASCII art ๐Ÿ™‚

    • Colin says:

      You’re absolutely right, they’ve changed it on me; I’ll update the post. That’s one of the headaches I’ve had with keeping up with projects like Ionic 2 and Angular 2 while they’re in Beta. Things are changing constantly, often for reasons that are hard for us to identify from our point of view. But that’s a risk we take with unstable releases. As for when they’ll update their docs, I have no idea. Until then I’ll just keep writing about my findings and update them whenever someone points out they’re outdated. Hopefully it continues to help. And hey, thanks for reading and especially thanks for commenting!

  7. Wasim Khan says:

    nice post, it solve my problem

  8. Anuj Pathak says:

    Hello,
    i have a issue using Life cycle Events
    eg:
    In Tab1 :ionViewWillEnter()=> fires
    ionViewWillLeave() => fires
    On clicking
    Tab 2 : ionViewWillEnter()=> fires
    ionViewWillLeave() => fires
    On clicking
    Tab 1 again: ionViewWillEnter()=> not fires
    ionViewWillLeave() => not fires

  9. Jhonny says:

    It is exactly what I try to do but I do not assign the http data to the userProfile attribute it is shown in the json console but it does not assign it and gives error, that is very important for example in the navigation by tabs of ionic 2 when you click A tab brings you the data and shows it on that page before uploading. I did not find the answer ! Best Regards ๐Ÿ™ … My code is similar to your example:

    import { Component, OnInit } from ‘@angular/core’;
    import { NavController, NavParams, LoadingController } from ‘ionic-angular’;
    import { Storage } from ‘@ionic/storage’;

    import { ProfileServices } from ‘../../services/profile.services’;

    @Component({
    selector: ‘page-profile’,
    templateUrl: ‘profile.html’,
    providers: [ProfileServices]
    })
    export class Profile implements OnInit {

    public user: any;
    public userProfile: any; // Variable to display in the view after setting it in the http service

    constructor(public navCtrl: NavController, private profile:ProfileServices, public params: NavParams, public storage: Storage, public loading:LoadingController ){
    // this.getPerfil();
    }

    getPerfil(){

    this.presentLoadingDefault();

    this.storage.get('user').then((user) => {
    this.user = JSON.parse(user);
    console.log("user then"+this.user.idUser);

    this.profile.getProfile("token", this.user.idUser ).subscribe(
    data => {

    this.userProfile = data;
    console.log("user profile"+data);
    console.log("user keys"+Object.keys(data));
    console.log("user lenght"+Object.keys(data).length);

    },
    err => console.error(err),
    () => console.log('Peticiรณn completed')
    );
    });

    }

    presentLoadingDefault() {
    let loading = this.loading.create({
    content: 'Please wait...'
    });

    loading.present();

    setTimeout(() => {
    loading.dismiss();
    }, 2000);
    }

    ionViewDidLoad()
    {
    this.getPerfil();
    console.log(“Dentro de ionViewDidLoad”,this.user);//undefined ????
    }

    ngOnInit() {
    // this.getPerfil();
    // console.log(“Dentro de ngOnInit”,this.userProfile); //undefined

    }

    }

  10. nanchao_p says:

    Awesome! thank ๐Ÿ™‚

  11. Al says:

    Hi Colin thank you for providing these resources, I keep returning. When I press Refresh on the browser, which of these events is expected to fire? I would like to save something to Storage when I press Reload on the browser. I am not sure if it is the same thing as 1) closing the browser tab and if it is the same thing as 2) exiting the app on a mobile device. Thanks.

  12. Al says:

    Following up on my questions, I have tried platform.pause and also via document.addEventListener but I don’t see it firing, I realized this Cordova Events stuff is meant for the mobile platform, but even on an Android phone, the code below does not seem to fire

    constructor(private platform: Platform) {
    this.platform.pause.subscribe(e => {
    this.OptionsSave() ;
    alert('pause - subscribe');
    });
    document.addEventListener('pause', () => {
    this.OptionsSave() ;
    alert('pause - document');
    });

    // 20170422
    OptionsLoad() {
    console.log("ChsPage------------>>OptionsLoad");
    this.Storage.ready().then(() => {
    this.Storage.get("FilterContentType").then((data) => {
    this.GlobalService.FilterContentType = data;
    console.log("ChsPage<<------------Storage.get ", data);
    });
    });
    }
    // 20170422
    OptionsSave() {
    console.log("ChsPage------------>>OptionsSave");
    this.Storage.set("FilterContentType", this.GlobalService.FilterContentType);
    }

    // 20170421
    ionViewDidEnter() {
    console.log("ChsPage<<------------ionViewDidEnter");
    this.OptionsLoad();
    }
    // 20170421 ionViewWillUnload
    ionViewDidLeave() {
    console.log("ChsPage<<------------ionViewDidLeave");
    this.OptionsSave();
    }

    It does not work if I run the app on Android device, 2) make a change, 3) exit noting get committed to storage. I must be doing something wrong, in this page, don’t know what? platform.ready ?

    The code works, even in the browser, if 1) I change a segment (filter/pipe) on this Tab’s view, then 2) select another tab, then 3) exit. (IOW, ionViewDidLeave is firing upon tab switching).

    How about for the PWA (Browser ) scenario do we have a solution for my original question if I press the browser Reload URL?
    Thank you.

  13. Al says:

    Hehe! derived a working solution ๐Ÿ™‚ I can go to sleep now it’s 4:13 AM.

    This code works in ionic2 native app exit scenario and in the browser as well, when the URL reload/refresh button is pressed or the browser tab is closed.

    // handles ionic in mobile/device app
    platform.pause.subscribe(e => {
    this.OptionsSave();
    });
    // handles ionic in browser (aka PWA)
    window.addEventListener('beforeunload', () => {
    this.OptionsSave();
    });

    Someone should abstract this into some “app::unload” event for all platforms.

  14. Ahmad Baktash says:

    Qouting the Ionic 2 docs:

    ionViewWillEnter void Runs when the page is about to enter and become the active page.

    ionViewCanEnter boolean/Promise Runs before the view can enter. This can be used as a sort of “guard” in authenticated views where you need to check permissions before the view can enter

    Therefore the hook ionViewCanEnter should be used instead. Meanwhile, since the ionViewWillEnter doesn’t return anything (void), so returning a promise in the function won’t work either:

    ionViewWillEnter() {
    return this.service.getComments().then(data => this.comments = data); // <===
    }

Leave a Reply

Your email address will not be published. Required fields are marked *