DevGang
Авторизоваться

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-разработчикам доступ к отрисованному компоненту для вызова его сигналов, входных параметров, методов и внутренних сервисов.

Источник:

#Angular
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу