Angular Dependency Injection

By Arvind Rai, February 13, 2024
This page will walk through Angular dependency injection example. Dependency injection (DI) is a design pattern where objects are passed to another object to complete the tasks. In angular a service or component may require other dependent services to complete a task. Angular uses dependency injection design pattern to fulfill these dependencies. The advantage of dependency injection design pattern is to divide the task among deferent services. The client service will not create the dependent object itself rather it will be created and injected by an Angular injector. The responsibility of Angular injector is creating service instances and injecting them into classes like components and services. Angular creates root injector during bootstrap process and then creates other injectors. Angular injectors do not know automatically how to create service instances, so we need to specify providers for every service otherwise service instance will not be injected. Injector creates singleton object of a service and hence same object is injected in components and services.
In Angular we specify providers for services using @Injectable(), @NgModule() and @Component() decorators. Dependency injection in the Angular components and services can be achieved using constructor or Injector. Now find the complete example of Angular dependency injection step by step.

1. Specify Providers for Services

As we know that we need to specify providers for services so that Angular injectors can create those service instances. We can specify providers using @Injectable(), @NgModule() and @Component() decorators.
@Injectable(): It has providedIn metadata to specify providers for services introduced in Angular 6 version.
@NgModule(): It has providers metadata to specify providers for services.
@Component(): It has providers metadata to specify providers for services.

Providers use following properties for dependency injection.
useClass: A class provider that creates and returns new instance of specified class.
useExisting: An alias provider that maps one token to another.
useFactory: Configures a factory provider that returns object for dependency injection.
useValue: A value provider that returns a fixed value for dependency injection.

Injector creates singleton object of the class configured by provider for DI. But If we have configured a service in more than one places using provider then object created by injector will be different. Suppose we have two components and they have their respective children components. Suppose both components configure same service class. Now for first component, injector will create a singleton object that will be available for first components and its children components up to the bottom component. For second component, injector will create a different singleton object that will be available for second component and its children components up to the bottom component for DI.
Now we will discuss by code how to use @Injectable(), @NgModule() and @Component() decorators to specify providers for services.

1.1. Using @Injectable() Providers

@Injectable() decorator identifies the class or service that is applicable for dependency injection by Angular injectors. @Injectable() can also specify providers for the service at which it is decorated. @Injectable() has providedIn properties using which we can specify provider for that service. Find the code snippet.
import { Injectable } from '@angular/core';

@Injectable({
    providedIn: 'root'
})
export class BookService {
    ------
} 
Configuring providedIn: 'root' means that above service instance will be created by root injector by using service constructor. If there are parameters in constructor, that will be provided by the injector. The service configured with providedIn: 'root' will be available for dependency injection for all components and services in the application. @Injectable() decorator can also configure providers using useClass, useExisting, useValue and useFactory properties. Find some examples.

useExisting Example:
import { Injectable } from '@angular/core';
import { Computer } from './computer';
import { LaptopService } from './laptop.service';

@Injectable({
	providedIn: 'root',
	useExisting: LaptopService
})
export class DesktopService implements Computer {
        ------
} 

useFactory Example:
import { Injectable } from '@angular/core';
import { BookService } from './book.service'

@Injectable({
	providedIn: 'root',
	useFactory: (bookService: BookService) => 
			  new PreferredBookService(bookService),
	deps: [ BookService ]
})
export class PreferredBookService {
	constructor(private bookService: BookService) {}
        ------
} 

1.2. Using @NgModule() Providers

@NgModule() creates a module and collects module configurations using metadata such as imports, declarations, providers, bootstrap etc. providers metadata is used to specify providers for services. Find the example.
@NgModule({
  providers: [ 
    BookService,
    LoggerService
  ],  
  ------ 
})
export class AppModule { } 
The services configured by @NgModule() in AppModule can be injected in the services and components configured in AppModule. We can also configure providers using useClass, useExisting, useValue and useFactory properties with @NgModule() decorator in providers metadata.

