name: angular-20-standalone-component description: “使用现代模式创建Angular 20独立组件:使用Signals进行状态管理、input()/output()函数(非装饰器)、@if/@for/@switch控制流(非*ngIf/*ngFor)、inject()依赖注入(非构造函数)以及OnPush变更检测。当需要搭建具有响应式状态、表单处理或遵循三层架构的服务集成的新UI组件时,使用此技能。” license: “MIT”
Angular 20 独立组件技能
此技能帮助创建遵循现代模式和项目标准的Angular 20组件。
核心原则
现代Angular 20模式
- 独立组件:100%独立,零NgModule
- Signals:使用
signal()、computed()、effect()进行状态管理 - 新语法:
input()、output()、@if、@for、@switch - inject():基于函数的依赖注入
- OnPush:用于性能的变更检测策略
架构集成
- 表示层:组件仅处理UI
- 服务集成:注入服务以处理业务逻辑
- 无直接存储库:绝不直接注入存储库
- 事件驱动:使用EventBus进行跨模块通信
组件模板
import { Component, signal, computed, effect, input, output, inject, ChangeDetectionStrategy } from '@angular/core';
import { SHARED_IMPORTS } from '@shared';
import { YourService } from '@core/services/your.service';
@Component({
selector: 'app-your-component',
standalone: true,
imports: [SHARED_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="component-container">
@if (loading()) {
<nz-spin nzSimple />
} @else if (hasError()) {
<nz-alert
nzType="error"
[nzMessage]="errorMessage()!"
nzShowIcon
/>
} @else {
<div class="content">
@for (item of items(); track item.id) {
<app-item-card
[item]="item"
(itemChange)="handleItemChange($event)"
/>
} @empty {
<nz-empty nzNotFoundContent="未找到项目" />
}
</div>
}
</div>
`,
styles: [`
.component-container {
padding: 24px;
}
.content {
display: grid;
gap: 16px;
}
`]
})
export class YourComponent {
// ✅ 使用 inject() 注入服务
private yourService = inject(YourService);
// ✅ 使用 input() 作为属性(非 @Input())
blueprintId = input.required<string>();
readonly = input(false);
// ✅ 使用 output() 作为事件(非 @Output())
itemChange = output<Item>();
// ✅ 使用 signal() 作为可变状态
loading = signal(false);
error = signal<string | null>(null);
items = signal<Item[]>([]);
// ✅ 使用 computed() 作为派生状态
hasError = computed(() => this.error() !== null);
errorMessage = computed(() => this.error());
totalItems = computed(() => this.items().length);
// ✅ 使用 effect() 处理副作用
constructor() {
effect(() => {
const id = this.blueprintId();
console.log('蓝图ID已更改:', id);
this.loadItems(id);
});
}
ngOnInit(): void {
this.loadItems(this.blueprintId());
}
async loadItems(blueprintId: string): Promise<void> {
this.loading.set(true);
this.error.set(null);
try {
const items = await this.yourService.getItems(blueprintId);
this.items.set(items);
} catch (err) {
this.error.set(err instanceof Error ? err.message : '未知错误');
} finally {
this.loading.set(false);
}
}
handleItemChange(item: Item): void {
// 更新本地状态
this.items.update(items =>
items.map(i => i.id === item.id ? item : i)
);
// 向父组件发出事件
this.itemChange.emit(item);
}
}
关键模式
1. Signal 状态管理
// 可写信号
private _items = signal<Item[]>([]);
// 只读公共访问
items = this._items.asReadonly();
// 计算派生状态
filteredItems = computed(() =>
this._items().filter(item => item.status === 'active')
);
// 更新信号
this._items.set([...]); // 替换
this._items.update(items => [...items, newItem]); // 转换
2. 控制流语法
// ✅ 正确:新的 @if 语法
@if (condition()) {
<div>内容</div>
} @else if (otherCondition()) {
<div>其他</div>
} @else {
<div>默认</div>
}
// ✅ 正确:新的 @for 语法,带 track
@for (item of items(); track item.id) {
<div>{{ item.name }}</div>
} @empty {
<p>无项目</p>
}
// ✅ 正确:新的 @switch 语法
@switch (status()) {
@case ('active') { <span class="badge-success">活跃</span> }
@case ('inactive') { <span class="badge-danger">非活跃</span> }
@default { <span class="badge-default">未知</span> }
}
// ❌ 错误:旧语法(禁止使用)
<div *ngIf="condition">...</div>
<div *ngFor="let item of items">...</div>
<div [ngSwitch]="status">...</div>
3. Input/Output 函数
// ✅ 正确:使用 input()/output() 函数
task = input.required<Task>();
readonly = input(false);
taskChange = output<Task>();
// ❌ 错误:装饰器(禁止使用)
@Input() task!: Task;
@Output() taskChange = new EventEmitter<Task>();
4. 依赖注入
// ✅ 正确:使用 inject()
private taskService = inject(TaskService);
private router = inject(Router);
private destroyRef = inject(DestroyRef);
// ❌ 错误:构造函数注入(禁止使用)
constructor(
private taskService: TaskService,
private router: Router
) {}
5. 订阅
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
// ✅ 正确:使用 takeUntilDestroyed 自动清理
private destroyRef = inject(DestroyRef);
ngOnInit(): void {
this.service.data$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(data => this.items.set(data));
}
// ❌ 错误:手动订阅且无清理
ngOnInit(): void {
this.service.data$.subscribe(data => this.items.set(data));
}
组件类型
智能组件(容器)
@Component({
selector: 'app-task-list',
standalone: true,
imports: [SHARED_IMPORTS, TaskItemComponent],
template: `
@for (task of tasks(); track task.id) {
<app-task-item
[task]="task"
(taskChange)="updateTask($event)"
/>
}
`
})
export class TaskListComponent {
private taskService = inject(TaskService);
tasks = signal<Task[]>([]);
ngOnInit(): void {
this.loadTasks();
}
async loadTasks(): Promise<void> {
const tasks = await this.taskService.getTasks();
this.tasks.set(tasks);
}
async updateTask(task: Task): Promise<void> {
await this.taskService.updateTask(task.id, task);
this.tasks.update(tasks =>
tasks.map(t => t.id === task.id ? task : t)
);
}
}
展示组件(纯组件)
@Component({
selector: 'app-task-item',
standalone: true,
imports: [SHARED_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<nz-card>
<h3>{{ task().title }}</h3>
<p>{{ task().description }}</p>
<button nz-button (click)="handleComplete()">
完成
</button>
</nz-card>
`
})
export class TaskItemComponent {
task = input.required<Task>();
taskChange = output<Task>();
handleComplete(): void {
const updated = { ...this.task(), status: 'completed' };
this.taskChange.emit(updated);
}
}
表单处理
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-task-form',
standalone: true,
imports: [SHARED_IMPORTS, ReactiveFormsModule],
template: `
<form nz-form [formGroup]="form" (ngSubmit)="handleSubmit()">
<nz-form-item>
<nz-form-label nzRequired>标题</nz-form-label>
<nz-form-control nzErrorTip="请输入任务标题">
<input nz-input formControlName="title" />
</nz-form-control>
</nz-form-item>
<button nz-button nzType="primary" [disabled]="!form.valid">
提交
</button>
</form>
`
})
export class TaskFormComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
title: ['', [Validators.required, Validators.maxLength(200)]],
description: [''],
status: ['pending']
});
taskSubmit = output<Partial<Task>>();
handleSubmit(): void {
if (this.form.valid) {
this.taskSubmit.emit(this.form.value);
this.form.reset();
}
}
}
检查清单
创建组件时:
- [ ] 具有导入的独立组件
- [ ] 使用 signal() 管理状态
- [ ] 使用 computed() 管理派生状态
- [ ] 使用 input()/output() 函数
- [ ] 使用 @if/@for/@switch 语法
- [ ] 使用 inject() 进行依赖注入
- [ ] OnPush 变更检测
- [ ] 组件中无业务逻辑
- [ ] 适当的错误处理
- [ ] 加载状态
- [ ] 空状态
- [ ] TypeScript 严格类型