angular
June 2

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