useClass Example:
@NgModule({
  providers: [ 
      GlobalErrorHandlerService,
      { provide: ErrorHandler, useClass: GlobalErrorHandlerService }
  ],  
  ------ 
})
export class AppModule { } 

useValue Example:
@NgModule({
  providers: [ 
     { provide: Book, useValue: JAVA_BOOK },
     { provide: HELLO_MESSAGE, useValue: 'Hello World!' }
  ]
  ------ 
})
export class AppModule { } 

1.3. Using @Component() Providers

@Component() decorator creates a class as component. It collects component configurations using metadata such as selector, providers, template etc. providers metadata is used to specify providers for services. Find the sample example.
@Component({
    providers: [ 
        AnimalService,
        LionService,
        CowService
    ],    
    ------
})
export class AnyAnimalComponent {
  ------
} 
The services configured by @Component() will be available for this component and its sub-component for dependency injection. We should avoid using providers metadata configuration in top component that is AppComponent. Using @Component() decorator we can also configure providers using useClass, useExisting, useValue and useFactory properties in providers metadata.

useValue Example:
@Component({
    providers: [ 
	    { provide: Book, useValue: JAVA_BOOK },
	    { provide: HELLO_MESSAGE, useValue: 'Hello World!' }
    ],    
    ------ 
})
export class BookComponent {
   ------
} 

useExisting Example:
@Component({
    providers: [ 
	 LaptopService,
	 { provide: DesktopService, useExisting: LaptopService }
    ],     
    ------
})
export class ComputerComponent {
    ------
} 

2. Inject Services using Constructor

Once we have specified providers for services then we can inject those services into components and other services using constructor. Suppose we have specified providers for BookService, LoggerService and UserService and has declared a component WriterComponent in @NgModule() decorator as following.
@NgModule({
  providers: [ 
    BookService,
    LoggerService,
    UserService
  ],  
  declarations: [
    WriterComponent
  ]
  ------ 
})
export class AppModule { } 
Now if we want to inject BookService and LoggerService into UserService and WriterComponent using constructor, we will do as following.
Inject BookService and LoggerService into UserService
@Injectable()
export class UserService {
    constructor(private bookService: BookService, private loggerService: LoggerService) { } 
    ------
} 
Now we can use bookService and loggerService objects into UserService.
Inject BookService and LoggerService into WriterComponent
@Component({
     selector: 'writer',
     ------
})
export class ComputerComponent {
    constructor(private bookService: BookService, private loggerService: LoggerService) { }
     ------
} 
Now we can use bookService and loggerService objects into ComputerComponent.

3. Inject Services using Injector

Angular Injector abstract class can be used to inject a service. It is useful in the case when services needs to be instantiated and injected before providers. First we need to inject Injector into a service using constructor and then using Injector.get method we can instantiate a service. Suppose we want LoggerService to inject into GlobalErrorHandlerService using Injector. First specify the providers for the services.
import { NgModule, ErrorHandler } from '@angular/core';

@NgModule({
  providers: [ 
      LoggerService,
      GlobalErrorHandlerService,
      { provide: ErrorHandler, useClass: GlobalErrorHandlerService }      
  ],  
  ------ 
})
export class AppModule { } 
Now find the code to inject LoggerService into GlobalErrorHandlerService using Injector.
import { Injectable, ErrorHandler, Injector } from '@angular/core';

@Injectable()
export class GlobalErrorHandlerService implements ErrorHandler {
    constructor(private injector: Injector) { }    

    handleError(error: any) {
      const loggerService = this.injector.get(LoggerService);  
      ------     
    }
} 

4. InjectionToken, @Inject() and @Optional()

InjectionToken creates a token that can be used in dependency injection provider specially for string, array, dates, number etc. These values will be injected using @Inject() decorator in a constructor.
The @Optional() decorator makes dependency injection optional. It means if service is not available for injection then null will be assigned. Find the sample example.
import { Component, InjectionToken, Inject } from '@angular/core';

