This is a fresh reply that's up to date with Angular 9+ (post-Ivy) and should be correct in 2022.
TL;DR: It's all about controlling how many instances of your service will be created, and where should they be available after being created.
Terminology:
Injectable - any class decorated with @Injectable
, for example a service.
injector - an Angular class that is capable of providing Injectables to classes below it. (This includes all components and modules.)
injector scope/level - the scope of all class instances that live "below" a specific injector.
injector hierarchy - a proritized tree of injector scopes, organized in platform -> root -> module -> component
order.
Injectable is provided - an instance of the Injectable will be given to classes below this specific injector level, whenever they request it.
Injectable is injected - a class constructor has requested to be given some instance of the service, so Angular will try to give it the nearest instance that can be found in the injector hierarchy".
tree-shaking - an optimization that happens automatically thanks to the Angular compiler. When it detects that some code is not being used, that code is removed from the final compilation of the app (or compilation of a given lazy-loaded module).
Other terms that you should already know: class, instance, module, component, lazy/eagerly loaded modules.
Q: What exactly does providedIn
do?
It's a setting that determines which injectors should provide your Injectable.
Let's assume we create an Injectable called MyService
, and go through what all the options do.
providedIn: Type<any> | 'root' | 'platform' | 'any' | null
providedIn: 'platform'
Angular will create and provide a single shared instance of MyService
to all Angular applications on the page. (This is only relevant in advanced use cases, if you use a micro-frontends architecture.)
providedIn: 'root'
Angular will create a single shared instance of MyService
and provide it to all classes in the application.
providedIn: 'any'
// DEPRECATED since Angular v15
Angular will create a single shared instance of MyService
and provide it to all classes in eagerly-loaded modules.
However, each lazy-loaded module will provide its own, new, separate instance of MyService
(that will then be only available in classes inside that module).
providedIn: MyModule
// DEPRECATED since Angular v15
Angular will only create an instance of MyService
if MyModule
is loaded.
If MyModule
is eagerly loaded, that instance will be available to all other eagerly loaded modules from now on. (Note that this is effectively identical to providedIn: 'root'
.)
However, if MyModule
is lazy loaded, then this instance will be provided only for classes inside MyModule
, whenever it happens to be loaded.
providedIn: MyComponent
// DEPRECATED since Angular v15
Angular will create a new, fresh instance of MyService
whenever MyComponent
is instantiated.
This MyService
instance will only be provided for descendants of that specific MyComponent
instance, and will be destroyed as soon as the component instance is destroyed. (Note that means that a new MyService
will be created for each time this component is rendered.)
providedIn: null
MyService
can only ever be instantiated by being added to providers
array in a specific module or component.
Whenever that module/component is instantiated, it will create a new instance of MyService
, and provide it only in its specific scope. (See full description of providers
array below.)
Q: What does providers
array do?
Any injector can be set up with a providers
array:
@NgModule({
providers: [MyService],
})
@Component({
providers: [MyService],
})
All Injectables can be added to a providers
array, regardless of their providedIn
setting.
Adding MyService
to providers
array will cause the injector to create and provide an entirely separate instance of it to classes in its scope. (The scope is exactly the same as described in providedIn: MyModule
and providedIn: MyComponent
examples above.)
This method of providing does not support tree-shaking. The service will always be included in the compilation, even if noone uses it. (See tree-shaking notes below.)
Q: Why would I use providers
array and providedIn
simultaneously?
An example use case might be if MyService
is providedIn: 'root'
and already has a shared instance, but you want your module/component to have its own, separate instance.
Additional notes:
Q: How do providedIn
/providers
settings affect tree-shaking?
An Injectable configured with providedIn
will be tree-shaken if it is not injected by any (eagerly or lazy loaded) class in its assigned injector scope.
However, an Injectable assigned to a providers
array in some module/component will never be tree-shaken, even if it is not injected anywhere.
To make tree-shaking most effective, you should aim to always use providedIn
over providers
array.
Q: Why would I use providedIn: 'root'
if I think using providers
array in AppModule
looks cleaner?
As explained above, the main difference is that between the two methods, providedIn
supports tree-shaking, and providers
array does not.
Other than that, it's an architectural decision: if we set providedIn
directly in the Injectable file, the Injectable owns the decision of how it should be provided. Distinguishing who owns the contract has significant implications for large apps and teams that have to cooperate between hundreds of modules.
Q: Is there a difference between setting providers: [MyService]
array in AppComponent
or AppModule
?
Yes. MyService
will be provided in lazy-loaded modules only if you do it in AppModule
, not AppComponent
.
(That's because lazy-loaded modules rely on Router
, which gets imported in AppModule
, one injector scope higher than AppComponent
.)