3

I need to handle 401 error correctly on my Ionic 3 app. The below mentioned error comes when the app has not been used for a few days and after that user tries to access the app. This app has a backend web API(django) and it uses jwt token to validate the user.

So can you tell me the correct workflow of handling this kind of use case on Ionic 3 app? I was unable to find out good resources to refer this on the web. If you have something to share, it would be great.

I have seen this URL where it mentioned about subscribe subjects. But I don't know how to implement such thing with the Ionic app. Any clue, please?

authProvider.ts

import { Injectable, } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Headers, RequestOptions, BaseRequestOptions } from '@angular/http';
import { Storage } from '@ionic/storage';



 createHeader(headers: Headers) {
        return new Promise((resolve, reject) => {
            this.storage.get('loggedInUser')
                .then((token: any) => {
                    if (token) headers.append('Authorization', 'token ' + token.token);
                    resolve(headers);
                }, err => {
                    resolve(headers);
                });
        });
    }

 get(api) {
        return new Observable(observer => {
            let header = new Headers();
            this.createHeader(header)
                .then(() => {
                    let options = new BaseRequestOptions();
                    options.withCredentials = true;
                    options.headers = header;
                    this.http.get(api, options)
                        .subscribe(response => {
                            observer.next(response);
                            observer.complete();
                        }, (e) => {
                            observer.error(e);
                        });
                })
        })
    }

Console error message:

enter image description here

Sampath
  • 63,341
  • 64
  • 307
  • 441

1 Answers1

6

retryWhen to the rescue !

If you want to retry the call once you fixed things (like renewing a token or so) retryWhen() is the way to go.

public retryWhen(notifier: function(errors: Observable): Observable): Observable

Returns an Observable that mirrors the source Observable with the exception of an error. If the source Observable calls error, this method will emit the Throwable that caused the error to the Observable returned from notifier.

If that Observable calls complete or error then this method will call complete or error on the child subscription. Otherwise this method will resubscribe to the source Observable.

tldr :This operator will handle the errors and if the observable returned by the notifier function emits a value, will resubscribe to the previous Observable. But, if the Observable emitted by notifier throws an error, the error will be propagated.

get(api) {
  let header = new Headers();
  return Observable.fromPromise(this.createHeader(header))
  .map(()=>{
    let options = new BaseRequestOptions();
    options.withCredentials = true;
    options.headers = header;
    return options
  })
  .switchMap((options)=>this.http.get(api, options))
  .retryWhen((errors)=>errors.switchMap((err:Error| Response)=>{
    if(err instanceof Response && err.status===401){
      // handle 401
      return this.someHandlingReturningAnObservable();
    }
    return Observable.throw(err);
  }));
}

Here, this.someHandlingReturningAnObservable() returns an Observable. In this method you fix your problem (ask for a new token, etc) and when it emits its value, the observable chain will be replayed. non-401 errors are just not handled.

catch() me if you can

If you just want to handle the error and do nothing more (display an error page for example) you can use the catch() operator :

public catch(selector: function): Observable

Catches errors on the observable to be handled by returning a new observable or throwing an error.

get(api) {
  let header = new Headers();
  return Observable.fromPromise(this.createHeader(header))
  .map(()=>{
    let options = new BaseRequestOptions();
    options.withCredentials = true;
    options.headers = header;
    return options
  })
  .switchMap((options)=>this.http.get(api, options))
  .catch((err:Error| Response)=>{
    if(err instanceof Response && err.status===401){
      // handle 401
      return this.someHandlingReturningAnObservable();
    }
    return Observable.throw(err);
  });
}
Sampath
  • 63,341
  • 64
  • 307
  • 441
n00dl3
  • 21,213
  • 7
  • 66
  • 76
  • Can you tell me the `operator` or `module` of `retryWhen`? – Sampath Sep 22 '17 at 10:37
  • I am not sure if I understand your question, but `import "rxjs/add/operator/retryWhen"` ? – n00dl3 Sep 22 '17 at 10:39
  • Yes. That is OK now. But what is this `someHandlingReturningAnObservable`? Do I need to write this method or? I need to capture this error in the `app.component.ts` (root) file and need to show `toast` message or redirect the user to `login` page. Any clue please? – Sampath Sep 22 '17 at 10:43
  • then go for the `catch()` part `someHandlingReturningAnObservable()` is the method you will use to handle the error, you have to implement it, yes this is where you will display toast and/or redirect. note that It has to return an observable (but it can be `Observable.empty()`). – n00dl3 Sep 22 '17 at 10:46
  • OK. On Ionic workflow, I have to do it inside the component.So can I emit an `event` here and capture it inside the `app` component? Then I can handle it as I wish. – Sampath Sep 22 '17 at 10:56
  • I don't know why you "have" to do this inside component as `ToastController` is a service, you can inject it inside your service, same for `Router`. Anyway you can do the catch inside the component instead of the service or create a subject that notifies there has been an error inside the service and subscribe inside your component, possibilities are endless. – n00dl3 Sep 22 '17 at 11:03
  • You're right. I can use the `toast` here. But how can I redirect the user to the `login` page? For that, I need to use `root` component no. My idea was to make an event here and subscribe that event inside the root page. Your thoughts? This is the event doc: http://ionicframework.com/docs/api/util/Events/ – Sampath Sep 22 '17 at 11:12
  • Why would you need such a thing ? Except if your route depends on the activated route parameters and is not one of its child, you can navigate to its absolute url : `this.router.navigate(["/login"]);`... – n00dl3 Sep 22 '17 at 11:21
  • `Ionic` page navigation is so different than `Angular`. Please see: http://ionicframework.com/docs/api/navigation/NavController/ – Sampath Sep 22 '17 at 11:31
  • Hope you went through the above doc? Any clue, please? – Sampath Sep 22 '17 at 12:19
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/155085/discussion-between-n00dl3-and-sampath). – n00dl3 Sep 22 '17 at 13:01
  • OK, Can you give me an example for this method `someHandlingReturningAnObservable` ? You can just show me how to show the `toaster` inside above method? That is it for me. – Sampath Sep 22 '17 at 13:44
  • Thanks a lot for the huge info :) – Sampath Sep 27 '17 at 09:21