Angular 19.1.0 對 ngComponentOutlet 新增 componentInstance getter。 透過 componentInstance,開發人員可以直接在模板和元件 class 中與渲染的元件進行互動。
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
有一個輸入元素,您可以在其中輸入名稱和單選按鈕以選擇使用者類型。當使用者選 "Admin"時,示範會渲染 AdminComponent
元件。當使用者選擇 "User" 時,它會渲染 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
,用於動態渲染。每個元件都有 type
、 permissions
和 name
的訊號輸入。它有一個 getGreeting
方法來傳回問候語。當 name
輸入更新時,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
是一個 injection token,它提供實作 setGreeting
函數的物件。 Injector.create
靜態方法建立一個 injector,當程式碼注入 GREETING_TOKEN
時,該 injector 會傳回 AdminGreetingService
。
export const configs = {
"admin": {
type: AdminComponent,
permissions: ['create', 'edit', 'view', 'delete'],
injector
},
"user": {
type: UserComponent,
permissions: ['view'],
injector
},
}
configs 物件將鍵對應到動態元件 (type)、permissions 和 injector。
@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
是一個計算訊號,當使用者選擇使用者類型時,它會尋找type、injector 和 permissions。
<ng-container [ngComponentOutlet]="ct.type"
[ngComponentOutletInputs]="inputs()"
[ngComponentOutletInjector]="ct.injector"
#instance="ngComponentOutlet"
/>
App
元件建立一個 NgContainer
並將 type、inputs 和 injector 分配給 ngComponentOutlet
、ngComponentOutletInputs
和 ngComponentOutletInjector
輸入。
此外,ngComponentOutlet
directive 將 componentInstance
公開給 instance
模板變數。
@let componentInstance = instance?.componentInstance;
<p>Greeting from componentInstance: {{ componentInstance?.getGreeting() }}</p>
<p>Greeting from componentInstance's injector: {{ componentInstance?.service.greeting() }}</p>
在模板中,我可以依靠 componentInstance
顯示 getGreeting
方法的值。 此外,我存取 AdminGreetingService
服務並顯示 greeting
訊號的值。
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 開發人員呼叫其訊號、輸入、方法和內部服務。