Функция inject()
Эта функция появилась в Angular 14 как альтернатива конструктору класса для внедрения зависимостей в службы, компоненты, директивы и т. д.
Внедрение зависимостей с помощью конструктора:
import { Component } from '@angular/core';
@Component({ /* ... */ })
export class MyComponent {
constructor(
@Inject(SOME_TOKEN) private readonly someToken: string,
private readonly myService: MyService,
private readonly httpClient: HttpClient,
) {}
}
Внедрение зависимостей функцией inject():
import { Component, inject } from '@angular/core';
@Component({ /* ... */ })
export class MyComponent {
private readonly someToken = inject(SOME_TOKEN);
private readonly myService = inject(MyService);
private readonly httpClient = inject(HttpClient);
}
Преимущества inject() перед конструктором:
- Код с ним чище, удобнее для восприятия и последовательнее, даже при внедрении токенов против служб.
- Типы выводятся автоматически, не указываются вручную.
- Наследование проще, не такое детализированное, подробнее об этом — ниже.
Усовершенствованное наследование
Функция inject()
особенно полезна с точки зрения наследования. Рассмотрим сценарий, в котором нужно переиспользовать код и создать абстрактный класс ParentService
, расширяемый дочерними:
export abstract class ParentService {
constructor(
protected readonly configKey: string,
protected readonly httpClient: HttpClient,
protected readonly helperService: HelperService,
) {}
// ...здесь код, переиспользуемый в дочерних классах «ParentService»
}
ParentService
расширяется дочерними классами так:
@Injectable({ providedIn: 'root' })
export class ChildService extends ParentService {
constructor(
protected readonly httpClient: HttpClient,
protected readonly helperService: HelperService,
) {
super('my-config-key', httpClient, helperService);
}
// ...здесь характерный для дочерних классов код
}
Здесь много повторений: всеми дочерними импортируются HttpClient
и HelperService
только потому, что требуются конструктору ParentService
.
Благодаря функции inject()
лишние повторения избегаются:
export abstract class ParentService {
protected abstract readonly configKey: string; // принудительно инициализируем это поле в дочерних классах, используя «abstract»
protected readonly httpClient = inject(HttpClient);
protected readonly helperService = inject(HelperService);
// ...здесь код, переиспользуемый в дочерних классах «ParentService»...
}
@Injectable({ providedIn: 'root' })
export class ChildService extends ParentService {
protected readonly configKey = 'my-config-key';
// ...здесь характерный для дочерних классов код
}
Итоговый код намного чище, без повторений: дочерним классам ParentService
не нужно импортировать HttpClient
и HelperService
только для передачи родительскому, но доступ к this.httpClient
и this.helperService
у них остается.
В сценарии, где зависимостей больше и много дочерних классов, которыми расширяется базовый, благодаря inject()
экономится много строк кода.
Реальный пример
При рефакторинге старого проекта, созданного на Angular еще в 2019 году, с помощью функции inject()
удалось удалить около 1000 строк кода, просто избавившись от конструкторов или сократив их использование. Как все случилось, смотрите в этом, этом, этом и этом коммитах.
А как же «композиция вместо наследования»? Но это уже тема отдельной статьи.
Заключение
- Функция
inject()
— более совершенный и современный способ внедрения независимостей, который часто предпочитают конструкторам. - Кодовую базу легко перевести на
inject()
; особенно полезно это, если приходится иметь дело с наследованием. - Переходите на
inject()
в устаревших проектах постепенно, не стоит переносить сразу всю кодовую базу.
Читайте также:
- Платформы Angular в деталях. Часть 2. Процесс начальной загрузки приложения
- Аутентификация и авторизация пользователей в Angular 16 с помощью JWT
- Angular и Wiz: вместе лучше
Читайте нас в Telegram, VK и Дзен
Перевод статьи Francesco Borzì: How Using Angular’s inject() Function Has Saved Me 1000 Lines of Code