Hierarchical Dependency Injection In Angular

Hierarchical Dependency Injection In Angular

In this article, We will learn about the how Angular dependency injection works. Angular creates a dependency injection framework that is hierarchical. It produces a tree of Injectors that is hierarchical. Angular Providers is duplicated for each Injector. The Angular dependency injection framework is built around these two components. The injector tree will be created by Angular. The injector's method for resolving the dependency.

Injector

For each Component, Directive, or other object loaded by Angular, an Injector instance is created. It also builds injector instances for the Root Module as well as each lazy loaded module. However, eagerly loaded modules share the Root Module's injector rather than having their own.

Injector Tree

Angular creates two injector trees instead of one. Element Injector Tree& Module Injector Tree

Modules (@NgModule) are injected through the Module Injector tree. For the Root Module and all Lazy Loaded Modules.

Components and Directives are DOM Elements, and the Element Injector tree is for them.

Module Injector Tree

For the services to be delivered at Module Levels, Angular creates the ModuleInjector.

The Module level services are registered in two ways.

  • Using the @NgModule's Providers Metadata ()
  • Using the @Injectable() Decorator in the service itself with providedIn: root

When the application starts, Angular creates the Module Injector tree.

Angular produces a Null Injector instance at the top of the Module Injector hierarchy. Unless we use the Optional decorator to decorate the dependency, the Null Injector always raises an error.

Angular produces a PlatformInjector object under Null Injector. Platform Injector typically comes with built-in suppliers such as DomSanitize and others.

Angular generates the Root Module Injector under the Platform Injector. It's set up with providers from the places listed below.

@NgModule of Root Module metadata provider.

Provides @NgModule metadata for every imported Modules (i.e. all eagerly loaded modules).

All services with the value root or any in their @Injectable() decorator have providedIn metadata. It contains services from both eagerly and slowly loaded modules.

Angular creates an Injector object for each Lazy loaded Module under Root Module Injector. They are only created when Angular loads them. They're set up with suppliers from the places listed below.

Provides metadata for the @NgModule of the Lazy loaded Module.

All services that have a value of any in their @Injectable() decorator have providedIn metadata.

Element Injector Tree

For services to be offered at the element level, such as Components and Directives, Angular develops the Element Injector tree.

When the application is launched, Angular constructs the Element Injector tree.

The Root Component's Injector instance becomes the Element Injector tree's root Injector. It gets the Providers from the Root Component's provider property.

Every element (i.e. Components or Directives) we build has a parent, which is the Root Component. Each of the elements can have children, resulting in an element tree. For each of these elements, Angular creates an Injector, forming a tree of Injectors.

The list of Providers is obtained by each Injector via the @Directive() or @Component() methods. Angular produces an empty Injector if the Providers array is empty.

Element Injector Tree Hierarchy In Angular


Dependency Resolution

The dependence is resolved in two stages by Angular.

  • First, use the Element Injector and its parents to solve it.
  • If the element isn't discovered in the Element Injector, check the Module Injector and its parents.

The token is used by the components to request dependencies in the constructor.

In the Element Injector tree, the search begins with the Injector connected with the component. It looks for dependencies in its Providers array using the token. Injector tries to see if the service instance already exists if it finds the provider. If the component already exists, it is injected into it; otherwise, a new instance is created. The component is then injected with it.

The request is passed to the parent by the injector. The Element's Injector

The root component owns the topmost Injector in the Element Injector tree. It does not throw an exception if the dependence is not discovered; instead, it returns.

The focus now switches to Module Injector Tree.

The Injector associated with the module to which the element belongs is where the search begins. The search for Root Modules and Eagerly Loaded Modules begins with the Root Module Injector. The resolutions for components from lazy loaded modules begin with the Module Injector associated with the Lazy Loaded Module.

The request will continue until it reaches Null Injector, which is the topmost injector in the Module Injector tree. There are no providers in the Null Injector. Its function is to throw the No provider for Service error. However, if we adorn the

Dependency Resolution in Picture

The graphic below illustrates how Angular resolves the dependency for a component in a Root Module.

The component requesting dependence (GrChildComponentA) is in the RootModule. The search begins with the Injector linked with the GrChildComponentA in the Element Injector hierarchy. The search progresses through the Injector tree until it reaches the Root Injector, which is part of the RootComponent. If the service cannot be located, the search moves to the Module Injector Hierarchy, starting with the Root Module (because GrChildComponentA belongs to Root Module).

Dependency Resolution In Angular

In a lazy loaded module, the following picture displays how Angular resolves the dependency for component.

The LazyModuleB contains the component (LazyBChildComponent) that requests dependence. As you can see, when the Element Injector tree's Injectors fail to offer the requested service, the request is sent on to the Module Injector Hierarchy. The search should begin with the Module that LazyBChildComponent belongs to, which is LazyModuleB.

Dependency Resolution of Lazy Loaded In Angular


Notes on Dependency Resolution

Element Injector Tree is not a child of Module Injector Tree. In the Element Injector Tree, each Element can have the same parent, however in the Module Injector Tree, each Element can have a different parent.

Eager Modules have no injectors. They both have access to it, as does the Root Module.

