53

I am trying to pass config data into a custom library in Angular.

In the users application, they will pass some config data to my library using forRoot

// Import custom library
import { SampleModule, SampleService } from 'custom-library';
...

// User provides their config
const CustomConfig = {
  url: 'some_value',
  key: 'some_value',
  secret: 'some_value',
  API: 'some_value'
  version: 'some_value'
};

@NgModule({
  declarations: [...],
  imports: [
    // User config passed in here
    SampleModule.forRoot(CustomConfig),
    ...
  ],
  providers: [
    SampleService
  ]
})
export class AppModule {}

In my custom library, specifically the index.ts, I can access the config data:

import { NgModule, ModuleWithProviders } from '@angular/core';
import { SampleService } from './src/sample.service';
...

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [...],
  exports: [...]
})
export class SampleModule {
  static forRoot(config: CustomConfig): ModuleWithProviders {
    // User config get logged here
    console.log(config);
    return {
      ngModule: SampleModule,
      providers: [SampleService]
    };
  }
}

My question is how do I make the config data available in the custom library's SampleService

Currently SampleService contains the following:

@Injectable()
export class SampleService {

  foo: any;

  constructor() {
    this.foo = ThirdParyAPI(/* I need the config object here */);
  }

  Fetch(itemType:string): Promise<any> {
    return this.foo.get(itemType);
  } 
}

I have read through the docs on Providers, however the forRoot example is quite minimal and doesn't seem to cover my use case.

Michael Doye
  • 8,063
  • 5
  • 40
  • 56
  • were you able to pass map to your custom config? something like this myMap: Map; I am trying to pass a map but it doesn't work if I switch on AOT (ng serve --AOT) – user911 Aug 23 '18 at 07:25

1 Answers1

96

You are almost there, simply provide both SampleService and config in your module like below:

export class SampleModule {
  static forRoot(config: CustomConfig): ModuleWithProviders<SampleModule> {
    // User config get logged here
    console.log(config);
    return {
      ngModule: SampleModule,
      providers: [SampleService, {provide: 'config', useValue: config}]
    };
  }
}
@Injectable()
export class SampleService {

  foo: string;

  constructor(@Inject('config') private config:CustomConfig) {
    this.foo = ThirdParyAPI( config );
  }
}

Update:

