Функция 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() в устаревших проектах постепенно, не стоит переносить сразу всю кодовую базу.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Francesco Borzì: How Using Angular’s inject() Function Has Saved Me 1000 Lines of Code

Предыдущая статьяРеализация захвата изменения данных с Docker, PostgreSQL, MongoDB, Kafka и Debezium: подробное руководство