Использование метода 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) ); }) }