export const HELLO_MESSAGE = new InjectionToken<string>('Hello!'); 

@Component({
    selector: 'book',
    providers: [ 
        { provide: HELLO_MESSAGE, useValue: 'Hello World!' }
    ],     
    ------
})
export class BookComponent {

    constructor( @Inject(HELLO_MESSAGE) private message: string,
                 @Optional() private loggerService: LoggerService ) { }
} 
In the above constructor the injected value for message will be Hello World!.
If LoggerService has not been specified in providers then Angular will not throw error but assign null to loggerService.

5. Dependency Injection (DI) Complete Example

Now we will provide complete dependency injection (DI) example with useClass, useExisting, useValue and useFactory properties. We will use @Injectable(), @NgModule() and @Component() decorators to specify providers for the services in our example. Now find the project structure of our demo application.
my-app
|
|--src
|   |
|   |--app 
|   |   |
|   |   |--services
|   |   |    | 
|   |   |    |--animal.service.ts 
|   |   |    |--lion.service.ts
|   |   |    |--cow.service.ts
|   |   |    |
|   |   |    |--logger.service.ts
|   |   |    |
|   |   |    |--desktop.service.ts
|   |   |    |--laptop.service.ts
|   |   |    |
|   |   |    |--book.service.ts
|   |   |    |--preferred-book.service.ts
|   |   |    |
|   |   |    |--computer.ts
|   |   |    |--book.ts
|   |   |     
|   |   |--animal-details.component.ts
|   |   |--any-animal.component.ts
|   |   |--lion.component.ts
|   |   |--cow.component.ts
|   |   |
|   |   |--global-error-handler.service.ts
|   |   |
|   |   |--computer.component.ts
|   |   |
|   |   |--book.component.ts
|   |   |
|   |   |--preferred-book.component.ts
|   |   |
|   |   |--app.component.ts
|   |   |--app.module.ts 
|   | 
|   |--main.ts
|   |--index.html
|   |--styles.css
|
|--node_modules
|--package.json 

5.1. DI with useClass

useClass is a class provider that creates and returns new instance of specified class. It is used as follows.
providers: [ 
    { provide: AnimalService, useClass: LionService }
] 
In the above configuration first part is token and second part is provider definition object. The class type of provider definition object must be same as token class or its subclass. LionService is the subclass of AnimalService. Suppose we have used AnimalService as DI using constructor in any component or service, by above configuration they will get the object of LionService as DI.
In case of useClass configuration if provider definition is same class as the class used for token given as below.
providers: [ 
    { provide: AnimalService, useClass: AnimalService }
] 
Then service can be configured by provider in short as following.
providers: [ 
    AnimalService
] 

Example 1: Using @Component()
animal.service.ts
import { Injectable } from '@angular/core';

@Injectable()
export class AnimalService {
	name = 'Animal';
	food = 'Food';	
	getName() {
		return this.name;
	}
        getFood() {
		return this.food;
	}		
} 
lion.service.ts
import { Injectable } from '@angular/core';
import { AnimalService } from './animal.service';

@Injectable()
export class LionService extends AnimalService {
	name = 'Lion';
	food = 'Meat';
	constructor() {
	    super();
	}
} 
cow.service.ts
import { Injectable } from '@angular/core';
import { AnimalService } from './animal.service';

@Injectable()
export class CowService extends AnimalService {
	name = 'Cow';
	food = 'Grass';	
	constructor() {
	    super();
	}
} 
animal-details.component.ts
import { Component, OnInit } from '@angular/core';
import { AnimalService } from './services/animal.service';

@Component({
    selector: 'animal-details',
    template: `
	   <h3> {{name}} eats {{food}} </h3>
	`
})
export class AnimalDetailsComponent implements OnInit {
	name: string;
	food: string;
        constructor(private animalService: AnimalService) {	}
	ngOnInit() {
		this.name = this.animalService.getName();
		this.food = this.animalService.getFood();
	}	
} 
any-animal.component.ts
import { Component } from '@angular/core';
import { AnimalService } from './services/animal.service';

