w3resource

Dependency Injection in Action

The consumer of an injected service doesn't need to know how to create that service. It's the job of the DI framework to create and cache dependencies. The consumer just needs to let the DI framework know which dependencies it needs.

The Angular CLI command ng new <project_name> gets you started. When you run this command, the CLI installs the necessary Angular npm packages and other dependencies in a new workspace, with a root folder named project_name. It also creates the following workspace and starter project files:

The following example shows that AppComponent declares its dependence on LoggerService and UserContext.

constructor(logger: LoggerService, public userContext: UserContextService) {
  userContext.loadUser(this.userId);
  logger.logInfo('AppComponent initialized');
}

Live Demo:

It is just a code snippet explaining a particular concept and may not have any output

See the Pen src/app/app.component.ts by w3resource (@w3resource) on CodePen.


UserContext in turn depends on both LoggerService and UserService, another service that gathers information about a particular user.

@Injectable({
  providedIn: 'root'
})
export class UserContextService {
  constructor(private userService: UserService, private loggerService: LoggerService) {
  }
}

Live Demo:

It is just a code snippet explaining a particular concept and may not have any output

See the Pen user-context.service.ts (injection) by w3resource (@w3resource) on CodePen.


When Angular creates AppComponent, the DI framework creates an instance of LoggerService and starts to create UserContextService. UserContextService also needs LoggerService, which the framework already has, so the framework can provide the same instance. UserContextService also needs UserService, which the framework has yet to create. UserService has no further dependencies, so the framework can simply use new to instantiate the class and provide the instance to the UserContextService constructor.

The parent AppComponent doesn't need to know about the dependencies of dependencies. Declare what's needed in the constructor (in this case LoggerService and UserContextService) and the framework resolves the nested dependencies.

Limit service scope to a component subtree

An Angular application has multiple injectors, arranged in a tree hierarchy that parallels the component tree. Each injector creates a singleton instance of a dependency. That same instance is injected wherever that injector provides that service. A particular service can be provided and created at any level of the injector hierarchy, which means that there can be multiple instances of a service if it is provided by multiple injectors.

Dependencies provided by the root injector can be injected into any component anywhere in the application. In some cases, you might want to restrict service availability to a particular region of the application. For instance, you might want to let users explicitly opt in to use a service, rather than letting the root injector provide it automatically.

You can limit the scope of an injected service to a branch of the application hierarchy by providing that service at the sub-root component for that branch. This example shows how to make a different instance of HeroService available to HeroesBaseComponent by adding it to the providers array of the @Component() decorator of the sub-component.

@Component({
  selector: 'app-unsorted-heroes',
  template: `<div *ngFor="let hero of heroes">{{hero.name}}</div>`,
  providers: [HeroService]
})
export class HeroesBaseComponent implements OnInit {
  constructor(private heroService: HeroService) { }
}

Live Demo:

It is just a code snippet explaining a particular concept and may not have any output

See the Pen src/app/sorted-heroes.component.ts (HeroesBaseComponent excerpt) by w3resource (@w3resource) on CodePen.


When Angular creates HeroesBaseComponent, it also creates a new instance of HeroService that is visible only to that component and its children, if any.

You could also provide HeroService to a different component elsewhere in the application. That would result in a different instance of the service, living in a different injector.

Examples of such scoped HeroService singletons appear throughout the accompanying sample code, including HeroBiosComponent, HeroOfTheMonthComponent, and HeroesBaseComponent. Each of these components has its own HeroService instance managing its own independent collection of heroes.

Multiple service instances (sandboxing)

Sometimes you want multiple instances of a service at the same level of the component hierarchy.

A good example is a service that holds state for its companion component instance. You need a separate instance of the service for each component. Each service has its own work-state, isolated from the service-and-state of a different component. This is called sandboxing because each service and component instance has its own sandbox to play in.

In this example, HeroBiosComponent presents three instances of HeroBioComponent.

@Component({
  selector: 'app-hero-bios',
  template: `
    <app-hero-bio [heroId]="1"></app-hero-bio>
    <app-hero-bio [heroId]="2"></app-hero-bio>
    <app-hero-bio [heroId]="3"></app-hero-bio>`,
  providers: [HeroService]
})
export class HeroBiosComponent {
}

Live Demo:

It is just a code snippet explaining a particular concept and may not have any output

See the Pen rgjRNy by w3resource (@w3resource) on CodePen.


Each HeroBioComponent can edit a single hero's biography. HeroBioComponent relies on HeroCacheService to fetch, cache, and perform other persistence operations on that hero.

@Injectable()
export class HeroCacheService {
  hero: Hero;
  constructor(private heroService: HeroService) {}

