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.