Search code examples
angulardependency-injection

Use provider from the same module in Angular DI


I have a fairly simple question about dependency injection in angular. I have two modules (Module1, Module2), each one has it's own component (cmp1, cmp2), and Module1 uses Module2. Both components are trying to use the same service, but with different configurations. Different configurations are provided through useFactory inside the modules, and they have the same InjectionToken. The problem is that, when cmp2 is created it receives a provider defined in Module1. Is it possible that components inside a module would receive providers defined in their own module? Maybe the whole approach is incorrect, I'm new to Angular DI, please feel free to point out the way you would approach this task.

For simplicity, I would use just an object to represent provider that is intended to be used. In fact it is created in following way new PermissionService(module1config) and new PermissionService(module2config)

Code for InjectionToken:

export const PERMISSION_SERVICE = new InjectionToken('PermissionService');

Code for Module2:

@Component({
  standalone: false,
  selector: 'cmp2',
  template: "CMP2",
})
export class Cmp2
{
  constructor(
    @Inject(PERMISSION_SERVICE) private permissionService: any
  ){
     console.log('cmp2 ' + permissionService.service); 
     // prints out `cmp2 service1`, I want `cmp2 service2`
  }
}

@NgModule({
  declarations: [ Cmp2 ],
  exports: [ Cmp2 ],
  providers: [
    {
      provide: PERMISSION_SERVICE,
      useFactory: () => ({ service: "service2" })
    }
  ]
})
export class Module2 {}

Code for Module1:

@Component({
  selector: 'cmp1',
  standalone: false,
  template: "<cmp2/>"
})
export class Cmp1
{
  constructor(
    @Inject(PERMISSION_SERVICE) private permissionService: any
  ){
     console.log('cmp1 ' + permissionService.service); // prints out `cmp1 service1`
  }
}

@NgModule({
  imports: [ Module2 ],
  declarations: [ Cmp1 ],
  exports: [ Cmp1 ],
  providers: [
    {
      provide: PERMISSION_SERVICE,
      useFactory: () => ({ service: "service1" })
    }
  ]
})
export class Module1 {}

To fix that I can define provider on component level, but that would mean I should do it in every component of the module. I'm not sure if it's ok.

@Component({
  standalone: false,
  selector: 'cmp2',
  template: "CMP2",
  providers: [
    {
      provide: PERMISSION_SERVICE,
      useFactory: () => ({ service: "service2" })
    }
  ]
})
export class Cmp2
{
  constructor(
    @Inject(PERMISSION_SERVICE) private permissionService: any
  ){
    console.log('cmp2 ' + permissionService.service); // prints out `cmp2 service2`
  }
}

Solution

  • As you have observed, the providers are not exported when you import a module. Nor do they need to be imported, since they are mean't to be shared and should take from the module they are imported into.

    The approach of adding the providers in the component is correct. Having to import them at for all the components is a pain, but it is necessary.


    There is an alternative you can do like.

    If you are using routing to load the component, the route level has a providers array, where you can supply the providers based on the route.

    Angular Route Level Providers: A Comprehensive Guide

    const routes: Routes = [
      {
        path: 'admin',
        component: AdminComponent,
        providers: [{ provide: LoggingService, useClass: AdminLoggingService }]
      },
      ...
    

    But for the long run make the component standalone and move away from modular approach, because if the component dependencies and providers are encapsulated within the component. It becomes portable and can be used anywhere in your application without imports, exports, modular complexities.