Angular Dependency Injection

Injector

A service like HeroService is just a class in Angular until you register it with an Angular dependency injector.

An Angular injector is responsible for creating service instances and injecting them into classes like the HeroListComponent.

You rarely create an Angular injector yourself. Angular creates injectors for you as it executes the app, starting with the root injector that it creates during the bootstrap process.
Injector
Provider ————-> Service Instance

Angular doesn’t automatically know how you want to create instances of your services or the injector to create your service. You must configure it by specifying providers for every service.

Providers tell the injector how to create the service. Without a provider, the injector would not know that it is responsible for injecting the service nor be able to create the service.

There are many ways to register a service provider with an injector. This section shows the most common ways of configuring a provider for your services.

Register Provider:

@Injectable providers

@Injectable configure a provider for those services.

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

@Injectable({
providedIn: 'root',
})
export class HeroService {
constructor() { }
}

providedIn tells Angular that the root injector is responsible for creating an instance of the HeroService (by invoking its constructor) and making it available across the application.

In the following excerpt, the @Injectable decorator is used to configure a provider that will be available in any injector that includes the HeroModule.

import { Injectable } from '@angular/core';
import { HeroModule } from './hero.module';
import { HEROES } from './mock-heroes';

@Injectable({
// we declare that this service should be created
// by any injector that includes HeroModule.

providedIn: HeroModule,
})
export class HeroService {
getHeroes() { return HEROES; }
}

@NgModule providers

providers: [
UserService,
{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }
],

The first entry registers the UserService class (not shown) under the UserService injection token. The second registers a value (HERO_DI_CONFIG) under the APP_CONFIG injection token.

@Component providers

In addition to providing the service application-wide or within a particular @NgModule, services can also be provided in specific components. Services provided in component-level is only available within that component injector or in any of its child components.

import { Component } from '@angular/core';
import { HeroService } from './hero.service';

@Component({
selector: 'app-heroes',
providers: [ HeroService ],
template: `
<h2>Heroes</h2>
<app-hero-list></app-hero-list>
`
})
export class HeroesComponent { }

@Injectable, @NgModule or @Component

  • @Injectable
    Tree shakable

  • @NgModule.providers
    registered with the application’s root injector. Angular can inject the corresponding services in any class it creates. Once created, a service instance lives for the life of the app and Angular injects this one service instance in every class that needs it.

You’re likely to inject the UserService in many places throughout the app and will want to inject the same service instance every time. Providing the UserService with an Angular module is a good choice if an @Injectable provider is not an option..

To be precise, Angular module providers are registered with the root injector unless the module is lazy loaded. In this sample, all modules are eagerly loaded when the application starts, so all module providers are registered with the app’s root injector.

  • @Component.providers

A component’s providers (@Component.providers) are registered with each component instance’s own injector.

Angular can only inject the corresponding services in that component instance or one of its descendant component instances. Angular cannot inject the same service instance anywhere else.

Providers

  • The provide object literal
providers: [Logger]

shorthand expressioin using a provider literal with two properties:

[{ provide: Logger, useClasss: Logger }]
  • Alternative class providers
[{ provide: Logger, useClass: BetterLogger }]
@Injectable()
export class EvenBetterLogger extends Logger {
  constructor(private userService: UserService) { super(); }

  log(message: string) {
    let name = this.userService.user.name;
    super.log(`Message to ${name}: ${message}`);
  }
}
[ 
    UserService,
    { provide: Logger, useClass: EvenBetterLogger }
]
  • Aliased class providers
    [ NewLogger,
      // Not aliased! Creates two instances of `NewLogger`
      { provide: OldLogger, useClass: NewLogger}]
[ NewLogger,
  // Alias OldLogger w/ reference to NewLogger
  { provide: OldLogger, useExisting: NewLogger}]
  • Value providers
export function SilentLoggerFn() {}

const silentLogger = {
  logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'],
  log: SilentLoggerFn
};
[{ provide: Logger, useValue: silentLogger }]
  • Factory providers
    hero.service.ts
    constructor(
      private logger: Logger,
      private isAuthorized: boolean) { }
    
    getHeroes() {
      let auth = this.isAuthorized ? 'authorized ' : 'unauthorized';
      this.logger.log(`Getting heroes for ${auth} user.`);
      return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
    }

hero.service.provider.ts

let heroServiceFactory = (logger: Logger, userService: UserService) => {
  return new HeroService(logger, userService.user.isAuthorized);
};

export let heroServiceProvider =
  { provide: HeroService,
    useFactory: heroServiceFactory,
    deps: [Logger, UserService]
  };
  • Creating Tree-shakeable providers
    @Injectable({
      providedIn: 'root',
      useFactory: () => new Service('dependency'),
    })
    export class Service {
      constructor(private dep: string) {
      }
    }

Injection Token

import { InjectionToken } from '@angular/core';
// export const TOKEN = new InjectionToken('desc');

export const TOKEN = 
  new InjectionToken('desc', { providedIn: 'root', factory: () => new AppConfig(), })

const TOKEN = 
  new InjectionToken('tree-shakeable token', 
    { providedIn: 'root', factory: () => 
        new AppConfig(inject(Parameter1), inject(Paremeter2)), });
        
export const HERO_DI_CONFIG: AppConfig = {
  apiEndpoint: 'api.heroes.com',
  title: 'Dependency Injection'
};
constructor(@Inject(TOKEN));