Search code examples
angularangular-routingangular-providers

Overridding a value with Providers array not working with lazy loaded modules / routing


I have an app module that is doing:

export const MY_TOKEN = new InjectionToken<any>('MY_TOKEN');
@NgModule({
  imports: [
    ApiModule,
    AppRoutingModule,
  ],
  providers: [
    { provide: MY_TOKEN, useValue: 'hello' },
  ],
  ...

that app routing module has a route:

{
  path: '',
  loadChildren: () => import('./open.module').then(m => m.OpenModule),
},

That open module is doing:

@NgModule({
  imports: [
    OpenRoutingModule,
  ],
  providers: [
    { provide: MY_TOKEN, useValue: 'lol' },
  ],

and then the open routing module has a path that that lazy loads a module / component

{
  path: 'some/where',
  loadChildren: () => import('./something.module').then(m => m.SomethingModule),
},

...

Ok, so next, I have an HTTP interceptor that is provided in the ApiModule ...

@NgModule({
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: MyInterceptor, multi: true },
  ],
})
export class ApiModule {}

and in that intercepter, I am doing:

constructor(
  @Inject(MY_TOKEN) public val: any
) {
  console.log(val);
}

When the component in the open module makes api calls, the value is hello rather than lol...

I thought at first this might be because the interceptor is getting instantiated before the module that gives the provider for the token, so then I tried using the injector directly in the interceptor:

intercept(
  request: HttpRequest<unknown>,
  next: HttpHandler
): Observable<HttpEvent<unknown>> {
  console.log(this.injector.get(MY_TOKEN));
  ...

But that still logs hello instead of lol ... Why is the provider I specified in the Open module not getting used?


Solution

  • The only way I could figure out how to do this was to initially have an object set and mutate that object in the various modules...

    export interface Context {
      open?: boolean;
    }
    export const context: Context = {};
    export const CONTEXT = new InjectionToken<Context>('CONTEXT', {
      providedIn: 'root',
      factory: () => context,
    });
    
    // app module:
    { provide: CONTEXT_TOKEN, useValue: context },
    
    // open module
    export class OpenModule {
      constructor(@Inject(CONTEXT) context: context) {
        context.open = true;
      }
    }
    
    // other modules
    export class ModuleXYZ {
      constructor(@Inject(CONTEXT) context: context) {
        context.open = false;
      }
    }
    

    Now the interceptor has the correct state....... I really just wanted to be able to not have this http interceptor be applied to things in the Open module, but that apparently was not possible because when I initially tried providing the http interceptors in the further down modules then they were never being applied... So I have had to resort to this dumb "context" code so that the interceptor can do:

      intercept(
        request: HttpRequest<unknown>,
        next: HttpHandler
      ): Observable<HttpEvent<unknown>> {
        if (this.context.open) {
          return next.handle(request);
        }
        ...
    

    If anyone has any suggestions on how I can do this in a less dumb way, I'd greatly appreciate it..