Использование метода runInInjectionContext() в Angular
В Angular v14 был представлен API EnvironmentInjector.runInContext. Используя эту функцию, разработчик может выполнить любую функцию у требуемом ему контексте. Как результат, в замыкании функции есть метод inject() для внедрения зависимостей. Рассмотрим пример. Допустим, у нас есть компонент GridActionsComponent:
export abstract class Action {
abstract getLabel(): string;
abstract invoke(): void;
}
@Component({
selector: 'grid-actions',
template: `
<button *ngFor="let action of actions" (click)="invoke(action)">
{{ action.getLabel() }}
</button>
`,
imports: [CommonModule],
standalone: true
})
export class GridActionsComponent {
@Input() actions: Action[] = [];
constructor(private injector: EnvironmentInjector) {}
invoke(action: Action) {
this.injector.runInContext(() => {
action.invoke()
})
}
}Мы ожидаем коллекцию элементов, реализующих интерфейс Action. При нажатии на действие мы вызываем runInContext(), передавая замыкание, которое выполняется в контексте этого инжектора окружения.
В результате мы можем использовать inject() в любом методе Action.invoke(), если это необходимо. Например, создадим действие CopyAction:
export class CopyAction extends Action {
getLabel() {
return 'Copy';
}
invoke() {
inject(CopyService).copy();
}
}@Component({
standalone: true,
imports: [GridActionsComponent],
template: `
<grid-actions [actions]="actions"></grid-actions>
`
})
export class GridCellComponent {
actions = [new CopyAction()];
}Таким образом, пользователь может выбрать, использовать ли DI в действиях или нет.
В версии v14.2.0 роутер позволяет предоставлять обычные функции для гвардов и резолверов:
const useDI = {
path: 'somePath',
component: EditCmp,
canActivate: [
() => inject(MyDependency).canActivate()
]
}
const notUseDI = {
path: 'somePath',
component: EditCmp,
canDeactivate: [
(component: EditCmp) => !component.hasUnsavedChanges
]
}Мы сможем предоставлять перехватчики как обычные функции в пакете HTTP и лениво загружаемых модулях:
export function jsonpInterceptor(
req: HttpRequest<unknown>,
next: HttpHandlerFn
): Observable<HttpEvent<unknown>> {
if (req.method === 'JSONP') {
return inject(JsonpClientBackend).handle(req);
}
// Fall through for normal HTTP requests.
return next(req);
}@NgModule({
providers: [
provideHttpInterceptors([jsonpInterceptor])
]
})
export class LazyModule {}В Angular v16 функция заменяется на чистую функцию с именем runInInjectionContext, которая принимает любой инжектор и выполняет код в его контексте инъекций.
import { runInInjectionContext } from '@angular/core';
export class FooComponent {
ngOnInit() {
runInInjectionContext(this.injector, () => {
console.log(
'I can access the NodeInjector using inject()',
inject(ElementRef)
);
})
}