@Component({
    selector: 'any-animal',
    providers: [ 
         AnimalService
    ],    
    template: `
	     <animal-details></animal-details>
	`
})
export class AnyAnimalComponent {
} 
lion.component.ts
import { Component } from '@angular/core';
import { AnimalService } from './services/animal.service';
import { LionService } from './services/lion.service';

@Component({
    selector: 'lion',
    providers: [ 
	    { provide: AnimalService, useClass: LionService }
    ],    
    template: `
	    <animal-details></animal-details>
	`
})
export class LionComponent {
} 
cow.component.ts
import { Component } from '@angular/core';
import { AnimalService } from './services/animal.service';
import { CowService } from './services/cow.service';

@Component({
    selector: 'cow',
    providers: [ 
	    { provide: AnimalService, useClass: CowService }
    ],     
    template: `
	     <animal-details></animal-details>
	`
})
export class CowComponent {
} 
Example 2: Using @NgModule()
global-error-handler.service.ts
import { Injectable, ErrorHandler, Injector } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { LoggerService } from './services/logger.service';

@Injectable()
export class GlobalErrorHandlerService implements ErrorHandler {
    constructor(private injector: Injector) { }    

    handleError(error: any) {
      const loggerService = this.injector.get(LoggerService);  
      if (error instanceof HttpErrorResponse) {
        loggerService.log('Backend returned status code: ' + error.status);
        loggerService.log('Response body:' + error.message);          	  
      } else {
        loggerService.log('An error occurred:' + error.message);          
      }     
    }
} 
app.module.ts
import { NgModule, ErrorHandler  } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }  from './app.component';
import { CowComponent }  from './cow.component';
import { LionComponent }  from './lion.component';
import { AnyAnimalComponent }  from './any-animal.component';
import { AnimalDetailsComponent }  from './animal-details.component';
import { ComputerComponent }  from './computer.component';
import { BookComponent }  from './book.component';
import { PreferredBookComponent }  from './preferred-book.component';
import { GlobalErrorHandlerService } from './global-error-handler.service';

@NgModule({
  imports: [     
      BrowserModule
  ],
  declarations: [
      AppComponent,
      CowComponent,
      LionComponent,
      AnyAnimalComponent,
      AnimalDetailsComponent,
      ComputerComponent,
      BookComponent,
      PreferredBookComponent		
  ],
  providers: [ 
      GlobalErrorHandlerService,
      { provide: ErrorHandler, useClass: GlobalErrorHandlerService }      
  ],  
  bootstrap: [
      AppComponent
  ]
})
export class AppModule { } 

5.2. DI with useExisting

useExisting is an alias provider that maps one token to another. Mapping can be done only if both tokens has same interface. Alias provider is used in the case when we have injected a service in component or in any other service and it has become old and now we want to use new service without changing the code.

Example: Using @Injectable()
computer.ts
export interface Computer {
    getComputerName();
} 
laptop.service.ts
import { Injectable } from '@angular/core';
import { Computer } from './computer';

@Injectable({
	providedIn: 'root'
})
export class LaptopService implements Computer {
	getComputerName() {
		return 'LAPTOP';
	}	
} 
desktop.service.ts
import { Injectable } from '@angular/core';
import { Computer } from './computer';
import { LaptopService } from './laptop.service';

@Injectable({
	providedIn: 'root',
	useExisting: LaptopService
})
export class DesktopService implements Computer {
	getComputerName() {
		return 'DESKTOP';
	}
} 
By above configuration wherever we have injected DesktopService, they will use LaptopService.
computer.component.ts
import { Component, OnInit } from '@angular/core';
import { DesktopService } from './services/desktop.service';

