Ionic-framework: `this` is undefined in an event subscription callback

Created on 16 Nov 2017  路  2Comments  路  Source: ionic-team/ionic-framework

Ionic version: (check one with "x")
(For Ionic 1.x issues, please use https://github.com/ionic-team/ionic-v1)
[ ] 2.x
[x] 3.x
[ ] 4.x

I'm submitting a ... (check one with "x")
[x] bug report
[ ] feature request

Please do not submit support requests or "How to" questions here. Instead, please use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/

Current behavior:
this is undefined when running a callback (handler) in the event subscription which is trying to reference another class function. For example, consider the following, a simple class:

import { Events } from 'ionic-angular';

export class MyEventsClass implements OnDestroy {
  constructor(private Events events) {
    events.subscribe('user:updated', this.saveUser);
  }

  saveUser() {
    // do something with `user`

    this.persist();
  }

  persist() {
    // do something to persist
  }

  ngOnDestroy() {
    this.events.unsubscribe('user:updated', this.saveUser);
  }
}

When the user:updated handler runs, you receive the error:

Uncaught TypeError: Cannot read property 'persist' of undefined

The handler reference is required when you want to unsubscribe from a particular instance of an event, as you need the same reference to pass in, hence the set up in this manner, and the call in ngOnDestroy. Otherwise, a lambda function would have sufficed.

Expected behavior:
To be able to reference class functions using the this reference.

I have done some debugging, and it is relating to the following line of code:

responses.push(handler(...args));

TypeScript transpiles this to:

responses.push(handler.apply(void 0, args));

And apply's first argument, void 0, results in undefined, hence the above error which makes sense.

Steps to reproduce:
See above.

Related code:
I have opened a StackBlitz to illustrate the issue here: https://stackblitz.com/edit/ionic-fj2swn

There is a workaround to achieve desired functionality, although not quite as nice, which is to curry the function and have saveUsers return a function which you can capture reference to this in via closure:

constructor(private Events events) {
  events.subscribe('user:updated', this.saveUser(this));
}

saveUsers(that: any) {
  return () => {
    that.persist();
  }
}

Other information:

Ionic info: (run ionic info from a terminal/cmd prompt and paste output below):

insert the output from ionic info here
stale issue

Most helpful comment

So, when consider the following code:

export class HomePage {
    constructor(public navCtrl: NavController, public events: Events ) {    }
    ngOnInit() {        
        this.events.subscribe('user:updated', this.saveUser )   
    }
    saveUser() {
        // do something with `user`        
        this.persist();
    }
    persist() {
        // do something to persist
        console.log('on persist');        
    }

when this code is compiled to javascript it becomes on:
```
HomePage.prototype.ngOnInit = function () {
this.events.subscribe('user:updated', this.fun);
};
HomePage.prototype.saveUser = function () {
this.persist();
};
HomePage.prototype.persist = function () {
console.log('on persist');
};
HomePage.prototype.editUser = function () {
this.events.publish('user:updated', { edited: true });
};

so `this` on `saveUser` refer to function `saveUser` and not the class HomePage. So the error.
To solve this you have two choices:
first use arrow functions:
```...
    ngOnInit() {
            this.events.subscribe('user:updated', () => {
                this.saveUser();
            })
        }
    ...

or

   ...
    fun: any;
    ngOnInit() {
        this.fun = this.saveUser.bind( this );
        this.events.subscribe('user:updated', this.fun )
    }
  ...

I hope I have helped.

All 2 comments

So, when consider the following code:

export class HomePage {
    constructor(public navCtrl: NavController, public events: Events ) {    }
    ngOnInit() {        
        this.events.subscribe('user:updated', this.saveUser )   
    }
    saveUser() {
        // do something with `user`        
        this.persist();
    }
    persist() {
        // do something to persist
        console.log('on persist');        
    }

when this code is compiled to javascript it becomes on:
```
HomePage.prototype.ngOnInit = function () {
this.events.subscribe('user:updated', this.fun);
};
HomePage.prototype.saveUser = function () {
this.persist();
};
HomePage.prototype.persist = function () {
console.log('on persist');
};
HomePage.prototype.editUser = function () {
this.events.publish('user:updated', { edited: true });
};

so `this` on `saveUser` refer to function `saveUser` and not the class HomePage. So the error.
To solve this you have two choices:
first use arrow functions:
```...
    ngOnInit() {
            this.events.subscribe('user:updated', () => {
                this.saveUser();
            })
        }
    ...

or

   ...
    fun: any;
    ngOnInit() {
        this.fun = this.saveUser.bind( this );
        this.events.subscribe('user:updated', this.fun )
    }
  ...

I hope I have helped.

Thanks for the issue! This issue is being closed due to inactivity. If this is still an issue with the latest version of Ionic, please create a new issue and ensure the template is fully filled out.

Thank you for using Ionic!

Was this page helpful?
0 / 5 - 0 ratings