Angular 19: Новая функция ngComponentOutlet — componentInstance
В Angular 19.1.0 директива ngComponentOutlet
стала мощнее благодаря добавлению геттера componentInstance
. Теперь разработчики могут получить прямой доступ к экземпляру динамически созданного компонента. Эта возможность крайне важна для Angular-разработчиков, так как упрощает взаимодействие с отображенным компонентом. Можно легко получать доступ к его входным параметрам и методам сразу после создания. С componentInstance
взаимодействие с компонентами в шаблонах и классах компонентов становится непосредственным.
Описание сервиса приветствий
import { Injectable, signal } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AdminGreetingService {
greeting = signal('');
setGreeting(msg: string) {
this.greeting.set(msg);
}
}
Это AdminGreetingService
— сервис с методом setGreeting
, который будет внедряться в отображаемые компоненты.
Создание формы пользователя
import { ChangeDetectionStrategy, Component, model } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-user-form',
imports: [FormsModule],
template: `
@let choices = ['Admin', 'User', 'Intruder'];
@for (c of choices; track c) {
@let value = c.toLowerCase();
<div>
<input type="radio" [id]="value" [name]="value" [value]="value"
[(ngModel)]="userType" />
<label for="admin">{{ c }}</label>
</div>
}
Name: <input [(ngModel)]="userName" />
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserFormComponent {
userType = model.required<string>();
userName = model.required<string>();
}
UserFormComponent
содержит поле ввода имени и радиокнопки для выбора типа пользователя. При выборе «Администратор программно отображается компонент AdminComponent
. При выборе «Пользователь» — компонент UserComponent
.
Динамически отображаемые компоненты
// app.component.html
<h2>{{ type() }} Component</h2>
<p>Name: {{ name() }}</p>
<h2>Permissions</h2>
<ul>
@for (p of permissions(); track p) {
<li>{{ p }}</li>
} @empty {
<li>No Permission</li>
}
</ul>
@Component({
selector: 'app-admin',
templateUrl: `app.component.html`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AdminComponent implements Permission {
permissions = input.required<string[]>();
name = input('N/A');
type = input.required<string>();
service = inject(GREETING_TOKEN);
getGreeting(): string {
return `I am an ${this.type()} and my name is ${this.name()}.`;
}
constructor() {
effect(() => this.service.setGreeting(`Hello ${this.name()}, you have all the power.`));
}
}
@Component({
selector: 'app-user',
templateUrl: `app.component.html`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserComponent implements Permission {
type = input.required<string>();
permissions = input.required<string[]>();
name = input('N/A');
service = inject(GREETING_TOKEN);
getGreeting(): string {
return `I am a ${this.type()} and my name is ${this.name()}.`;
}
constructor() {
effect(() => this.service.setGreeting(`Hello ${this.name()}.`));
}
}
В примере используются два компонента для динамической отрисовки: AdminComponent
и UserComponent
. Каждый компонент имеет три входных параметра: тип, разрешения и имя. Метод getGreeting
возвращает приветствие. При изменении входного имени, effect
запускает функцию обратного вызова для установки приветствия в AdminGreetingService
.
Определение конфигурации динамического компонента
export const GREETING_TOKEN = new InjectionToken<{ setGreeting: (name: string) => void }>('GREETING_TOKEN');
const injector = Injector.create({
providers: [{
provide: GREETING_TOKEN,
useClass: AdminGreetingService
}]}
);
Токен GREETING_TOKEN
— это токен инъекции, предоставляющий объект, реализующий функцию setGreeting
. Статический метод Injector.create
создает инжектор, который возвращает AdminGreetingService
при инъекции токена GREETING_TOKEN
.
export const configs = {
"admin": {
type: AdminComponent,
permissions: ['create', 'edit', 'view', 'delete'],
injector
},
"user": {
type: UserComponent,
permissions: ['view'],
injector
},
}
Объект configs
связывает ключ с динамическим компонентом, входными разрешениями и инжектором.
Программная отрисовка компонентов с помощью ngComponentOutlet
@Component({
selector: 'app-root',
imports: [NgComponentOutlet, UserFormComponent],
template: `
<app-user-form [(userType)]="userType" [(userName)]="userName" />
@let ct = componentType();
<ng-container [ngComponentOutlet]="ct.type"
[ngComponentOutletInputs]="inputs()"
[ngComponentOutletInjector]="ct.injector"
#instance="ngComponentOutlet"
/>
@let componentInstance = instance?.componentInstance;
<p>Greeting from componentInstance: {{ componentInstance?.getGreeting() }}</p>
<p>Greeting from componentInstance's injector: {{ componentInstance?.service.greeting() }}</p>
<button (click)="concatPermissionsString()">Permission String</button>
hello: {{ permissionsString().numPermissions }}, {{ permissionsString().str }}
`,
})
export class App {
userName = signal('N/A');
userType = signal<"user" | "admin" | "intruder">('user');
componentType = computed(() => configs[this.userType()]);
inputs = computed(() => ({
permissions: this.componentType().permissions,
name: this.userName(),
type: `${this.userType().charAt(0).toLocaleUpperCase()}${this.userType().slice(1)}`
}));
outlet = viewChild.required(NgComponentOutlet);
permissionsString = signal({
numPermissions: 0,
str: '',
});
concatPermissionsString() {
const permissions = this.outlet().componentInstance?.permissions() as string[];
this.permissionsString.set({
numPermissions: permissions.length,
str: permissions.join(',')
});
}
}
componentType = computed(() => configs[this.userType()]);
componentType
— вычисляемый сигнал, который определяет компонент, инжектор и разрешения пользователя на основе выбранного типа пользователя.
<ng-container [ngComponentOutlet]="ct.type"
[ngComponentOutletInputs]="inputs()"
[ngComponentOutletInjector]="ct.injector"
#instance="ngComponentOutlet"
/>
Компонент App
создает NgContainer
и назначает тип, инжектор и входные параметры для входов ngComponentOutlet
, ngComponentOutletInputs
и ngComponentOutletInjector
.
Директива ngComponentOutlet
предоставляет доступ к componentInstance
через переменную шаблона instance
.
Использование componentInstance в шаблоне
@let componentInstance = instance?.componentInstance;
<p>Greeting from componentInstance: {{ componentInstance?.getGreeting() }}</p>
<p>Greeting from componentInstance's injector: {{ componentInstance?.service.greeting() }}</p>
В строчном шаблоне можно получить доступ к методу getGreeting
через componentInstance
и отобразить его значение. Также доступен сервис AdminGreetingService
и отображение значения сигнала greeting
.
Использование componentInstance внутри класса компонента
outlet = viewChild.required(NgComponentOutlet);
permissionsString = signal({
numPermissions: 0,
str: '',
});
concatPermissions() {
const permissions = this.outlet().componentInstance?.permissions() as string[];
this.permissionsString.set({
numPermissions: permissions.length,
str: permissions.join(',')
});
}
Функция viewChild.required
запрашивает NgComponentOutlet
, а this.outlet().componentInstance
предоставляет доступ к отрисованному компоненту. Метод concatPermissions
объединяет входные данные permissions
отрисованного компонента и присваивает результат сигналу permissionsString
.
<button (click)="concatPermissions()">Permission String</button>
hello: {{ permissionsString().numPermissions }}, {{ permissionsString().str }}
При нажатии кнопки вызывается метод concatPermissions
, обновляющий сигнал permissionString
, и шаблон отображает значение сигнала.
В заключение componentInstance
предоставляет Angular-разработчикам доступ к отрисованному компоненту для вызова его сигналов, входных параметров, методов и внутренних сервисов.