0

I'm learning about abstract classes in Typescript. The this keyword works perfectly in every method in this class except for handleRetry. Even if I try to console.log(this.amiUrl) at the top of that method, it blows up and tells me that it can't find it.

I've already tried removing the protected keyword, believing I had misunderstood its use. No change.

Angular 4.3

Typescript 2.4.1

import { HttpHeaders, HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';

import { ToastsManager } from 'ng2-toastr/ng2-toastr';

import { Store } from '@ngrx/store';
import * as uiActions from '../../core/store/actions/ui.actions';
import * as fromRoot from '../../core/store/reducers';

import { environment } from '../../../environments/environment';

@Injectable()
export abstract class RestService {
  protected amiUrl = environment.api;
  protected maxRetryAttempts = 3;

  constructor (
    private http: HttpClient,
    private store: Store<fromRoot.State>,
    private toastr: ToastsManager ) { }

  private get headers(): HttpHeaders {
    const headers: HttpHeaders = new HttpHeaders();
    headers.set('Authorization', 'Bearer ' + environment.accessToken);
    return headers;
  }

  protected get(url: string) {
    return this.http.get(this.amiUrl + url, { headers: this.headers })
      .let(this.handleRetry);
  }

  protected post(url: string, payload: any) {
    return this.http.post(this.amiUrl + url, payload, { headers: this.headers })
      .let(this.handleRetry);
  }

  protected delete(url: string) {
    return this.http.delete(this.amiUrl + url, { headers: this.headers })
      .let(this.handleRetry);
  } 

  protected handleRetry<T>(source: Observable<T>): Observable<T> { 
    return source.retryWhen(e => 
      e.scan((errorCount, error) => {
        if (errorCount >= this.maxRetryAttempts) {
          this.store.dispatch(new uiActions.ClearRetryNotificationAction);
          throw error;
        } else {
          this.store.dispatch(new uiActions.CreateRetryNotificationAction({ attempt: errorCount + 1, maxAttempts: this.maxRetryAttempts }))
          return errorCount + 1;
        }
      }, 0)
      .delay(2000))
  }

  protected handleError(err: HttpErrorResponse, customMessage?: string) {
    this.store.dispatch(new uiActions.CreateErrorNotificationAction(customMessage));

    console.log(err.error);
    console.log(err.status);
    console.log(err.name);
    console.log(err.message);

    if (!environment.production) {
      this.toastr.error(customMessage);
    }

    return Observable.throw(err.message);
  }
}
Kevin B
  • 94,570
  • 16
  • 163
  • 180
wolfhoundjesse
  • 1,085
  • 9
  • 32

1 Answers1

3

That's because you are passing this.handleRetry as a callback.
When the callback is invoked the scope changes and this no longer reference the instance of RestService.

To fix this you have four options:

(1) Use the bind method:

...
.let(this.handleRetry.bind(this))

(2) Use an arrow function:

...
.let(source  => this.handleRetry(source))

(3) Bind the method in the ctor:

constructor (
    private http: HttpClient,
    private store: Store<fromRoot.State>,
    private toastr: ToastsManager ) {

    this.handleRetry = this.handleRetry.bind(this);
}

Then when you pass this.handleRetry it's already bound to the instance and will stay like that even when invoked.

(4) Use an arrow function instead of a method:

handleRetry = <T>(source: Observable<T>): Observable<T> => {
    ...
}

That will create a property of type function in the instance, and because it's an arrow function it is bound to it.
It's not a method though, it won't be part of the prototype and so won't be inherited if you extend the class.

Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
  • I would have known this was a duplicate question had I thought of my problem as a callback problem. I'm surprised I haven't run into this before. Thanks for the additional comment about properties of type function not being inherited. That is very useful. I choose option 4! – wolfhoundjesse Jul 18 '17 at 15:27