kdocs
GitHub
Lang - Web
Lang - Web
  • Base
    • Css
    • Javascript
    • Typescript
      • New Project
  • Frameworks
    • Angular
      • Directives
      • Components
      • Templates
        • Bindings
        • Control Flow
        • Variables
      • Signals
      • Pipes
      • Services
        • Dependency Injection
      • Forms
        • Reactive Form
        • Template-Driven Form
      • Router
      • HTTP Client
      • Observables RxJS
      • Testing
        • Components
        • Directives
        • Pipes
        • Services
      • Optimization & Performance
      • Security
Powered by GitBook
On this page
  • About
  • Providing a Dependency
  • Injection Context
  • At application root level using providedIn
  • At component level
  • At application root level using ApplicationConfig
  • Hierarchical Injectors
  • Resolution Rules (Sequence)
  • Injector Hierarchies
  • Resolution Modifiers
  1. Frameworks
  2. Angular
  3. Services

Dependency Injection

PreviousServicesNextForms

Last updated 5 months ago

About

The two main roles of DI in Angular are:

  • Dependency consumer;

  • Dependency provider.

Angular facilitates the interaction between these two roles using an abstraction called Injector.

When a dependency is requested, the Injector checks its registry to see if there is an instance already available there.

If not, a new instance is created and stored in the registry.

Angular can create an application-wide injector known as root injector, during the bootstrap process.

Providing a Dependency

The DI system relies internally on a runtime context where the current injector is available. This means that injectors can only work when code is executed in such context.

This means in pratice that you cannot call inject() anywhere.

Knowing when you are in an injection context will allow you to use the inject().

Check the docs to see specific places that have injection context.

At application root level using providedIn

@Injectable({
    providedIn: 'root'
})

Angular creates a single, shared instance of the service, and injects it into any class you ask for it.

Using providedIn: 'root' when creating a service, allows injecting the service into all other classes.

You must not declare the service in @Component({ providers: [] }) array when using it, or it will create new instances.

If you want to call a Service A inside another Service B, then Service A will have to be providedIn: 'root'.

It enables Angular code optimizers to effectively remove services that are unused.

Known as Tree-Shaking.

Will use the root injector.

At component level

You can provide services at @Component level by using the providers: [] property in the component decorator.

@Component({
    providers: [SomeService]
})

The default behavior is for the injector to instantiate that class using new operator.

In this case, the service becomes available to all instances of this component and other components and directives used in the Template.

Declaring services like this causes the services to always be included in your application - even if the service is unused.

No Tree-Shaking.

Will use component-specific injector.

You can configure DI to associate a Service provider token with a different class or other values like.

The expanded provider configuration is an object literal with two properties:

  • The provide property holds the token that serves as the key for consuming the dependency value.

  • The second property is a provider definition object, which tells the injector how to create the dependency value. This can be one of the following:

    • useClass

    • useExisting

    • useFactory

    • useValue

useClass

[{ provide: Logger, useClass: TestLogger }]

Tells Angular DI to instanciate a provided class when a dependency is injected.

Useful to substitute/extend an alternative implementation for a common or default class.

Or even emulate the behavior of the real class in a test case.

If the alternative class providers have their own dependencies, specify both providers in the providers metadata property of the parent module or component.

[
    UserService, // dependency needed in `EvenBetterLogger`.
    { provide: Logger, useClass: EvenBetterLogger }
]

useExisting

Allows you to mapo one token to another. (Alias a token and reference any existing one)

In effect, the first token is an alias for the service associated with the second token, creating two ways to access the same service object.

[
    NewLogger,
    // Alias OldLogger with reference to NewLogger
    { provide: OldLogger, useExisting: NewLogger},
]

useFactory

Allows you to create a dependency object by calling a factory function. With this approach, you can create a dynamic value based on information available in the DI and elsewhere in the app.

useValue

Lets you associate a static value with a DI token.

Use this technique to provide runtime configuration constants such:

  • Website base addresses.

  • Feature flags.

Also use a value provider in a unit test to provide mock data in place of a production data service.