@Component({
    selector: 'computer',
    template: `
	     <h3> I work on {{computerName}} </h3>
	`
})
export class ComputerComponent implements OnInit {
	computerName: string;
	constructor(private computerService: DesktopService) { }
	ngOnInit() {
		this.computerName = this.computerService.getComputerName();
	}
} 

5.3. DI with useValue

useValue is value provider that returns a fixed value for dependency injection. Injector does not create the object in this case but we create the object and that object is configured with provider using useValue.

Example: Using @Component()
book.ts
export class Book {
	constructor(public name: string, public category: string){}
} 
book.component.ts
import { Component, OnInit, InjectionToken, Inject } from '@angular/core';
import { Book } from './services/book';

const JAVA_BOOK = new Book('Learning Java', 'Java');
export const HELLO_MESSAGE = new InjectionToken<string>('Hello!'); 

@Component({
    selector: 'book',
    providers: [ 
	    { provide: Book, useValue: JAVA_BOOK },
		{ provide: HELLO_MESSAGE, useValue: 'Hello World!' }
    ],     
    template: `
	     <p>Book Name: <b>{{book.name}}</b> </p>
	     <p>Category: <b>{{book.category}}</b></p>
	     <p>Message: <b>{{message}}</b> </p>
	`
})
export class BookComponent implements OnInit {
	constructor(private book: Book, 
	            @Inject(HELLO_MESSAGE) private message: string) { }
	
	ngOnInit() {
	}
} 

5.4. DI with useFactory

useFactory configures a factory provider that returns object for dependency injection.

Example: Using @Injectable()
book.service.ts
import { Injectable } from '@angular/core';
import { Book } from './book'

const BOOKS: Book[] = [
         {"name": "Head First Java", "category": "Java"},
	 {"name": "Hibernate in Action", "category": "Hibernate"},
	 {"name": "Thinking in Java", "category": "Java"},
	 {"name": "Beginning Hibernate", "category": "Hibernate"},
	 {"name": "Effective Java", "category": "Java"},
	 {"name": "Learning Java", "category": "Java"},
	 {"name": "Hibernate Recipes", "category": "Hibernate"},		 
];

@Injectable({
        providedIn: 'root'
})
export class BookService {
	getAllBooks(): Book[] {
		return BOOKS;
	}
} 
preferred-book.service.ts
import { Injectable } from '@angular/core';
import { BookService } from './book.service'

@Injectable({
	providedIn: 'root',
	useFactory: (bookService: BookService) => 
			  new PreferredBookService(bookService),
	deps: [ BookService ]
})
export class PreferredBookService {
	constructor(private bookService: BookService) {}

	getPreferredBooks() {
	  return this.bookService.getAllBooks()
           .filter( book => book.category === 'Java')
           .map(book => book.name)
           .slice(0, Math.max(0, 3))
	   .join(' | ');
	}
} 
deps configures the token that will be used by injector to provide dependency injection required by factory method.
preferred-book.component.ts
import { Component, OnInit } from '@angular/core';
import { PreferredBookService } from './services/preferred-book.service';

@Component({
    selector: 'preferred-book',
    template: `
	  <h3>Preferred Books</h3>
	  {{preferredBooks}}
	`
})
export class PreferredBookComponent implements OnInit {
	preferredBooks: string;
	constructor(private preferredBookService: PreferredBookService) { }
	
	ngOnInit() {
		this.preferredBooks = this.preferredBookService.getPreferredBooks();
	}
} 
app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
          --- useClass ---
	  <any-animal></any-animal>
	  <lion></lion> 
	  <cow></cow>

	  --- useExisting ---
	  <computer></computer>
	  
	  --- useValue ---
	  <book></book>
	  
	  --- useFactory ---
	  <preferred-book></preferred-book>
  `
})
export class AppComponent {
} 

6. Output

Find the print-screen of the output.
Angular Dependency Injection

7. References

Angular Dependency Injection

8. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us