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

106 Views Asked by At

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?

2

There are 2 best solutions below

0
On BEST ANSWER

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..

2
On

The behavior you're observing, where the MY_TOKEN provider in the OpenModule is not getting used, is expected because Angular's Dependency Injection system follows a hierarchy when resolving dependencies, and it uses the nearest provider it can find in the hierarchy. In your scenario, the ApiModule provides the MyInterceptor, and when the MyInterceptor is constructed, it tries to resolve the MY_TOKEN dependency. The nearest provider of MY_TOKEN it finds is in the AppModule, not the OpenModule, so it uses the MY_TOKEN value from the AppModule.

Here's a simple explanation of what's happening:

When the MyInterceptor is constructed, it looks for the MY_TOKEN provider in the dependency injection hierarchy. It finds the MY_TOKEN provider in the AppModule, so it uses the value 'hello' specified in the AppModule. To achieve your desired behavior, where the MY_TOKEN from the OpenModule is used, you can follow these steps:

Remove the MY_TOKEN provider from the AppModule.

Keep the MY_TOKEN provider in the OpenModule.