Passing data back from a child page (Ionic tips and tricks)

When working with Ionic (3), I came across the need to pass between pages.

Full steam ahead!

Here is what the Ionic docs have to say about it:

push(page, params, opts) 

//Push a new component onto the current navigation stack.
//Pass any aditional information along as an object.
//This additional information is accessible through NavParams

Let’s try it out!

export class StartPage {
    constructor(public navCtrl: NavController) {
    }

    pushPage(): void {
        this.navCtrl.push(OtherPage, {
          id: "123",
          name: "Carl"
        });
    }
}

class OtherPage {
    constructor(private navParams: NavParams) {
        let id = navParams.get('id');
        let name = navParams.get('name');
    }
}

As you can see above, it’s quite straight forward. You push a new component on the navigation stack and pass an object as your navigation parameter and then get those parameters in the child page.

Going back the other way

There might be situations where you want to pass data from your child page to the parent. The user might select something and you want this information in the parent to fill out a form for example.

That’s where things get a little tricky.

Here the documentation say:

pop(opts) 
//Call to navigate back from a current component.
//Similar to push(), you can also pass navigation options.

However, those navigation options don’t offer you all that much

Property   Value    Description
animate    boolean  Whether or not the transition should animate.
animation  string   What kind of animation should be used.
direction  string   The conceptual direction the user is navigating. For example, is the user navigating forward, or back?
duration   number   The length in milliseconds the animation should take.
easing     string   The easing for the animation.

We will have to come up with some other way of passing data back to the parent.

The bad

Two approaches I don’t like at all are: having a global object and having some kind of pub/sub mechanism.

When you maintain a global object which will keep informations for all pages to be read from things can get quite complicate. Once your application gets quite large this starts to look like a mess and having a single file grow to a few hundreds or even thousands of lines (I have worked once on a project that had a globalSettings object was a nightmare) is not really something to look up to.

Another approach is to have some kind of pub/sub functionality where one page would publish/broadcast something and then all other views would just pick it up update their state. I don’t really like this approach since you, again, rely on a single object to take care of everything.

The good

One solution I like quite a lot is the use of Promises. You pass the resolve callback as a parameter to the child view and once the view pops you execute the resolve callback and pass data to the parent.

Your code could look something like this:

export class StartPage {
    constructor(public navCtrl: NavController) {
    }

    pushPage(){
        new Promise((resolve, reject) => {
            this.navCtrl.push(OtherPage, {
                id: "123",
                name: "Carl",
                resolve: resolve
            });
        }).then((data) => {
          console.log(data); // 'some data'
        });
    }
}

class OtherPage {
    private resolve: Function;
    constructor(private navParams: NavParams) {
        let id = navParams.get('id');
        let name = navParams.get('name');
        resolve = navParams.get('resolve'); 
    }
    
    returnData(): void {
        this.navCtrl.pop().then(() => this.resolve('some data'))
    }   
}

This looks nice and there isn’t a lot of overhad involved but, unfortunately, couples your parent its child.

My favorite approach

One thing that bugs me about the above approach is the new Promise we have to create and then wire up to get it working. That’s why the next approach is by far my favorite:

export class StartPage {
    constructor(public navCtrl: NavController) {
    }

    pushPage(): void {
        this.navCtrl.push(OtherPage, {
          id: "123",
          name: "Carl",
          callback: this.handleChildPage
        });
    }
    
    handleChildPage(data): void {
        console.log(data); // 'some data'
    }
}

class OtherPage {
    private callback: Function;
    constructor(private navParams: NavParams) {
        let id = navParams.get('id');
        let name = navParams.get('name');
        callback = navParams.get('callback'); 
    }
    
    returnData(): void {
        this.callback('some data');
        this.navCtrl.pop();
    }   
}

It doesn’t look much different than the solution with promises but I like this one a little more because we have avoided two .then(…) statements which make the code look a little more cluttered than I like. But, just as the example above, this one also couples your two views and that’s something we will have to live with (for now).

The ideal solution

I am looking forward where we will be able to pass navParams into the pop function and then just pick them up in the parent and use them.

Let me know about your approaches in the comments below.

Until next time,

Happy coding