15

I'm getting the following error in my typescript class and cannot understand why. All I am doing is trying to call a helper function passing the token.

Error:

post error: TypeError: this.storeToken is not a function(…)

Class:

/**
 * Authentication Service:
 *
 * Contains the http request logic to authenticate the
 * user.
 */
import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';

import 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';

import { AuthToken } from './auth-token.service';

import { User } from '../../shared/models/user.model';

@Injectable()
export class Authenticate {

  constructor(
    private http: Http,
    private authToken: AuthToken
  ) {}

  post(user: User): Observable<any> {
    let url = 'http://localhost:4000/';
    let body = JSON.stringify(user);
    let headers = new Headers({ 'content-type': 'application/json' });
    let options = new RequestOptions({ headers: headers });

    return this.http.post(url + 'login', body, options)
      .map(this.handleData)
      .catch(this.handleError);
  }

  private storeToken(token: string) {
    this.authToken.setToken(token);
  }

  private handleData(res: Response) {
    let body = res.json();
    this.storeToken(body.token);
    return body.fields || {};
  }

  private handleError(error: any) {
    console.error('post error: ', error);
    return Observable.throw(error.statusText);
  }
}

I am new to typescript so I'm sure I am missing something ridiculously simple. Any assist would be great.

Thanks.

Chris Stillwell
  • 10,266
  • 10
  • 67
  • 77
Aaron
  • 2,364
  • 2
  • 31
  • 56

1 Answers1

21

It should either be (using Function.prototype.bind):

return this.http.post(url + 'login', body, options)
      .map(this.handleData.bind(this))
      .catch(this.handleError.bind(this));

Or (using arrow functions):

return this.http.post(url + 'login', body, options)
      .map((res) => this.handleData(res))
      .catch((error) => this.handleError(error));

What happens is that you are passing a reference to your method but it's not bound to a specific this, so when the method is executed the this in the function body isn't the instance of the class but the scope that executes the method.

Each of of those help keep the right context for this, but in a different way.


Edit

Another option is:

export class Authenticate {
    ...

    private handleData = (res: Response) => {
        ...
    }

    private handleError = (error: any) => {
        ...
    }
}

In this way the "methods" are already bound, but in this case they won't be part of the prototype, and will in fact become just properties of type function.
For example:

class A {
    method1() { }
    method2 = () => {}
}

Compiles to:

// es5
var A = (function () {
    function A() {
        this.method2 = function () { };
    }
    A.prototype.method1 = function () { };
    return A;
}());

// es6
class A {
    constructor() {
        this.method2 = () => { };
    }
    method1() { }
}

Because of that method2 can't be (easily) overriden, so beware of this implementation.

Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
  • Sure thing. I edited my answer with another approach, which I'm not a fan of, but it's still an option. – Nitzan Tomer Dec 05 '16 at 00:43
  • I feel the arrow functions are the way to go since it is a consistent pattern. I was experimenting with some different patterns I found online and forgot to convert those to the arrow function which is why I had the issue and didn't realize what happened. – Aaron Dec 05 '16 at 00:49