Blog

- April 18, 2016

In the previous part of the article, we dealt with the way to manage data from a RESTful service and to link requests with form elements. We now tackle global issues like request interception, error handling, security, and retry.

Intercepting requests

The request interception feature isn’t provided out of the box by Angular2. That said, the framework allows you to override classes gotten from dependency injection. The Http class is responsible from leveraging the underlying XMLHttpObject object to execute HTTP requests.

This class is the one to extend to intercept request calls. The following snippet describes how to implement such class to detect events: before the request, after request, and on error. For this, we leverage the catch and finally operators of observables.

@Injectable()
export class CustomHttp extends Http {
  constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
    super(backend, defaultOptions);
  }

  request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
    console.log('Before the request...');
    return super.request(url, options)
        .catch((err) => {
          console.log('On received an error...');
          return Observable.throw(err);
        })
        .finally(() => {
          console.log(After the request...');
        });
  }

  get(url: string, options?: RequestOptionsArgs): Observable<Response> {
    console.log('Before the request...');
    return super.get(url, options)
        .catch((err) => {
          console.log('On received an error...');
          return Observable.throw(err);
        })
        .finally(() => {
          console.log(After the request...');
        });
  }
}

Now we implemented the CustomHttp class, we need to configure its provider when bootstrapping our application. This can be done using the provide function with the useFactory attribute to keep the hand on the way to instantiate it and provide it the right dependencies.

bootstrap(AppComponent, [
  HTTP_PROVIDERS,
  provide(Http, {
    useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => {
      return new CustomHttp(backend, defaultOptions);
    },
    deps: [ XHRBackend, RequestOptions ]
  })
]);

Be careful to configure the provider of the CustomHttp class after specifying HTTP_PROVIDERS.

This class will be used in the following as foundations to implement generic features like error handling, security and retries under the hood and without any updates in parts of the application that uses the http object. Let’s start by error handling.

Handling errors

Sure we could handle errors for each HTTP call but this could be inconvenient. A better approach consists of leveraging our CustomHttp class. Using the catch operator we can intercept errors. We need then to notify the other parts of the application. For example, the component that is responsible for displaying them.

For this, we need to create a dedicated service ErrorNotifierService that will include an observable and its associated observer. Based on this service, we will be able to notify when errors occur and to be notified.

export class ErrorNotifierService {
  private errorObservable:Observable<any>;
  private errorObserver:Observer<any>;

  constructor() {
    this.errorObservable = Observable.create((observer:Observer) => {
      this.errorObserver = observer;
    }).share();
  }

  notifyError(error:any) {
    this.errorObserver.next(error);
  }

  onError(callback:(err:any) => void) {
    this.errorObservable.subscribe(callback);
  }
}

Be careful to register this service when bootstrapping your application to make it shared by the whole application.

bootstrap(AppComponent, [
  (...)
  ErrorNotifierService
]);

This can be injected in the CustomHttp class. When an error is caught, the notifyError method can be called. All components that registered a callback using the onError method, we will be notified and display messages accordingly.

@Injectable()
export class CustomHttp extends Http {
  constructor(backend: ConnectionBackend,
            defaultOptions: RequestOptions,
            private errorService:ErrorNotifierService) {
    super(backend, defaultOptions);
  }

  request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
    console.log('Before the request...');
    return super.request(url, options)
        .catch((err) => {
          this.errorService.notifyError(err);
        })
        .finally(() => {
          console.log(After the request...');
        });
  }

  (...)
}

Don’t forget to update the way we configured our CustomHttp class for dependency injection:

bootstrap(AppComponent, [
  HTTP_PROVIDERS,
  provide(Http, {
    useFactory: (backend: XHRBackend, defaultOptions: RequestOptions,
                         errorService:ErrorNotifierService) => {
      return new CustomHttp(backend, defaultOptions, errorService);
    },
    deps: [ XHRBackend, RequestOptions, ErrorNotifierService ]
  }),
  ErrorNotifierService
]);

This approach is a bit too global. In some cases, we don’t need to intercept the error but let the error be handled by the code that triggers the request. It’s particularly the case for asynchronous validators of forms and partially when submitting forms. In fact, it’s rather linked to the HTTP status code of the response. The 400 and 422 ones must be handled separately to add some user-friendly messages within forms. For example along with a specific field.

