Angular provider scopes explained

How Can We Help?

Back
Print
Reading Time: 8 minutes

Services are one of the building blocks of Angular you will see in every Angular application. Their main purpose is to increase modularity and reusability or in other words to separate a component’s view-related logic from any other kind of processing. Usually, components are delegating various tasks to services like fetching data from a server, validating user input, logging, etc. By defining these kinds of tasks in injectable services, we are making them available to any component.

One service is just a TypeScript class that has @Injectable decorator attached. This decorator makes service available to Angular’s Dependency Injection (DI) mechanism which is built into the Angular framework.

import { Injectable } from '@angular/core';

@Injectable()
export class ExampleService {

  constructor() { }
}

When components like consumers reference one service, Angular DI provides an instance from that service.

import { Component } from '@angular/core';
import {ExampleService} from "./services/example.service";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  // Reference to ExampleService
  constructor(private exampleService: ExampleService) {
  }
}
Source: https://memegenerator.net/instance/55044567/futurama-fry-wait-a-second

How the Angular DI knows how many instances of one service to provide? Is it always just one? How can we tell Angular how many instances to create?

The answer is: provider scopes.

Before we dive into provider scopes and ways of providing service in Angular, let’s define module injector (ModuleInjector) hierarchies.

Visualization of module injector hierarchy

On the image above we can notice the ModuleInjector hierarchy:

  • Platform Module injector – the top module injector, usually used for special things like DomSanitizer
  • Root Module injector – main injector and it is the place for all eagerly loaded module providers
  • Lazy Modules injectors – all lazy-loaded modules are creating separate child injector from the root injector

These module injectors are used from Angular Dependency Injection during creating instances of services.

Now it’s time to dive into provider scopes and how everything works in practice.

There are five provider scopes in Angular:

  • Module scope
  • Component scope
  • Root scope
  • Platform scope
  • ‘any’ injector

Module scope provider

When the service is registered in providers array in one @NgModule, we say its service in the module.

...
import {ExampleService} from "./services/example.service";

@NgModule({
  ...
  providers: [ExampleService]
})
export class AppModule { }

There are two different scenarios to cover when one service is provided in Angular Module:

  • Service is provided in the root module or in an eagerly loaded module – The Angular DI mechanism will use a Root Module Injector and will create one service instance which will be shared between the root module and eagerly loaded modules (all providers from all imported modules are merged into the root injector)
  • Service is provided in a lazy-loaded module – The Angular DI mechanism will use Lazy Module (child injector) and will create one instance for every different lazy-loaded module where it is provided

Component scope provider

One service can be provided also in @Component, registering should be also made in the providers’ array. This means if the service is provided and referenced in a particular component, then for that component and all their children will be created one separate instance of the service, not depending on other providers. This is applied per instance of the component, which in practice means if that component has 3 instances, 3 instances of the service will be created.

import { Component } from '@angular/core';
import {ExampleService} from "./services/example.service";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  providers: [ExampleService]
})
export class AppComponent {
  // Reference to ExampleService
  constructor(private exampleService: ExampleService) {
  }
}

The component scope provider is the most specific and Angular starts searching for providers at the first from component and then goes up by hierarchy until it finds it or to the last Platform Module Injector.

Root scope provider

Starting from Angular version 6 it is introduced the possibility to provide a service without registering in @NgModule or @Component, instead of that the providing info should be placed inside @Injectable decorator with providedIn: ‘root’ option.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ExampleService {

  constructor() { }
}

With this kind of providing services, they are provided in Root Module Injector (typically called AppModule) and Angular DI creates a single, shared instance of the service and injects the same instance in every reference. Actually, the service acts like Singleton (Singleton pattern). This is valid if it is referenced in lazy and non-lazy modules too, they all receive the same instance.

Visualization where services with providedIn: ‘root’ option will be provided

The other benefit of this kind of registering is the tree-shaking option for optimizing the app bundle size, if it turns out that the service is not referenced anywhere, Angular DI does not register the service into the root injector at all.

Platform scope provider

Starting from Angular 9, one of the two new ways of providing services is providedIn: ‘platform’ option.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'platform'
})
export class ExampleService {

  constructor() { }
}

When one service is defined with providedIn: ‘platform’ it means that the service will be provided in Platform Module Injector and it acts like Singleton for all applications, also all lazy modules will use the instance from the platform.

Visualization where services with providedIn: ‘platform’ option will be provided

This kind of provider looks similar to ‘root’, but the key difference is only when we are running multiple Angular applications in the same window. Every application that runs in the window will have a separate Root Module Injector, but they will both share the Platform Module Injector. In practice this means, we are sharing the services over the application boundaries.

‘any’ Injector

This kind of providing services is the second newly added option starting from Angular version 9, the syntax is providedIn: ‘any’.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'any'
})
export class ExampleService {

  constructor() { }
}

Providing a service like this means every service will be provided in every module where it is used. So one service can have more than one instance, depending on its usages. The rule here is that every lazy-loaded module will have its own instance, and all eagerly loaded modules will share one instance provided by the Root Module Injector.

Visualization where services with providedIn: ‘any’ option will be provided

Summary

When Angular finds one service referenced in some component, in order to create an instance or use one of the created ones Angular starts looking for a provider in the following order:

  1. Component providers
  2. Module providers
  3. Root providers
  4. Looks for providedIn: ‘any’ injector and decides the number of instances
  5. Platform providers

Component scope provider option mostly will be used in some edge cases for example when we have dynamically created tabs, all have the same component like root, but every tab should have its own state, then we added one service in providers list of the tab root component and for every tab, we will have a different instance.

Module scope provider option was the default option for providing services before Angular 6, nowadays it is also very much used when we want one instance per lazy-loaded module, but as we exposed previously starting from Angular 9 this can be achieved with providedIn: ‘any’ option.

providedIn: ‘root’ this option is the default option when we create a new injectable service in Angular and most of the time we will need tree-shakable singleton services within an application.

providedIn: ‘any’ this is a very helpful option if we want to make sure that one service is singleton within module boundaries.

providedIn: ‘platform’ this option will be mostly used when we want singleton service within several Angular applications which run in the same window.

Table of Contents
0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *