Как стать автором
Обновить

Комментарии 10

Афигенная статья! Автору, Спасибо!
P.S. странно но про инжектирование компонентов я знал =)
Хорошая статья, сразу увидел пару мест в текущем проекте, где можно оптимизировать ))
Автору, большое спасибо!
Делайте пожлауйста больше статей о DI в Ангуляр. Я прям капец не догоняю его смысла. Недавно наткнулся на одно видео, где человек обьянял и показывал на реальном примере патерн Bridge, очень понравилось и сразу понятно
Поделись пожалуйста ссылкой!

Спасибо за статью! Очень интересно, особенно применение в абстрактных классах.

Пример useFactory для сервиса:


Первоначальный вариант


import * as jwtDecode from "jwt-decode";

@Injectable({ providedIn: "root" })
export class SessionService {
  public getUserId(): string {
    const token = // get token from cookies
    const decoded = jwtDecode(token);
   return decoded.public.user_id;
  }
}

Выглядит неплохо. Но в тестах придется использовать какой-то валидный токен, чтобы jwt-decode его декодировал без ошибок. Тест будет хуже читаться, труднее вносить изменения.


Используем DI


export const JwtInjectionToken = new InjectionToken();

@Injectable({ providedIn: "root" })
export class SessionService {
  constructor(@Inject(JwtInjectionToken) private jwtDecode: typeof jwtDecode) {}

  getUserId() {
    ... то же самое, но вызываем this.jwtDecode(token);
  }
}

Теперь мы можем легко протестировать. Но чтобы использовать этот сервис, надо где-то в приложении задать провайдер для токена JwtInjectionToken. Неудобно.


А теперь применим useFactory:


@Injectable({
  providedIn: "root",
  useFactory: () => new SessionService(jwtDecode)
})
export class SessionService {
  ...
}

Этим самым мы совместили плюсы обоих подходов: подменить jwt-decode в тесте тривиально, и не надо ничего дополнительно провайдить (как это сказать по-русски?) на уровне приложения — сервис сразу готов к использованию!

У токена тоже можно объявить фабрику. Довольно удобно использовать для jwt. И ненужно для каждого сервиса отдельную фабрику писать, можно только для токена.

Можно и так. Просто токен — это дополнительная сущность. Если написать useFactory прямо в сервисе, то токен не нужен.


С токеном:


const Token = new InjectionToken("token", () => jwt)

class Service {
  constructor(@Inject(Token) jwt) {}
}

Без токена:


@Injectable({useFactory: () => new Service(jwt)})
class Service {
  constructor(jwt) {}
}
Самый полезный, по моему мнению, способ использования фабрики в tree shakable сервисах выглядит так:

Пример кмк не очень удачный (как и плюсы), если нужен app-wide синглтон, то есть рекомендованный providedIn: 'root'.


Это работает точно так же, как и следующий пример:@NgModule({providers: [SomeService]})

Вот только в этом случае tree shakable невозможен (из статьи это немного неочевидно, да и вообще оно deprecated). И работает кстати тоже не совсем так как ожидаешь, при @Injectable({providedIn: SomeModule}) SomeModule не сможет использовать этот сервис внутри себя из-за cyclic dependency))) Приходится создавать отдельный пустой модуль для сервисов и импортировать его в SomeModule. Неудобно :(


@Injectable({providedIn: SomeModule})

И тут мы еще получаем одну неочевидную проблему с приоритетом (кто последний тот и папа), что затрудняет переопределение провайдеров определенных в импортируемых модулях (иногда оно нужно).


Поэтому в 99% providedIn: 'root' предпочтительнее.

Спасибо за статью, много полезного для себя узнал!

К теме инжектирования без конструктора

const former = setCurrentInjector(injector); const service = inject(HelloService);

Есть функция ɵɵdirectiveInject (тоже, правда из приватных), аналог inject, но для контекста директив (компонентов)

@Directive()
class BaseComponent<T = object> {
  cdr = directiveInject(ChangeDetectorRef);
  state: T = {} as never;
  setState(value: T): void {
    this.state = value;
    this.cdr.markForCheck();
  }
}
Зарегистрируйтесь на Хабре, чтобы оставить комментарий