Комментарии 10
P.S. странно но про инжектирование компонентов я знал =)
Автору, большое спасибо!
Спасибо за статью! Очень интересно, особенно применение в абстрактных классах.
Пример 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 в тесте тривиально, и не надо ничего дополнительно провайдить (как это сказать по-русски?) на уровне приложения — сервис сразу готов к использованию!
Можно и так. Просто токен — это дополнительная сущность. Если написать 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();
}
}
Возможности Angular DI, о которых почти ничего не сказано в документации