In this case, we need to throw the intercepted error, as described below:

@Injectable()
export class CustomHttp extends Http {
  (...)

  request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
    console.log('Before the request...');
    return super.request(url, options)
        .catch((err) => {
          if (err.status === 400 || err.status === 422) {
            return Observable.throw(err);
          } else {
            this.errorService.notifyError(err);
          }
        })
        .finally(() => {
          console.log(After the request...');
        });
  }

  (...)
}

Handling security

As you can see, we didn’t handle security when executing HTTP requests. That said we don’t want to impact all this code by adding it. Using the class that extends the Http one, we will be able to such processing under the hood without any impact on the existing code.

There are several parts where security can apply especially if the routing feature of Angular is used. For example, we could extend the RouterOutlet directive to check if the user is authenticated before activating secured routes. We could also extend the RouterLink one to check the current roles of the user to display or hide links to secure routes. All these aspects would contribute to improving the user experience.

In this section, we will only focus on securing HTTP calls transparently, i.e. with no impact on the existing code that uses the Http class. We will of course leverage the request interception mechanism previously described.

We can even go further by displaying a dialog to invite the user to fill his credentials. Under the hood, it can be basic authentication or token-based security with tokens that need to be revalidated automatically using a refresh token.

For the basic authentication, it only corresponds to setting the Authorization header based on the credentials the user provides when authenticating. In the case, using the merge method of the request options is enough:

