Angular20独立组件技能 angular-20-standalone-component

这是一个用于创建Angular 20独立组件的技能指南,专注于现代前端开发模式。它详细介绍了如何使用Signals进行响应式状态管理、最新的input()/output()函数替代传统装饰器、新的@if/@for/@switch控制流语法、基于inject()的依赖注入以及OnPush变更检测策略。该技能适用于搭建高性能、可维护的UI组件,遵循三层架构,实现业务逻辑与表示层的分离。关键词:Angular 20, 独立组件, Signals, 状态管理, 现代前端, 响应式编程, 组件架构, 性能优化。

前端开发 0 次安装 0 次浏览 更新于 2/28/2026

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 严格类型

参考