Since Angular 7 ModuleWithProviders is generic, so it needs ModuleWithProviders<SampleService>

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 3
    Great answer! I usually use it but instead of `string` i prefer using `OpaqueToken(InjectionToken)` – yurzui Apr 08 '17 at 11:29
  • 2
    Thanks :). I find `OpaqueToken` too cumbersome to explain, but you are right of course. It's the preferred way. It's actually now `InjectionToken` https://angular.io/docs/ts/latest/api/core/index/InjectionToken-class.html – Günter Zöchbauer Apr 08 '17 at 11:32
  • 2
    _Not_ using `InjectionToken` actually illustrates the flexibility of the injector. It also demonstrate usages of the `@Inject(dep)` pattern which is highly useful. – Aluan Haddad Apr 08 '17 at 11:43
  • This works well. Very much appreciated. Will be looking into `InjectionToken` as well – Michael Doye Apr 08 '17 at 11:44
  • I guess that you don't need the `Injectable` decorator in the class if you have only one dependency and you are using the `Inject` decorator to instruct Angular what to inject. – Jaime Aug 09 '17 at 01:35
  • @Jaime I don't know. It's considered good practice to just always add `@Injectable()` if the class is used as a service. – Günter Zöchbauer Aug 09 '17 at 03:39
  • @Jaime exactly. It's surprising when adding a parameter to a constructor breaks your application. You make life easier for yourself and your coworkers if you just aleays add it ;-) – Günter Zöchbauer Aug 10 '17 at 05:22
  • Should the providers line be this instead: `(providers: [SampleService, {provide: 'config', useValue: config}]` – Niko Konstantakos Oct 12 '17 at 16:53
  • @NikoKonstantakos, I think it's correct as I have it in my answer. Why do you think it is not correct? – Günter Zöchbauer Oct 12 '17 at 18:49
  • Because you're specifying the class name rather than the actual instance of the object, right? – Niko Konstantakos Oct 13 '17 at 19:32
  • In my code the value `const CustomConfig = { ... }` would be injected, your code example should cause an error, because there is no `config` in scope. Perhaps in my code the provider should be added to `AppModule` instead of `SampleModule` to make it more clear. – Günter Zöchbauer Oct 13 '17 at 19:38
  • @GünterZöchbauer `Injectable` is such an awful name and `@Injectable()` is such an ugly syntax. It should be `inject` or `injected` and the syntax should be `@injected`. – Aluan Haddad Nov 09 '17 at 07:21
  • 1
    @AluanHaddad that's just bikeshedding :D and I don't think your examples are better. `@Injectable()` marks a class to make it injectable, so the name isn't that bad. – Günter Zöchbauer Nov 09 '17 at 09:12
  • @GünterZöchbauer that's not what `@Injectable()` does. It marks a class such that _its_ dependencies can be injected. What makes the class _itself_ injectable is listing it in the `providers` config array of an `NgModule`. Granted, I like to bikeshed ;), but that's not the case here. I also don't like the parens. Why is Injectable a _decorator factory_ and not a _decorator_? (this last point is bikeshedding :D) – Aluan Haddad Dec 18 '17 at 22:25
  • @GünterZöchbauer PS: I'm not sure if it works that way in Angular Dart. If I ever write an Angular app again, I will use Dart since I can't use Angular with JavaScript anymore. – Aluan Haddad Dec 18 '17 at 22:37
  • In Dart there is no `forRoot` and no modules, at lrast not in the sense of Angular TS modules. – Günter Zöchbauer Dec 19 '17 at 07:00
  • @GünterZöchbauer sounds like a better design. – Aluan Haddad Dec 19 '17 at 17:16
  • With this configuration, when building AOT I'm getting `NullInjectorProvider` error, see here: https://stackoverflow.com/questions/50881070/aot-ngmodule-provider-with-dependencies-nullinjectorerror – Miquel Jun 15 '18 at 18:37
  • 3
    What if have nested modules, how can I pass the config? I mean I can't use in AppModule: `imports: [ModuleA.forRoot(parentConfig)]` and then in ModuleA have `imports: [ModuleB.forRoot(parentConfig.childConfig)]`. – bersling Jun 29 '18 at 12:34
  • This requirement sounds a bit odd to me. Could not a service in `ModuleA` that is injected in services in `ModuleB` provide the information if `ModuleB` needs it? – Günter Zöchbauer Jun 29 '18 at 12:53
  • Why should we use `static` keyword for `forRoot` definication? – Harish Kommuri Aug 01 '18 at 10:22
  • @Harish because otherwise you would first need to create an instance of `SampleModule` to be able to pass the function reference and with `static` this is not necessary. – Günter Zöchbauer Aug 01 '18 at 11:24
  • Hello @GünterZöchbauer, would above cause `config` to be available in the entire app (i.e. root injector) or will `config` only be provided module-wide? – Top-Master Jan 29 '19 at 11:43
  • 1
    That's the purpose of `forRoot`. If you don't want that then provide it in `providers` of a module or component. – Günter Zöchbauer Jan 29 '19 at 12:16
  • 1
    @Top-Master thanks for the intention but that is the sole purpose of `forRoot` and I don't think it's necessary to mention that in my answer explicitly. – Günter Zöchbauer Jan 29 '19 at 13:12
  • I remember that there were seveal such questions some years ago when Angular appeared. I think you need to load that either outside Angular before Angular starts or depending on your concrete use case using `APP_INITIALIZER` might be an easier option. – Günter Zöchbauer Nov 06 '19 at 04:03
  • 1
    ModuleWithProviders expects generic of type module, `ModuleWithProviders` – hyperdrive May 12 '20 at 15:05
  • Does this still work for later releases of Angular11? When I put my LibraryModule.forRoot(libConfig) - the compiler wants a type insertion, then argument libConfig is not assignable to routes. – Voltan Jul 07 '21 at 03:59