I am trying to create a provider for a library module, that when added triggers my factory provider. However, when I do so, the provider never executes the console.log so I know it isn't running...
Here is the example service that I am trying to instantiate:
@Injectable()
export class ExampleService {
addOptions(...options: any[]) {
// Set options
}
}
When the module is created, I would like to create the service and add some default options to it, so I made a factory provider like this, as I don't want to add it at the component level, or for every instance of ExampleService. Just a one time thing for this module.
@NgModule({
declarations: [ExampleComponent],
exports: [ExampleComponent],
providers: [
{
provide: ExampleService,
multi: false,
useFactory: () => {
console.log('Creating Example Service...');
const service = new ExampleService();
service.addOptions('one', 'two', 'three');
return service;
}
}
],
})
export class LibraryModule {}
I also have a component that I want to also inject the service into, but not share the same instance as the one in the module, so I did this:
@Component({
providers: [ExampleService]
})
export class ExampleComponent {
constructor(private example: ExampleService) {}
}
When a team in my company uses our library, they would import it like this, and the options would automatically be set from the LibraryModule.
@NgModule({
imports: [LibraryModule]
})
export class App {}
Currently the one in the component gets created as intended, but the one in the module is not running the factory as I don't see output from the console.log. What Do I need to do to fix this? Or, is this not the correct way to do this?
Note: I am testing this using storybook, not sure if that makes a difference.
Which looks like this:
@Component({
standalone: true,
imports: [LibraryModule]
})
export class StorybookExample {
constructor(private example: ExampleService) {}
}
export default {
title: 'Test',
component: StorybookExample,
} as Meta<StorybookExample>;
Angular instantiates services when they are used, i.e. when it creates something that has this service injected. That is why just specifying the factory is not enough - the factory is called only when the service is used somewhere.
What you can do is inject the service into the module's constructor:
Please keep in mind that importing this module does not force a new instance creation - if the service was provided and injected somewhere else there could be already an instance that will be passed to the module constructor. That is why the presence of
provideIn: 'root'at the service decorator does not make any difference to how many instances are created.This behavior changes for lazy-loaded modules since they create their injector, so if this module is imported as part of a lazy-loaded module a new instance is created for a scope of the lazy-loaded module.
Playgroud
If this looks messy, it is. Not so long time ago Angular introduced Standalone API, which makes it possible to have an application without using NgModule at all. And there is a special DI token, that can be user for running initialization logic:
This can be specified for
bootstrapApplicationor for a route configuration.It may be the case that what is actually needed is a way to pass some configuration to the service. For such cases, it may be better to introduce an
InjectionTokenthat then can be used for providing the configurationAnd usage:
Finally, to make it less verbose, we could create a providers factory, in a similar way to some Angular's API, e.g. provideRouter: