🅰️ Angular Q122 / 125

Explain Angular injector scopes.

AI-Powered Answer ✓ Answered

Angular's dependency injection system relies on injectors, which manage and provide instances of services. Understanding injector scopes is crucial for controlling service lifetimes, sharing instances across different parts of your application, and optimizing performance. This guide explains the various injector scopes and their implications.

Understanding Injectors and Providers

In Angular, an injector is a mechanism that can create and deliver dependencies. A provider is a recipe that configures an injector to create an instance of a dependency. When a component or service declares a dependency, Angular's injector system looks for a registered provider to satisfy that dependency.

Hierarchical Injector System

Angular applications have a hierarchical injector system. This means that every component and NgModule can have its own injector, forming a tree structure that mirrors the component tree. When a component requests a dependency, Angular traverses up this injector tree, starting from the component's own injector, until it finds a provider for that dependency.

Types of Scopes

The scope of a service defines where it is provided and how long its instance lives. Angular offers several ways to define a service's scope, primarily through the providedIn property of the @Injectable() decorator or by listing it in the providers array of an NgModule or Component.

Root Injector Scope (Singleton)

When a service is configured with providedIn: 'root', it becomes a singleton across the entire application. There will be only one instance of this service, created by the application's root injector, and shared by all parts of the application that inject it.

  • Provided once at the application's root.
  • Singleton instance shared across the entire application.
  • Automatically tree-shakable (removed if not used).
  • Ideal for global services like authentication, logging, or core data stores.

Platform Injector Scope

Less common for typical application services, providedIn: 'platform' registers the service at the platform level. The platform injector is a parent to all root injectors of applications running on the same page. This is relevant in advanced scenarios like multiple Angular applications bootstrapping on a single page.

  • Provided at the platform level, above the root injector.
  • Singleton instance shared across all Angular applications on a single page.
  • Rarely used for application-specific services.
  • Useful for services that should be truly unique across multiple independent Angular apps on the same page.

Any Injector Scope

Using providedIn: 'any' makes the service available to any lazy-loaded module. It ensures that if a module imports the service, it gets its own unique instance. If no lazy-loaded module imports it, it defaults to the root injector's behavior.

  • If used in an eagerly loaded module, behaves like providedIn: 'root'.
  • If used in a lazy-loaded module, a new instance is created for that lazy-loaded module.
  • Useful when you want a singleton for each distinct lazy-loaded context.
  • Can lead to multiple instances of the service across the application.

NgModule Injector Scope

When a service is listed in the providers array of an @NgModule (other than the root AppModule), its instance is scoped to that specific NgModule. If the NgModule is eagerly loaded, the service becomes a singleton within that NgModule's scope. If the NgModule is lazy-loaded, a new injector is created for it, and a new instance of the service is provided to that lazy-loaded module.

  • Service instance is scoped to the NgModule that provides it.
  • For eagerly loaded NgModules, behaves like a singleton within that module's scope.
  • For lazy-loaded NgModules, each lazy-loaded instance gets its own service instance.
  • Common for feature-specific services that should not be global.

Component Injector Scope

Providing a service directly in the providers array of a @Component decorator scopes the service to that component and its child components. Each new instance of the component will get its own unique instance of the service. This is useful for services that manage component-specific state or interact with the DOM directly.

  • Each instance of the component that provides the service gets its own service instance.
  • Service instance is available to the component itself and its direct child components in the template.
  • Useful for transient, component-specific state or resources.
  • Not tree-shakable automatically.
typescript
@Component({
  selector: 'app-my-component',
  template: '<p>Component content</p>',
  providers: [MyService] // Scoped to this component instance
})
export class MyComponent {
  constructor(private myService: MyService) {}
}

How Resolution Works

When a component or service requests a dependency, Angular starts looking for a provider in the requesting component's own injector. If not found, it moves up to its parent component's injector, then to its containing NgModule's injector, and so on, until it reaches the root injector, and finally the platform injector. The first provider found is used to create and return the service instance.

Practical Implications

  • Use providedIn: 'root' for application-wide singletons (e.g., AuthService, HttpClient).
  • Use providedIn: 'any' carefully when you want separate instances per lazy-loaded module context, or global if not lazy-loaded.
  • Use providers in an eagerly loaded @NgModule for services specific to that module's features.
  • Use providers in a lazy-loaded @NgModule for services that should be singletons only within that lazy-loaded module.
  • Use providers in a @Component for services that need a unique instance per component instance, or for managing component-specific state.

Understanding Angular's injector scopes is fundamental for designing robust, scalable, and efficient applications. Correctly scoping services helps manage state, prevent memory leaks, and optimize the application's bundle size by allowing for effective tree-shaking.