angular-core

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Standalone Components (REQUIRED)

独立组件(强制要求)

Components are standalone by default. Do NOT set
standalone: true
.
typescript
@Component({
  selector: 'app-user',
  imports: [CommonModule],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `...`
})
export class UserComponent {}

组件默认是独立的,不要设置
standalone: true
typescript
@Component({
  selector: 'app-user',
  imports: [CommonModule],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `...`
})
export class UserComponent {}

Input/Output Functions (REQUIRED)

输入/输出函数(强制要求)

typescript
// ✅ ALWAYS: Function-based
readonly user = input.required<User>();
readonly disabled = input(false);
readonly selected = output<User>();
readonly checked = model(false);  // Two-way binding

// ❌ NEVER: Decorators
@Input() user: User;
@Output() selected = new EventEmitter<User>();

typescript
// ✅ 推荐写法:基于函数
readonly user = input.required<User>();
readonly disabled = input(false);
readonly selected = output<User>();
readonly checked = model(false);  // 双向绑定

// ❌ 禁止写法:装饰器
@Input() user: User;
@Output() selected = new EventEmitter<User>();

Signals for State (REQUIRED)

使用Signals管理状态(强制要求)

typescript
readonly count = signal(0);
readonly doubled = computed(() => this.count() * 2);

// Update
this.count.set(5);
this.count.update(prev => prev + 1);

// Side effects
effect(() => localStorage.setItem('count', this.count().toString()));

typescript
readonly count = signal(0);
readonly doubled = computed(() => this.count() * 2);

// 更新值
this.count.set(5);
this.count.update(prev => prev + 1);

// 副作用处理
effect(() => localStorage.setItem('count', this.count().toString()));

NO Lifecycle Hooks (REQUIRED)

禁止使用生命周期钩子(强制要求)

Signals replace lifecycle hooks. Do NOT use
ngOnInit
,
ngOnChanges
,
ngOnDestroy
.
typescript
// ❌ NEVER: Lifecycle hooks
ngOnInit() {
  this.loadUser();
}

ngOnChanges(changes: SimpleChanges) {
  if (changes['userId']) {
    this.loadUser();
  }
}

// ✅ ALWAYS: Signals + effect
readonly userId = input.required<string>();
readonly user = signal<User | null>(null);

private userEffect = effect(() => {
  // Runs automatically when userId() changes
  this.loadUser(this.userId());
});

// ✅ For derived data, use computed
readonly displayName = computed(() => this.user()?.name ?? 'Guest');
Signals可替代生命周期钩子,不要使用
ngOnInit
ngOnChanges
ngOnDestroy
typescript
// ❌ 禁止写法:生命周期钩子
ngOnInit() {
  this.loadUser();
}

ngOnChanges(changes: SimpleChanges) {
  if (changes['userId']) {
    this.loadUser();
  }
}

// ✅ 推荐写法:Signals + effect
readonly userId = input.required<string>();
readonly user = signal<User | null>(null);

private userEffect = effect(() => {
  // 当userId()变化时自动执行
  this.loadUser(this.userId());
});

// ✅ 派生数据使用computed
readonly displayName = computed(() => this.user()?.name ?? 'Guest');

When to Use What

场景对应方案

NeedUse
React to input changes
effect()
watching the input signal
Derived/computed state
computed()
Side effects (API calls, localStorage)
effect()
Cleanup on destroy
DestroyRef
+
inject()
typescript
// Cleanup example
private readonly destroyRef = inject(DestroyRef);

constructor() {
  const subscription = someObservable$.subscribe();
  this.destroyRef.onDestroy(() => subscription.unsubscribe());
}

需求推荐使用
响应输入变化
effect()
监听输入signal
派生/计算状态
computed()
副作用处理(API调用、localStorage)
effect()
销毁时清理资源
DestroyRef
+
inject()
typescript
// 资源清理示例
private readonly destroyRef = inject(DestroyRef);

constructor() {
  const subscription = someObservable$.subscribe();
  this.destroyRef.onDestroy(() => subscription.unsubscribe());
}

inject() Over Constructor (REQUIRED)

