简体   繁体   中英

Dynamic providers array with forRoot

I am writting an angular2 InterceptorModule which is part of big core project. Every module needs to be fully configurable and independent. All modules are written with foorRoot method wchich allow us to achieve global singletons and pass any configuration into it.

We decided to define HTTP_INTERCEPTORS providers in forRoot metod of main module. Thanks to this we have one configuration in one place with all URL rules to fire particular interceptors.

This is simple usage in import section of main AppModule :

CoreInterceptorModule.forRoot([
  {
    instance: TestInterceptor,
    runConditions: [],
  },
  {
    instance: MenuInterceptor,
    runConditions: [InterceptorRunConditions.WhiteList],
    whiteList: ['v1/'],
  },
  {
    instance: MenuInterceptor,
    runConditions: [
      InterceptorRunConditions.WhiteList, 
      InterceptorRunConditions.BlackList
    ],
    whiteList: ['v1/'],
    blackList: ['v1/authorize']
  },
]),

CoreStorageModule.forRoot({
  mode: StorageMode.LocalStorage,
  prefix: 'plCore',
  modesPriority: [StorageMode.SessionStorage, StorageMode.Memory],
}),

I have a problem with dynamically injecting providers from forRoot section into ModuleWithProviders from CoreInterceptorModule .

Working scenario

This is a working solution of injecting HTTP_INTERCEPTORS :

@NgModule({})
export class CoreInterceptorModule {
  static config(interceptorConfig: IInterceptorConfig[]): ModuleWithProviders {
    return {
      ngModule: CoreInterceptorModule,
      providers: [
        { provide: INTERCEPTOR_CONFIG, useValue: interceptorConfig },
        { provide: HTTP_INTERCEPTORS, useClass: interceptorConfig[0].instance, multi: true },
        { provide: HTTP_INTERCEPTORS, useClass: interceptorConfig[1].instance, multi: true },
        { provide: HTTP_INTERCEPTORS, useClass: interceptorConfig[2].instance, multi: true },
      ],
    };
  }
}

Of course this solution is bad because amount of HTTP_INTERCEPTORS defined in forRoot is dynamic.

Not working scenario

Solution is just to inject providers dynamically, like that:

@NgModule({})
export class CwaCoreInterceptorModule {
  static forRoot(interceptorConfig: IInterceptorConfig[]): ModuleWithProviders {

    // base module configuration
    const moduleWithProviders: ModuleWithProviders = {
      ngModule: CwaCoreInterceptorModule,
      providers: [
        { provide: INTERCEPTOR_CONFIG, useValue: interceptorConfig },
      ],
    };

    // update HTTP_INTERCEPTORS array
    interceptorConfig.forEach((e) => {
      moduleWithProviders.providers.push({
        provide: HTTP_INTERCEPTORS, useClass: e.instance, multi: true
      });
    });

    return moduleWithProviders;
  }
}

This piece of code causing an exception:

ERROR in Error during template compile of 'AppModule' Function calls are not supported in decorators but 'CoreInterceptorModule' was called.

I read some topics about this problem and none of them helped in my case. The problem is that it's not possible to call any function before return statement in forRoot method.

The question is how dynamically add HTTP_INTERCEPTORS in forRoot method? I thought about something like useFactory but it seams it can return just one value (one instance).

I resolved this problem. Instead of using my custom configuration type IInterceptorConfig (which is later impossible to iterate) I created custom Angular DI provider type inherited from original Angular\\@code\\classProvider :

export interface InterceptorClassProvider extends ClassProvider {
  runConditions: InterceptConditions[];
  whiteList?: string[];
  blackList?: string[];
}

Thanks to this I have my additional fields . forRoot method configuration looks like that:

CwaCoreInterceptorModule.forRoot([
  {
    provide: HTTP_INTERCEPTORS,
    useClass: MenuInterceptor,
    multi: true,
    runConditions: [InterceptConditions.WhiteList],
    whiteList: ['/v1']
  },
]),

And finally and the most important I can dynamically fill ModuleWithProviders interface in sharedModule without errors :

static forRoot(interceptorProviders: InterceptorClassProvider[]): ModuleWithProviders {
  return {
    ngModule: CwaCoreInterceptorModule,
    providers: [
      { provide: INTERCEPTOR_CONFIG, useValue: interceptorProviders },
      ...interceptorProviders
    ],
  };
}

It works because I use array spread operator ( ... ) instead of any additional functions call. The only cons of this solution is some unnecessary attributes like provide , useClass , multi which can not be encapsulated in any lower layer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM