122

I have a component which calls a service to fetch data from a RESTful endpoint. This service needs to be given a callback function to execute after fetching said data.

The issue is when I try use the callback function to append the data to the existing data in a component's variable, I get a EXCEPTION: TypeError: Cannot read property 'messages' of undefined. Why is this undefined?

TypeScript version: Version 1.8.10

Controller code:

import {Component} from '@angular/core'
import {ApiService} from '...'

@Component({
    ...
})
export class MainComponent {

    private messages: Array<any>;

    constructor(private apiService: ApiService){}

    getMessages(){
        this.apiService.getMessages(gotMessages);
    }

    gotMessages(messagesFromApi){
        messagesFromApi.forEach((m) => {
            this.messages.push(m) // EXCEPTION: TypeError: Cannot read property 'messages' of undefined
        })
    }
}
2
  • Which version of TypeScript are you using? (You can check that with tsc -v) Commented Jul 7, 2016 at 12:28
  • The exception because forEach. Use For-of instead. Commented Mar 7, 2018 at 8:56

5 Answers 5

219

Use the Function.prototype.bind function:

getMessages() {
    this.apiService.getMessages(this.gotMessages.bind(this));
}

What happens here is that you pass the gotMessages as a callback, when that is being executed the scope is different and so the this is not what you expected.
The bind function returns a new function that is bound to the this you defined.

You can, of course, use an arrow function there as well:

getMessages() {
    this.apiService.getMessages(messages => this.gotMessages(messages));
}

I prefer the bind syntax, but it's up to you.

A third option so to bind the method to begin with:

export class MainComponent {
    getMessages = () => {
        ...
    }
}

Or

export class MainComponent {
    ...

    constructor(private apiService: ApiService) {
        this.getMessages = this.getMessages.bind(this);
    }

    getMessages(){
        this.apiService.getMessages(gotMessages);
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Worked, thanks! And thanks for explaining why this happens.
Thank you! The fact that OOP-style is leaking javascript specifics (bind) is tragic.
Totally argee with @skfd that the .bind( this ) should be default TypeScript behaviour, since we are programming OO! :-) But thanks, I'll remember to add this to all callbacks on class member functions now, which is always case in my case, since ...
21

Or you can do it like this

gotMessages(messagesFromApi){
    let that = this // somebody uses self 
    messagesFromApi.forEach((m) => {
        that.messages.push(m) // or self.messages.push(m) - if you used self
    })
}

1 Comment

You are my hero
20

Because you're just passing the function reference in getMessages you don't have the right this context.

You can easily fix that by using a lambda which automatically binds the right this context for the use inside that anonymous function:

getMessages(){
    this.apiService.getMessages((data) => this.gotMessages(data));
}

2 Comments

That was it! Thanks
Perfect. Simplest and efficient solution.
2

I have same issue, resolved by using () => { } instead function()

1 Comment

this does not add any information to the existing answers
0

Please define function

gotMessages = (messagesFromApi) => {
  messagesFromApi.forEach((m) => {
    this.messages.push(m)
  })
}

1 Comment

this does not add any information to the existing answers

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.