  fetchCachedHero(id: number) {
    if (!this.hero) {
      this.hero = this.heroService.getHeroById(id);
    }
    return this.hero;
  }
}

Live Demo:

It is just a code snippet explaining a particular concept and may not have any output

See the Pen JqEzdp by w3resource (@w3resource) on CodePen.


Three instances of HeroBioComponent can't share the same instance of HeroCacheService, as they'd be competing with each other to determine which hero to cache.

Instead, each HeroBioComponent gets its own HeroCacheService instance by listing HeroCacheService in its metadata providers array.

@Component({
  selector: 'app-hero-bio',
  template: `
    <h4>{{hero.name}}</h4>
    <ng-content></ng-content>
    <textarea cols="25" [(ngModel)]="hero.description"></textarea>`,
  providers: [HeroCacheService]
})

export class HeroBioComponent implements OnInit  {
  @Input() heroId: number;

  constructor(private heroCache: HeroCacheService) { }

  ngOnInit() { this.heroCache.fetchCachedHero(this.heroId); }

  get hero() { return this.heroCache.hero; }
}

Live Demo:

It is just a code snippet explaining a particular concept and may not have any output

See the Pen MdJxyJ by w3resource (@w3resource) on CodePen.


The parent HeroBiosComponent binds a value to heroId. ngOnInit passes that ID to the service, which fetches and caches the hero. The getter for the hero property pulls the cached hero from the service. The template displays this data-bound property.

Qualify dependency lookup with parameter decorators

When a class requires a dependency, that dependency is added to the constructor as a parameter. When Angular needs to instantiate the class, it calls upon the DI framework to supply the dependency. By default, the DI framework searches for a provider in the injector hierarchy, starting at the component's local injector of the component, and if necessary, bubbling up through the injector tree until it reaches the root injector.

  • The first injector configured with a provider supplies the dependency (a service instance or value) to the constructor.
  • If no provider is found in the root injector, the DI framework throws an error.

There are a number of options for modifying the default search behavior, using parameter decorators on the service-valued parameters of a class constructor.

Make a dependency @Optional and limit search with @Hostlink

Dependencies can be registered at any level in the component hierarchy. When a component requests a dependency, Angular starts with that component's injector and walks up to the injector tree until it finds the first suitable provider. Angular throws an error if it can't find the dependency during that walk.

In some cases, you need to limit the search or accommodate a missing dependency. You can modify Angular's search behavior with the @Host and @Optional qualifying decorators on a service-valued parameter of the component's constructor.

  • The @Optional property decorator tells Angular to return null when it can't find the dependency.
  • The @Host property decorator stops the upward search at the host component. The host component is typically the component requesting the dependency. However, when this component is projected into a parent component, that parent component becomes the host. The following example covers this second case.

These decorators can be used individually or together, as shown in the example.

@Component({
  selector: 'app-hero-contact',
  template: `
  <div>Phone #: {{phoneNumber}}
  <span *ngIf="hasLogger">!!!</span></div>`
})
export class HeroContactComponent {

  hasLogger = false;

  constructor(
      @Host() // limit to the host component's instance of the HeroCacheService
      private heroCache: HeroCacheService,

      @Host()     // limit search for logger; hides the application-wide logger
      @Optional() // ok if the logger doesn't exist
      private loggerService: LoggerService
  ) {
    if (loggerService) {
      this.hasLogger = true;
      loggerService.logInfo('HeroContactComponent can log!');
    }
  }

  get phoneNumber() { return this.heroCache.hero.phone; }

}

Live Demo:

It is just a code snippet explaining a particular concept and may not have any output

See the Pen XwpGja by w3resource (@w3resource) on CodePen.


The @Host() function decorating the heroCache constructor property ensures that you get a reference to the cache service from the parent HeroBioComponent. Angular throws an error if the parent lacks that service, even if a component higher in the component tree includes it.

A second @Host() function decorates the loggerService constructor property. The only LoggerService instance in the app is provided at the AppComponent level. The host HeroBioComponent doesn't have its own LoggerService provider.

Angular throws an error if you haven't also decorated the property with @Optional(). When the property is marked as optional, Angular sets loggerService to null and the rest of the component adapts.

If you comment out the @Host() decorator, Angular walks up the injector ancestor tree until it finds the logger at the AppComponent level. The logger logic kicks in and the hero display updates with the "!!!" marker to indicate that the logger was found.

If you restore the @Host() decorator and comment out @Optional, the app throws an exception when it cannot find the required logger at the host component level.

Previous: Workspace and project file structure
Next: Angular Workspace Configuration



Become a Patron!

Follow us on Facebook and Twitter for latest update.

It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.

https://www.w3resource.com/angular/dependency-injection-in-action.php