动态组件 #
一、动态组件概述 #
动态组件允许在运行时动态创建和销毁组件,适用于模态框、标签页、动态表单等场景。
二、基本动态组件 #
2.1 使用ViewContainerRef #
typescript
import { Component, ViewChild, ViewContainerRef, AfterViewInit, ComponentRef } from '@angular/core';
import { UserCardComponent } from './user-card.component';
@Component({
selector: 'app-dynamic-demo',
template: `
<button (click)="createComponent()">创建组件</button>
<button (click)="destroyComponent()">销毁组件</button>
<ng-template #container></ng-template>
`
})
export class DynamicDemoComponent implements AfterViewInit {
@ViewChild('container', { read: ViewContainerRef }) container!: ViewContainerRef;
private componentRef: ComponentRef<UserCardComponent> | null = null;
ngAfterViewInit() {}
createComponent() {
this.container.clear();
this.componentRef = this.container.createComponent(UserCardComponent);
this.componentRef.instance.user = {
name: 'John',
email: 'john@example.com'
};
this.componentRef.instance.selected.subscribe((user) => {
console.log('选中用户:', user);
});
}
destroyComponent() {
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
}
}
2.2 传递数据给动态组件 #
typescript
@Component({
selector: 'app-user-card',
template: `
<div class="card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
<button (click)="onSelect()">选择</button>
</div>
`
})
export class UserCardComponent {
@Input() user: User;
@Output() selected = new EventEmitter<User>();
onSelect() {
this.selected.emit(this.user);
}
}
// 创建并传递数据
createComponent() {
const componentRef = this.container.createComponent(UserCardComponent);
componentRef.instance.user = this.currentUser;
componentRef.instance.selected.subscribe(user => {
console.log('选中:', user);
});
}
三、动态模态框 #
3.1 模态框服务 #
typescript
import { Injectable, inject } from '@angular/core';
import { ViewContainerRef } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class ModalService {
private container: ViewContainerRef;
setContainer(container: ViewContainerRef) {
this.container = container;
}
open<T>(component: Type<T>, inputs?: Partial<T>): ComponentRef<T> {
this.container.clear();
const componentRef = this.container.createComponent(component);
if (inputs) {
Object.assign(componentRef.instance, inputs);
}
return componentRef;
}
close() {
this.container.clear();
}
}
3.2 模态框组件 #
typescript
@Component({
selector: 'app-modal',
template: `
<div class="modal-overlay" (click)="onOverlayClick()">
<div class="modal-content" (click)="$event.stopPropagation()">
<div class="modal-header">
<h3>{{ title }}</h3>
<button (click)="close.emit()">×</button>
</div>
<div class="modal-body">
<ng-content></ng-content>
</div>
<div class="modal-footer">
<button (click)="cancel.emit()">取消</button>
<button (click)="confirm.emit()">确认</button>
</div>
</div>
</div>
`,
styles: [`
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
min-width: 300px;
}
`]
})
export class ModalComponent {
@Input() title: string = '模态框';
@Output() close = new EventEmitter<void>();
@Output() confirm = new EventEmitter<void>();
@Output() cancel = new EventEmitter<void>();
onOverlayClick() {
this.close.emit();
}
}
3.3 使用模态框 #
typescript
@Component({
selector: 'app-root',
template: `
<button (click)="openModal()">打开模态框</button>
<ng-template #modalContainer></ng-template>
`
})
export class AppComponent implements AfterViewInit {
@ViewChild('modalContainer', { read: ViewContainerRef }) modalContainer!: ViewContainerRef;
constructor(private modalService: ModalService) {}
ngAfterViewInit() {
this.modalService.setContainer(this.modalContainer);
}
openModal() {
const modalRef = this.modalService.open(ModalComponent, {
title: '确认操作'
});
modalRef.instance.confirm.subscribe(() => {
console.log('确认');
this.modalService.close();
});
modalRef.instance.cancel.subscribe(() => {
console.log('取消');
this.modalService.close();
});
}
}
四、动态标签页 #
4.1 标签页配置 #
typescript
interface Tab {
id: string;
title: string;
component: Type<any>;
inputs?: Record<string, any>;
}
@Component({
selector: 'app-tabs',
template: `
<div class="tabs">
<div class="tab-headers">
<button
*ngFor="let tab of tabs"
[class.active]="activeTab === tab.id"
(click)="selectTab(tab.id)">
{{ tab.title }}
<span (click)="closeTab(tab.id); $event.stopPropagation()">×</span>
</button>
</div>
<div class="tab-content">
<ng-template #tabContainer></ng-template>
</div>
</div>
`
})
export class TabsComponent implements AfterViewInit {
@ViewChild('tabContainer', { read: ViewContainerRef }) container!: ViewContainerRef;
@Input() tabs: Tab[] = [];
@Output() tabClosed = new EventEmitter<string>();
activeTab: string | null = null;
private componentRef: ComponentRef<any> | null = null;
ngAfterViewInit() {
if (this.tabs.length > 0) {
this.selectTab(this.tabs[0].id);
}
}
selectTab(tabId: string) {
const tab = this.tabs.find(t => t.id === tabId);
if (!tab) return;
this.activeTab = tabId;
this.container.clear();
this.componentRef = this.container.createComponent(tab.component);
if (tab.inputs) {
Object.assign(this.componentRef.instance, tab.inputs);
}
}
closeTab(tabId: string) {
this.tabClosed.emit(tabId);
}
}
4.2 使用标签页 #
typescript
@Component({
selector: 'app-dashboard',
template: `
<app-tabs
[tabs]="tabs"
(tabClosed)="onTabClosed($event)">
</app-tabs>
`
})
export class DashboardComponent {
tabs: Tab[] = [
{ id: 'home', title: '首页', component: HomeComponent },
{ id: 'users', title: '用户', component: UsersComponent },
{ id: 'settings', title: '设置', component: SettingsComponent }
];
onTabClosed(tabId: string) {
this.tabs = this.tabs.filter(t => t.id !== tabId);
}
}
五、动态表单字段 #
5.1 字段配置 #
typescript
interface FieldConfig {
type: 'text' | 'number' | 'select' | 'date';
name: string;
label: string;
options?: { label: string; value: any }[];
validators?: ValidatorFn[];
}
@Component({
selector: 'app-dynamic-field',
template: `
<div [ngSwitch]="config.type">
<input
*ngSwitchCase="'text'"
type="text"
[formControl]="control"
[placeholder]="config.label"
/>
<input
*ngSwitchCase="'number'"
type="number"
[formControl]="control"
[placeholder]="config.label"
/>
<select
*ngSwitchCase="'select'"
[formControl]="control">
<option value="">{{ config.label }}</option>
<option
*ngFor="let option of config.options"
[value]="option.value">
{{ option.label }}
</option>
</select>
<input
*ngSwitchCase="'date'"
type="date"
[formControl]="control"
/>
</div>
`
})
export class DynamicFieldComponent {
@Input() config: FieldConfig;
@Input() control: FormControl;
}
5.2 动态表单组件 #
typescript
@Component({
selector: 'app-dynamic-form',
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div *ngFor="let field of fields">
<label>{{ field.label }}</label>
<app-dynamic-field
[config]="field"
[control]="form.get(field.name)">
</app-dynamic-field>
</div>
<button type="submit">提交</button>
</form>
`
})
export class DynamicFormComponent {
@Input() fields: FieldConfig[] = [];
@Output() submit = new EventEmitter<any>();
form: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnChanges() {
this.createForm();
}
private createForm() {
const controls: { [key: string]: FormControl } = {};
for (const field of this.fields) {
controls[field.name] = new FormControl('', field.validators || []);
}
this.form = this.fb.group(controls);
}
onSubmit() {
if (this.form.valid) {
this.submit.emit(this.form.value);
}
}
}
六、懒加载组件 #
6.1 懒加载服务 #
typescript
import { Injectable } from '@angular/core';
import { ViewContainerRef, Type } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class LazyLoadService {
async loadComponent<T>(
container: ViewContainerRef,
importFn: () => Promise<{ [key: string]: Type<T> }>,
componentName: string
): Promise<ComponentRef<T>> {
container.clear();
const module = await importFn();
const component = module[componentName];
return container.createComponent(component);
}
}
6.2 使用懒加载 #
typescript
@Component({
selector: 'app-lazy-demo',
template: `
<button (click)="loadComponent()">加载组件</button>
<ng-template #container></ng-template>
`
})
export class LazyDemoComponent {
@ViewChild('container', { read: ViewContainerRef }) container!: ViewContainerRef;
constructor(private lazyLoadService: LazyLoadService) {}
async loadComponent() {
const componentRef = await this.lazyLoadService.loadComponent(
this.container,
() => import('./heavy-component/heavy-component.component'),
'HeavyComponent'
);
componentRef.instance.data = { id: 1 };
}
}
七、最佳实践 #
7.1 清理资源 #
typescript
ngOnDestroy() {
if (this.componentRef) {
this.componentRef.destroy();
}
}
7.2 使用类型安全 #
typescript
interface ComponentInputs {
user: User;
mode: 'edit' | 'view';
}
createComponent(inputs: ComponentInputs) {
const componentRef = this.container.createComponent(UserFormComponent);
Object.assign(componentRef.instance, inputs);
}
7.3 处理订阅 #
typescript
createComponent() {
const componentRef = this.container.createComponent(MyComponent);
const subscription = componentRef.instance.someEvent.subscribe(data => {
console.log(data);
});
componentRef.onDestroy(() => {
subscription.unsubscribe();
});
}
八、总结 #
| 概念 | 说明 |
|---|---|
ViewContainerRef |
视图容器引用 |
createComponent |
创建动态组件 |
ComponentRef |
组件引用 |
instance |
组件实例 |
destroy |
销毁组件 |
下一步:模块与懒加载
最后更新:2026-03-26