Angular Dependency Injection In Depth-Dependency Injection Decorators

Dependency Injection Decorators

Angular provides decorators such as @Optional, @Self, @SkipSelf, and @Host that can be used to modify the default injection behavior. These decorators allow for more precise control over dependency resolution and can be handy in complex dependency scenarios.

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

@Injectable()
export class Logger {
  log(message: string): void {
    console.log(`[Logger] ${message}`);
  }
}

@Optional

The @Optional decorator is used when injecting the Logger service into the TodoListComponent component. In this case, the Logger service is optional, so if it’s not available, the application should not throw an error. Here’s the code:

import { Component, Optional } from '@angular/core';
import { Todo } from '../models/todo.model';
import { Logger } from '../services/logger.service';
import { TodoServiceV2 } from '../services/todo-service-v2';


@Component({
  selector: 'app-todo-list',
  template: `
    <ul>
      <li *ngFor="let todo of todos">
        {{ todo.title }}<button (cllick)="deleteTodo(todo.id)">Delete</button>
      </li>
    </ul>
  `,

})
export class TodoListComponent {
  todos: Todo[] = [];

  constructor(private todoServiceV2: TodoServiceV2, @Optional() private logger?: Logger) {
    this.todos = this.todoServiceV2.getAllTasks();
    if (this.logger) {
      this.logger.log('TaskListComponent created');
    }
  }
  deleteTodo(id: number) {
    this.todoServiceV2.deleteTask(id);
  }
}



Output of optional

If the Logger service is available, the following log message will be printed:

[Logger] TodoListComponent created

If the Logger service is not available, there will be no log message printed, and this.logger will be undefined.

@Self

The @Self decorator ensures that the Logger service is resolved from the current component’s injector and not from any parent components. Here’s the code for the TaskItemComponent:

import { Component, Optional, Self } from '@angular/core';
import { Todo } from '../models/todo.model';
import { Logger } from '../services/logger.service';
import { TodoServiceV2 } from '../services/todo-service-v2';


@Component({
  selector: 'app-todo-list',
  template: `
    <ul>
      <li *ngFor="let todo of todos">
        {{ todo.title }}<button (cllick)="deleteTodo(todo.id)">Delete</button>
      </li>
    </ul>
  `,

})
export class TodoListComponent {
  todos: Todo[] = [];

  constructor(private todoServiceV2: TodoServiceV2,@Self() private logger?: Logger) {
    this.todos = this.todoServiceV2.getAllTasks();
    if (this.logger) {
      this.logger.log('TaskListComponent created');
    }
  }
  deleteTodo(id: number) {
    this.todoServiceV2.deleteTask(id);
  }
}



Output of self

The TaskItemComponent will only be created if the Logger service is available in the current component’s injector. If the Logger service is not provided at the current level, an error will be thrown during component creation.

@SkipSelf: The @SkipSelf decorator allows you to skip the parent component’s injector and resolve the Logger service from a higher-level injector. Here’s the code for the TaskListComponent:

import { Component, Optional, Self, SkipSelf } from '@angular/core';
import { Todo } from '../models/todo.model';
import { Logger } from '../services/logger.service';
import { TodoServiceV2 } from '../services/todo-service-v2';


@Component({
  selector: 'app-todo-list',
  template: `
    <ul>
      <li *ngFor="let todo of todos">
        {{ todo.title }}<button (cllick)="deleteTodo(todo.id)">Delete</button>
      </li>
    </ul>
  `,

})
export class TodoListComponent {
  todos: Todo[] = [];

  constructor(private todoServiceV2: TodoServiceV2,@SkipSelf() private logger?: Logger) {
    this.todos = this.todoServiceV2.getAllTasks();
    if (this.logger) {
      this.logger.log('TaskListComponent created');
    }
  }
  deleteTodo(id: number) {
    this.todoServiceV2.deleteTask(id);
  }
}

@Host

The @Host decorator is used when injecting the Logger service into the TaskDirective. It ensures that the Logger service is resolved from the host component’s injector. Here’s the code for the TaskDirective:

import { Directive, Host, Optional } from '@angular/core';
import { Logger } from '../services/logger.service';

@Directive({
  selector: '[appTodoDirective]'
})
export class TodoDirective {
  constructor(@Host() @Optional() private logger?: Logger) {
    if (this.logger) {
      this.logger.log('TodoDirective created');
    }
  }
}

Output of host

The TodoDirective will only be created if the Logger service is available in the host component’s injector. If the Logger service is not available, there will be no log message printed, and this.logger will be undefined.

These examples demonstrate the usage of dependency injection decorators in the context of your task management app. They allow you to control how dependencies are resolved, handle optional dependencies, and navigate the injector hierarchy to obtain the desired instances of services or components.

Next Post Previous Post
No Comment
Add Comment
comment url