merge(options?:RequestOptionsArgs):RequestOptions {
  (...)
  var credentials = this.securityService.getCredentials();
  if (credentials) {
    let headers = options.headers | {};
    headers['Authorization] = 'Basic ' +
          btoa(credentials.username + ':' + credentials.password);
    options.headers = headers;
  }
  return super.merge(options);
}

In the case of tokens, this must be done within our custom Http class since we need to eventually chain requests using observables and their operators. If we detected that the current token expired, we need to execute the request to refresh this token. When we receive the response, we need to return the observable for the initial request using the flatMap operator or throw an error.

request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
  if (this.securityService.hasTokenExpired()) {
    return this.securityService
             .refreshAuthenticationObservable()
             .flatMap((authenticationResult:AuthenticationResult) => {
               if (authenticationResult.authenticated) {
                 this.securityService.setAuthorizationHeader(request.headers);
                 return super.request(url, request);
               } else {
                 return Observable.throw(new Error('Can't refresh the token'));
               }
    });
  } else {
    return super.request(url, options);
  }
}

The last thing missing to our application is the ability retry requests in failure before returning the failure to the application.

Retry support

Before considering a request as failed, we could consider implementing a retry mechanism. This feature is supported out of the box by observables thanks to the retry operator. Let’s add some retries when calling an HTTP request, for example, within the getBookKinds method.

getBookKinds(): Observable<BookKind[]> {
  return this.http.get('/bookkinds')
                  .retry(4)
                  .map(res => res.map());
}

This approach is much too basic since retries are immediately executed without waiting for a delay. A better one consists of waiting for a bit before retrying and abort after a given amount of time. Observables allow to mix retryWhen, delay and timeout operators to achieve this, as described in the following snippet:

getBookKinds(): Observable<BookKind[]> {
  return this.http.get('/bookkinds')
                  .retryWhen(error => error.delay(500))
                  .timeout(2000, new Error('delay exceeded'))
                  .map(res => res.map());
}

Because such a feature is generic, we can implement it within our CustomHttp class as described below:

@Injectable()
export class CustomHttp extends Http {
  (...)

  request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
    return super.request(url, options)
        .retryWhen(error => error.delay(500))
        .timeout(2000, new Error('delay exceeded'));
  }

  (...)
}

You simplify the code above for clarity by removing processing for security and handling previously described.

Conclusion

In this article, we discussed how to interact with a RESTful service from an Angular 2 application. We deal with the HTTP support provided by the framework and show how Reactive Programming can fit at this level and what it can provide.

We also describe how to implement concrete use cases to control the way requests are executed, to get data from several requests, error handling, security, and retries. All these features contribute to making the application most robust.

We made an effort on the design to leverage the best features and mechanisms of Angular 2. The idea is to add global features with the least impact on the existing code of the application.

The source code is available in the following Github repository: https://github.com/restlet/restlet-samples-angular2-rxjs.

CTA_free trial_3

  • Sean
  • Sean Levy
  • doesn’t the private errorObservable attribute needs to be instantiated using “new”?

    • Yes, you’re right! There was something missing in the snippet:

      this.errorObservable = Observable.create(...

      I updated the article. Thanks very much for pointing this out 😉

  • Pingback: Interacting efficiently with a RESTful service with Angular2 and RXJS (Part 2) | Restlet - We Know About APIs()

  • Lisa

    I`ve been searching for example of custom http for so long. Thank you very much! It`s really helpful.

  • Lisa

    Thierry Templier, Hello!

    I want to add custom public property to my CustomHttp service. I will trigger this property before a request and after a request ends. When I try to access this property outside the CustomHttp class, I get th error – “Http doesn`t have customProperty”. How can I access this customProperty?

    It seems like overriden http service has only original Http methods, not mathods from CustomHttp.

    And if we override Http with CustomHttp in bootstrap provide method, we have no ability to access original Http anymore.

    Thank you in advanced.

  • Thierry Templier

    Hi Lisa,

    I think that this question on StackOverflow could help you: http://stackoverflow.com/questions/38438020/extend-http-class-and-access-custom-properties-angular2-typescript/38439499#38439499. In fact, you need to cast the Http instance to CustomHttp.

    Hope it answers your questions!
    Thierry

  • Tom Schreck

    Thanks for the article. In your ‘Handling Security’ section, your code snippet has this:

    ‘…if (this.securityService.hasTokenExpired())…’

    Where is your securityService? I’ve looked through your GitHub repo and cannot find securityService. securityService is missing from https://github.com/restlet/restlet-samples-angular2-rxjs/tree/master/src/app/services.

    I’m trying to implement this approach, but the key ingredient is missing. This approach will hopefully solve my problem with dealing with refresh tokens. Can you add securityService please? Thank you.

    • Hey Tom – maybe a topic for a different article. The security service agnostic to the Http – there are many ways to implement this. Check auth0-jwt.

  • Josh

    As Tom Schreck mentioned, I would also be very interested in seeing how a securityService with refresh tokens would be implemented in this case. What you have for the http service is so elegant; I’d love to be able to leverage something similar to incorporate security.

    Thanks for your time!

  • hi!Thierry, thx for your article. I have some trouble in implementing your solution.

    My environment which is Angular2 release version. When I use CustomHttpService to send a POST Request and I can`t get the options.url of the AppRequestOptions which is belong to merge. however I can get this from other methods.

  • All request by POST call in file app.request.options.ts

    console.log(options);

    /////////////
    RequestOptions {method: null, headers: null, body: “likes”, url: “http://localhost:8000/api/v1null”, search: null…}

    please help me!!!thanks

  • Paul

    About the handling security part, you cannot inject security service in a class that extends http. Since generating new refresh token will need a post to the server you will need to use http, so it will generate a cyclic dependency error.

    • Velin

      Hello,

      Yes this is exactly what I am facing right now and trying to figure out. The code snippets above are very elegant however I don’t see how they work and have not managed to make them work due to the circular dependency between CustomHttpService and SecurityService … I am currently think what is the best way to solve this issue. Any guidance will help.

  • Jo

    As everyone here, I am really interested in the security piece of your code. I am trying to refresh the token but unfortunately it is not working for me.

  • Vili

    Hello,

    I agree with Paul. Could you provide a plunker with this working. I am currently facing the cyclic dependency issue.

    Best regards

  • Kevin Quiring

    Hello,

    I have referred to this blog post and other posts on stackoverflow related to handling security. I just posted a stackoverflow question and wondered if you could take a look and see if there is an obvious answer I am missing.

    http://stackoverflow.com/questions/42960123/in-angular-2-how-to-intercept-unauthorized-http-call-refresh-token-automatical/42961076#42961076