0

Angular 2+ registers providers in the following way:

// @NgModule decorator with its metadata
@NgModule({
  declarations: [...],
  imports: [...],
  providers: [<PROVIDERS GO HERE>],
  bootstrap: [...]
})
export class AppModule { }

I want to register application-scoped providers separately from this declaration site.

Specifically, I am using NSwag to generate service clients for my entire Web API and I want to dynamically add them all as providers. However, I'm not sure how to do that since @NgModule is an attribute applied to this AppModule class.

Is this possible?

MgSam
  • 12,139
  • 19
  • 64
  • 95
  • Does this help? http://www.damirscorner.com/blog/posts/20170526-DynamicDependencyInjectionInAngular.html – David Feb 06 '18 at 16:25

1 Answers1

1

Any DI provider needs to be included in the module at compile time.

Since Angular dependency injection works with Typescript type symbols / tokens, there's no Javascript functionality to accomplish the same task after it's compiled.

What you can do is dynamically add the provider at compile time, like so:

import { Load, SomeToken } from '../someplace';


@NgModule({
  declarations: [...],
  imports: [...],
  providers: [
    {
      provide: SomeToken,
      useValue: Load(someVariable)
  ],
  bootstrap: [...]
})
export class AppModule { }

and then implement the Load function and token elsewhere:

export const SomeToken = new OpaqueToken<any>('SomeToken');

export const Load = (someVariable) => {
  // logic to return an @Injectable here. Variable provided could be something like an environment variable, but it has to be exported and static
}

This approach of course has the limitation of needing to be known at compile time. The other approach is to either globally import all providers that are needed throughout the app regardless of circumstance and then lazy load components that have the appropriate provider injected for that circumstance (Angular will not initialize the provider until a component that utilizes it is initialized), or create a provider that in itself is able to perform the logic regardless of the dynamic criteria. An idea for that is to create another service that utilizes this service and resolves things based off of that dynamic criteria (i.e. you could have a method called GetLoginInfo on the first service and the second service would be able to resolve the correct API call for that method.)

If it's just API information you need (i.e. URLs), then you could possibly achieve the above by grabbing the URL information from a config.json file or API call, and inject those values into the service so the calls and tokens remain the same but use different values. See here for more information on how to accomplish that.

joh04667
  • 7,159
  • 27
  • 34
  • In the above code, would you still need a separate provider object for each provider? My goal here was to automate Angular service creation and registration whenever a new Web API is added, so the number of providers shouldn't be required to be known at the declaration site. – MgSam Feb 06 '18 at 16:43
  • Yes, you would need a separate decorated provider. The providers need to be decorated and injected at runtime. – joh04667 Feb 06 '18 at 16:58
  • Do the providers need to be different code, or just different instances of the same provider – joh04667 Feb 06 '18 at 16:59
  • Each provider is a different class. – MgSam Feb 07 '18 at 15:25