The following example show how to inject global data variables. (APP_DATA_TOKEN and APP_DATA don't have to be created in the same file)

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

interface AppData {
    ip: string;
    env: string;
    ...
}

// `InjectionToken` requires a parameter which is just a description for the Token
const APP_DATA_TOKEN = new InjectionToken<AppData>('app.data description');
const APP_DATA: AppData = { ip: '127.0.0.1', env: 'development' };

@Component({
    providers: [{ provide: APP_DATA_TOKEN, useValue: APP_DATA }]
})
export class Component {
    constructor(@Inject(APP_DATA_TOKEN) appData: AppData) {}
}

At application root level using ApplicationConfig

export const appConfig: ApplicationConfig = {
    providers: [{ provide: HeroService }],
};

The service will be available to all components, directives and pipes.

But the service will always be included in your application - even if the service is unused.

No Tree-Shaking.

Injectors have rules that you can leverage to achieve the desired visibility of injectables in your application.

There may be sections in your application that work completely independent, with its own local copies of the services and other dependencies that it needs.

With hierarchical DI, you can:

  • Isolate sections of the application and give them their own private dependencies.

  • Have parent components share certain dependencies with its child components only.

Resolution Rules (Sequence)

  1. When a Component declares a dependency, Angular tries to satisfy the dependency with its own ElementInjector.

  2. If the components lacks the provider, it passes the request up to its parent ElementInjector.

  3. This request keeps forwarding up until Angular finds an injector that can handle the request or runs out of ancestors ElementInjector.

  4. If it still could not find it, it goes back to the element where the request originated and looks in the EnvironmentInjector hierarchy.

  5. If it still does not find it, it throws an error.

If you have registered a provider for the same DI token at different levels, the first one Angular encounters is the one it uses to resolve the dependency.

Injector Hierarchies

Angular has two injector hierarchies:

EnvironmentInjector

Can be configured in one of two ways:

  • The @Injectable({ providedIn: root | plataform }) property to refer to root or plataform.

  • The ApplicationConfig providers array.

Tree-Shaking

Only available with @Injectable({ providedIn }) method.

Is an optimization tool, which removes services that your application isn't using, resulting in smaller bundle sizes.

ElementInjector

Angular creates ElementInjector hierarchies implicitly for each DOM element.

Providing a service in the @Component using providers: [] or viewProviders property configures an ElementInjector.

@Component({
    providers: []
    // Or
    viewProviders: []
})

Services provided like this are available at that component instance, and may be visible at child component/directives based on visibility rules. (Components and directive on the same element share an injector)

When the component instance is destroy, so is that service instance.

Angular's resolution behavior can be modified.

Use them when injecting services, in:

  • Component class constructor.

  • Or the inject() configuration.

@Optional()

Allows Angular to consider a service you inject to be optional.

So if it cannot be resolved at runtime, Angular resolves the service as null, rather than throwing an error.

export class Component {
    constructor(@Optional() private logger?: LoggerService) {}
}

@Self()

Use it so that Angular will only look at the ElementInjector for the current component or directive. (It will not go up to it's ancestors)

A good case for @Self() is to inject a service but only if it is available on the current host element.

It's good practice to use @Self() with @Optional().

In the following example, if LoggerService is provided inside Component class it will be availble, otherwise it will be null, because it won't check if the parent component of Component has it.

export class Component {
    constructor(@Self() @Optional() private logger?: LoggerService) {}
}

@SkipSelf()

It is the opposite of @Self(), so Angular starts looking in the parent ElementInjector, rather than in the current one.

@Self() and @SkipSelf() are exclusive from one another.

Also use @SkipSelf() with @Optional() to prevent an error if the value is null.

In the following example, any instance of LoggerService provided inside Component class will be ignored. Angular will try to get it from Component parent, and if the parent don't provide it then it will be null.

export class Component {
    constructor(@SkipSelf() @Optional() private logger?: LoggerService) {}
}

@Host()

Lets you designate a component as the last stop in the injector tree when searching for providers.

Even if there is a service instance further up the tree, Angular won't continue looking.

@Host() and @Self() are exclusive from one another.

Although there are specific places that have injection context, you can also create an injection context with .

Injection Context
runInInjectionContext
Specifying a provider Token
Check the example in the docs.
Hierarchical Injectors
providers vs viewProviders.
Resolution Modifiers
AngularAngular
dependency-injection
Logo