优先使用inject()而非构造函数(强制要求)

typescript
// ✅ ALWAYS
private readonly http = inject(HttpClient);

// ❌ NEVER
constructor(private http: HttpClient) {}

typescript
// ✅ 推荐写法
private readonly http = inject(HttpClient);

// ❌ 禁止写法
constructor(private http: HttpClient) {}

Native Control Flow (REQUIRED)

原生模板控制流(强制要求)

html
@if (loading()) {
  <spinner />
} @else {
  @for (item of items(); track item.id) {
    <item-card [data]="item" />
  } @empty {
    <p>No items</p>
  }
}

@switch (status()) {
  @case ('active') { <span>Active</span> }
  @default { <span>Unknown</span> }
}

html
@if (loading()) {
  <spinner />
} @else {
  @for (item of items(); track item.id) {
    <item-card [data]="item" />
  } @empty {
    <p>暂无数据</p>
  }
}

@switch (status()) {
  @case ('active') { <span>活跃</span> }
  @default { <span>未知</span> }
}

RxJS - Only When Needed

RxJS:仅在必要时使用

Signals are the default. Use RxJS ONLY for complex async operations.
Use SignalsUse RxJS
Component stateCombining multiple streams
Derived valuesDebounce/throttle
Simple async (single API call)Race conditions
Input/OutputWebSockets, real-time
Complex error retry logic
typescript
// ✅ Simple API call - use signals
readonly user = signal<User | null>(null);
readonly loading = signal(false);

async loadUser(id: string) {
  this.loading.set(true);
  this.user.set(await firstValueFrom(this.http.get<User>(`/api/users/${id}`)));
  this.loading.set(false);
}

// ✅ Complex stream - use RxJS
readonly searchResults$ = this.searchTerm$.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(term => this.http.get<Results>(`/api/search?q=${term}`))
);

// Convert to signal when needed in template
readonly searchResults = toSignal(this.searchResults$, { initialValue: [] });

Signals是默认方案,仅在处理复杂异步操作时使用RxJS。
使用Signals的场景使用RxJS的场景
组件状态管理多流合并
派生值计算防抖/节流
简单异步操作(单次API调用)竞态条件处理
输入/输出处理WebSocket、实时数据
复杂错误重试逻辑
typescript
// ✅ 简单API调用:使用Signals
readonly user = signal<User | null>(null);
readonly loading = signal(false);

async loadUser(id: string) {
  this.loading.set(true);
  this.user.set(await firstValueFrom(this.http.get<User>(`/api/users/${id}`)));
  this.loading.set(false);
}

// ✅ 复杂流处理:使用RxJS
readonly searchResults$ = this.searchTerm$.pipe(
  debounceTime(300),
  distinctUntilChanged(),
  switchMap(term => this.http.get<Results>(`/api/search?q=${term}`))
);

// 模板中需要时转换为signal
readonly searchResults = toSignal(this.searchResults$, { initialValue: [] });

Zoneless Angular (REQUIRED)

无Zone Angular(强制要求)

Angular is zoneless. Use
provideZonelessChangeDetection()
.
typescript
bootstrapApplication(AppComponent, {
  providers: [provideZonelessChangeDetection()]
});
Remove ZoneJS:
bash
npm uninstall zone.js
Remove from
angular.json
polyfills:
zone.js
and
zone.js/testing
.
采用无Zone的Angular,使用
provideZonelessChangeDetection()
typescript
bootstrapApplication(AppComponent, {
  providers: [provideZonelessChangeDetection()]
});
移除ZoneJS:
bash
npm uninstall zone.js
angular.json
的polyfills中移除:
zone.js
zone.js/testing

Zoneless Requirements

无Zone环境要求

  • Use
    OnPush
    change detection
  • Use signals for state (auto-notifies Angular)
  • Use
    AsyncPipe
    for observables
  • Use
    markForCheck()
    when needed

  • 使用
    OnPush
    变更检测策略
  • 使用Signals管理状态(自动通知Angular)
  • 对可观察对象使用
    AsyncPipe
  • 必要时使用
    markForCheck()

Resources

参考资源