Module Ejector Tree: Separate Injector for Lazy Loaded Modules

If two Eager Modules offer the same service for the same token, the module in the imports array that appears last wins. For example, providers of Eager2Module overwrite providers of Eager1Module for the same token in the following imports.

imports: [BrowserModule, FormsModule, Eager1Module, Eager2Module],

If Eager Module and Root Module both deliver the same service for the same token, Root Module is the winner.

Any service that is offered The Root Module Injector becomes a component of the root value of the lazy loaded module.

The Root Module Injector becomes a component of the root value of the lazy loaded module.

Remove it from the providedIn and add it to the Module's provider's array to limit service to the lazy loaded module.

Within the scope of an injector, the Services are singletons. The injector produces a new instance of a service when it receives a request for it for the first time. It will return the already constructed instance for all subsequent requests.

When Angular removes the related Module or element, the Injectors are removed.

The scope, longevity, and bundle size of your services are determined by where you configure them.

Resolution Modifiers can be used to change the behavior of injectors. In Angular, look up the @Self, @SkipSelf, and @Optional Decorators, as well as the @Host Decorator.

Example of hierarchical dependency injection

We take one sample application. There are three modules in the app: RootModule, EagerModule, and LazyModule.

Each module has a single service. Each one creates a different random number. We associate the number with the service's name so that you can tell which service created it.

The AppService's source code. Other services are nearly equivalent to this.

Add the following code under the app.service.ts file
import { Injectable } from '@angular/core';
 
@Injectable()
export class AppService {
  sharedValue: string;
 
  constructor() {
    console.log('Shared Service initialised');
    this.sharedValue = 'App:' + Math.round(Math.random() * 100);
    console.log(this.sharedValue);
  }
 
  public getSharedValue() {
    return this.sharedValue;
  }
}

All components inject and display the results of all services. The @Optional() decorator is used. If the dependency is not detected, the Injector will return null instead of an error.
import { Component, Optional } from '@angular/core';
import { AppService } from './app.service';
import { EagerService } from './eager/eager.service';
import { LazyService } from './lazy/lazy.service';
 
@Component({
  selector: 'parent1-component',
  template: `
    <div class="box">
      <strong>Parent1</strong>
      {{ appValue }}
      {{ eagerValue }}
      {{ lazyValue }}
 
      <child1-component></child1-component>
      <child2-component></child2-component>
 
    </div>
  `,
  providers: []
})
export class Parent1Component {
  appValue;
  eagerValue;
  lazyValue;
 
  constructor(
    @Optional() private appService: AppService,
    @Optional() private eagerService: EagerService,
    @Optional() private lazyService: LazyService
  ) {
 
    this.appValue = appService?.getSharedValue();
    this.eagerValue = eagerService?.getSharedValue();
    this.lazyValue = lazyService?.getSharedValue();
  }
}

Let's play around now and observe how giving services in different locations affects the outcome.

Provide Services In The Respective Modules

Add the AppService, EagerService, and LazyService to their respective NgModules using the Providers array.

Providing Services In Modules Angular

  • Because they are available in the RootModule Injector, AppService and EagerService return the same value everywhere.
  • Only the LazyModule Injector supports LazyService. As a result, it's only available in the LazyComponent.

You might want to give the following a shot.
  • Instead of EagerModule, register EagerService in AppModule.
  • Both EagerService and AppService should have providedIn: 'root'.
Both will provide the same outcome.

Using ProvidedIn in LazyService

  • For the LazyService, add providedIn:'root'. Root Module Injector now includes LazyService.
  • Remove LazyService from the Array of LazyModules in the Provider.
@Injectable({ providedIn: 'root' })
export class LazyService {

Because the LazyService is now available through the Root Module Injector, it will be available as a singleton throughout the application.

ProvidedIn Metadata With Value Root Injector In Angular


You might want to give the following a shot.
  • For the LazyService, keep providedIn:'root'.
  • Add LazyService to the LazyModule's Providers array.
LazyService is now available from two injectors. In the RootModule Injector and the LazyModule Injector, respectively. The service from the LazyModule Injector will be used by the LazyComponent, whereas the service from the RootModule Injector will be used by the rest of the App. As a result, you have two different values.

LazyService in AppComponent

  • For the LazyService, keep providedIn:'root'.
  • Add LazyService to the LazyModule's Providers array.
  • Add LazyService to the AppComponent's Providers array.
LazyService is now available from three injectors (code). RootModule Injector, LazyModule Injector, and AppComponent Injector are all examples of injectors. The root of the Element Injector Tree is the AppComponent Injector. The AppComponent is the parent of all Components. As a result, they all have the same value.

Services in a Component

Register the AppService in Parent1Component's Providers array.

The Parent1Component and all of its child components get their copy of AppService from the Parent1Component, whereas the rest of the App gets it from AppModule, as seen in the image below.

Services Provided In Parent Component Angular


Conclusion

In this article, We learned how Angular dependency injection works. Angular creates a dependency injection framework that is hierarchical. It produces a tree of Injectors that is hierarchical. Angular Providers is duplicated for each Injector.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in comment section.

Post a Comment

Previous Post Next Post