the-a-train
the-a-train

Reputation: 1143

Using Angular 2+ forRoot pattern to pass callbacks into modules

I have created an Angular Module called DataModule which contains services for accessing my backend through a token-secured HTTP API. On HTTP access control errors such as 401 Unauthorized and 403 Forbidden I want to be able to redirect to /login or try and refresh the token etc.

I want to re-use this DataModule in an Ionic application, so i don't want any coupling with the standard @angular/router RouterModule or anything else.

Currently, I am passing in some primitive values via the forRoot method when importing the DataModule in my AppModule:

DataModule.forRoot({
  url: environment.apiEndpoint,
  token() {
     return localStorage.getItem('auth_token')
  },
  onError(errors: any) {
     console.log(this);
  }
});

I need the onError callback to have the ability to call router.navigate('/login') or similar, but as this is a simple config object I don't think router can be injected at this stage.

I am looking for a way to pass something like this in and I have reviewed several other popular ng projects such as ngx-translate, angularitics2 and ngx-restangular which all pass in providers that seem to be then provided back by the components. Is this the way to achieve what I am after by passing in an AuthException service or similar that implements an interface kept in the DataModule package?

Update:

I've ended up taking the idea from the projects I mentioned which is for my DataModule to have a default service for error handling which basically does nothing. I then inject a better error handling class that is specific to the app in the forRoot which then gets used in the providers:

static forRoot(config: any): ModuleWithProviders {

    return {
      ngModule: DataModule,
      providers: [
        config.authExceptionService || AuthExceptionService,
        { provide: CONFIG, useValue: config },
        {
          provide: ApiService,
          useFactory: ApiServiceFactory,
          deps: [Http, config.authExceptionService || AuthExceptionService, CONFIG]
        },
      ]
    }
  }

My ApiServiceFactory for reference:

export function ApiServiceFactory(http: Http, authException: AuthExceptionService, config: any) {

  return new ApiService(http, authException, {
    url: config.url,
    token: config.token
  });
}

I also had to make use of an InjectionToken in order to provide my config object as a dependency to the factory:

export const CONFIG = new InjectionToken<any>('CONFIG');

From my main app I do the following:

DataModule.forRoot({
      authExceptionService: AppAuthExceptionService,
      url: environment.apiEndpoint,
      token() {
        return localStorage.getItem('auth_token')
      }
    });

It would be great to hear from anyone if there are any potential issues with this approach. I am hoping I should be able to use this module in the Ionic app with something like the following:

DataModule.forRoot({
      authExceptionService: MobileAppAuthExceptionService,
      url: environment.apiEndpoint,
      token() {
        return storage.getItem('auth_token') // ionic uses different storage
      }
    })

Upvotes: 1

Views: 811

Answers (2)

JusMalcolm
JusMalcolm

Reputation: 1431

One possible solution for this is to extend your DataModule in each project and override the onError and constructor methods respectively.

export class MyDataModule extends DataModule {

  static ForRoot(...){
    ...
  }

  constructor(...args, private router: Router){
    super(...args)
  }

  onError() {
    this.router.navigate(errorPage);
  }
}

Then in your ionic application you can handle the error in some other way.

Upvotes: 0

Ced
Ced

Reputation: 17327

You could extend Http and apply a filter

export class MyHttp extends Http {
   //... 
   constructor(){super()}

   get(){
      super.get()
        .filter(x => {
         // do your logic here
         if(x.status === 403){
            router.navigate( errorPage )
            return false;
         }             
      })
   }
}

Or alternatively use map and throw an error

if (response.status === 403) {
   throw Observable.throw(response);  
 } 

and have a catch function that does the redirect (which imo is the better way to do it).

Upvotes: 0

Related Questions