1

Doing our first big App we have encounter a problem that supposed to be fixed in typescript 2.0+

Having the Abstract class:

export abstract class HttpBaseService {
  protected abstract readonly TAG: any;
  protected abstract _state: GlobalState;
  private r = Math.random();

  protected extractData(res: Response) {
    console.log(`${this.TAG}:extractData:`, res.json());
    const data = res.json() || [];
    return data;
  }

  protected handleErrors(error: Response) {
    console.error(`${this.TAG}:handleErrors:`, error);
    console.log(this.TAG);
    console.log(this._state);
    if (error.status === 401 || error.status === 403) {// Not Authorized
      console.error(`${this.TAG}:handleErrors: NOT AUTHORIZED`, error);
      this._state.notifyDataChanged("logout", error);
    }
    return Observable.throw(error.json());
  }

}

and the implementation:

@Injectable()
export class AlbumService extends HttpBaseService {
  TAG = AlbumService.name;

  constructor(protected _state: GlobalState,
              private http: Http,
              private auth: AuthService,
              private localStorage: LocalStorageService,) {
    super();
    this._state = _state;
  }

  albums() {
    console.log(`${this.TAG}:albums:`);
    console.log(this._state);
    const ALBUMS_URL = this.albumsUrl();
    const token = this.auth.getToken();
    const headers = new Headers();
    headers.append('Authorization', `Token ${token}`);

    return this.http
      .get(`${Constants.API_URL}${ALBUMS_URL}`, {headers})
      .catch(this.handleErrors)
      .map(this.extractData);
  }
}

When the function handleError from the super class is executed in a sub class has both parameters TAG and _state as "undefined"

Notice that i have test it putting it abstract, protected, public, from the constructor, manually assigning the members, and maybe several others.

I have to be missing something, because in the sub class constructor, i tried calling the handleError method after the assigning of _state and it works as expected, having a value for TAG an a value for _state.

UPDATE: Reducing the problem to a minimun

Now i have:

export abstract class HttpBaseService {
  protected abstract _state: GlobalState;

  protected handleErrors(error: Response) {
    console.log(this._state);
  }
  protected extractData(res: Response) {
    const data = res.json() || [];
    return data;
  }
}

and the implementation:

@Injectable()
export class AlbumService extends HttpBaseService {

  constructor(_state: GlobalState,
              private http: Http,
              private auth: AuthService,
              private localStorage: LocalStorageService,) {
    super();
    this._state = _state;
  }

  albums() {
    const token = this.auth.getToken();
    const headers = new Headers();
    headers.append('Authorization', `Token ${token}`);

    return this.http
      .get(`${Constants.API_URL}`, {headers})
      .catch(this.handleErrors)
      .map(this.extractData);
  }
}
alacret
  • 572
  • 4
  • 19
  • I don't know if it's related to the problem you're having but `this._state = this._state;` looks redundant - should that be `this._state = _state;`? – 0mpurdy Jul 26 '17 at 23:15
  • @0mpurdy it was an error form one of the implementations that i tried – alacret Jul 26 '17 at 23:20
  • Already tried without being an abstract class and it does not work – alacret Jul 26 '17 at 23:29
  • I try like 9 other solutions that include, non abstract extension, constructor in super class, assigning in the constructor of sub class – alacret Jul 27 '17 at 00:09
  • 1
    Have you tried removing most of the code down to the bare minimum to reproduce the problem? This will make it easier to answer and also in the process you might solve your own problem! [mcve] – 0mpurdy Jul 27 '17 at 00:11
  • Yes, i already create another class for isolating the problem with just one method that does a console.log and it does not work. I will publish the update @0mpurdy – alacret Jul 27 '17 at 00:13
  • Thanks for adding that @alacret however it's very late here - hopefully someone will get this answered before tomorrow otherwise I'll tackle it then! – 0mpurdy Jul 27 '17 at 00:26

2 Answers2

0
  1. Remove abstract from the properties:

    protected abstract readonly TAG: any;
    protected abstract _state: GlobalState;
    
  2. this._state = this._state should probably be this._state = _state;

  3. AlbumService.name does not appear to be defined anywhere.

So you are assigning undefined this._state to this._state, leaving it undefined, and assigning underfined AlbumService.name to TAG.

Sᴀᴍ Onᴇᴌᴀ
  • 8,218
  • 8
  • 36
  • 58
RichGoldMD
  • 1,252
  • 1
  • 10
  • 18
  • Already tried that, i actually put abstract into properties after tried without "abstract" keyword. – alacret Jul 26 '17 at 23:22
  • this._state = this._state was an error from one of the times. I fixed now. – alacret Jul 26 '17 at 23:23
  • AlbumService.name is the name of the class. It works in the subclass, but not when calling the methods defined in the Super class – alacret Jul 26 '17 at 23:23
0

I found the answer here: Angular 2 & RxJs catch function callback binding to 'this' causes http request to be repeated over and over

It turns out that as i'm passing a reference function to .catch and to .map those functions are copied, and the meaning of "this" changes.

So "this" in the function references to the Observable Subscriber. I do not know if this is a design problem but it took me all day to figure out.

So, now my method calls in the Super Class are like this:

export abstract class HttpBaseService {
  protected readonly TAG = HttpBaseService.name;
  protected state: GlobalState;

  protected extractData(res: Response) {
    console.log(`${this.TAG}:extractData:`, res.json());
    const data = res.json() || [];
    return data;
  }

  protected handleErrors(error: Response) {
    console.error(`${this.TAG}:extractData:`, error);
    if (error.status === 401 || error.status === 403) {
      this.state.notifyDataChanged('logout', error);
    }
    return Observable.throw(error.json());
  }

}

And i used like this:

return this.http
  .delete(`${Constants.API_URL}${ALBUMS_URL}${id}/`, {headers})
  .catch((e, c) => this.handleErrors(e))
  .map(res => this.extractData(res));
alacret
  • 572
  • 4
  • 19