O "this" do componente Angular2 é indefinido ao executar a função de retorno de chamada

92

Eu tenho um componente que chama um serviço para buscar dados de um ponto de extremidade RESTful. Este serviço precisa receber uma função de retorno de chamada para ser executado após a busca dos dados.

O problema é que, quando tento usar a função de retorno de chamada para acrescentar os dados aos dados existentes na variável de um componente, obtenho um EXCEPTION: TypeError: Cannot read property 'messages' of undefined. Por que está thisindefinido?

Versão TypeScript: Versão 1.8.10

Código do controlador:

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
        })
    }
}
Michael Gradek
fonte
Qual versão do TypeScript você está usando? (Você pode verificar isso com tsc -v)
rinukkusu
A exceção porque forEach. Em vez disso, use For-of.
Pax Beach,

Respostas:

155

Use a função Function.prototype.bind :

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

O que acontece aqui é que você passa o gotMessagescomo callback, quando está sendo executado o escopo é diferente e então thisnão é o que você esperava.
A bindfunção retorna uma nova função que está vinculada ao que thisvocê definiu.

Você pode, é claro, usar uma função de seta lá também:

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

Eu prefiro a bindsintaxe, mas é com você.

Uma terceira opção para ligar o método para começar:

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

Ou

export class MainComponent {
    ...

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

    getMessages(){
        this.apiService.getMessages(gotMessages);
    }
}
Nitzan Tomer
fonte
1
Funcionou, obrigado! E obrigado por explicar por que isso acontece.
Michael Gradek
Obrigado! O fato de que o estilo OOP está vazando especificações de javascript (bind) é trágico.
skfd
21

Ou você pode fazer assim

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

Porque você está apenas passando a referência de função, getMessagesvocê não tem o thiscontexto certo .

Você pode corrigir isso facilmente usando um lambda que vincula automaticamente o thiscontexto certo para o uso dentro dessa função anônima:

getMessages(){
    this.apiService.getMessages((data) => this.gotMessages(data));
}
Rinukkusu
fonte
Era isso! Obrigado
Michael Gradek
Perfeito. Solução mais simples e eficiente.
Kon
1

Eu tenho o mesmo problema, resolvido usando () => {} em vez da função ()

Bharathiraja
fonte
0

Defina a função

gotMessages = (messagesFromApi) => {
  messagesFromApi.forEach((m) => {
    this.messages.push(m)
  })
}
Tài
fonte