← 返回 Skill 列表
extension
分类: 开发与工程API Key 暂未确认

angular-components

使用信号、输入、输出、生命周期钩子、变更检测、内容投影以及智能/展示模式创建、构建、重构和优化Angular组件。在构建UI组件、修复组件错误或提高性能时使用。

person作者: jakexiaohubgithub

Angular Components

What I Do

  • Create Angular components following modern best practices
  • Implement signal-based inputs, outputs, and model bindings
  • Configure change detection strategies (Default vs OnPush)
  • Set up content projection with ng-content
  • Apply smart/presentational component architecture
  • Use lifecycle hooks correctly (ngOnInit, ngOnDestroy, afterRender)
  • Query child components with viewChild/contentChild signals
  • Fix common errors (ExpressionChangedAfterItHasBeenChecked)

Standalone is the default. As of Angular v17+, all components, directives, and pipes generated by ng generate component are standalone by default. Standalone components declare their own dependencies via the imports array — no NgModule required. NgModule is legacy and only needed when maintaining pre-v17 code or sharing a single module across many bootstrapped entry points. The examples throughout this skill assume standalone.

When to Use Me

Use this skill when you:

  • Create, generate, or scaffold new Angular components
  • Refactor components to use signals and modern APIs
  • Implement parent-child component communication
  • Configure content projection with ng-content slots
  • Optimize components with OnPush change detection
  • Debug lifecycle hook or change detection issues

Component Architecture

Smart vs Presentational Pattern

Presentational - Pure UI, no services, OnPush, input/output only:

@Component({
  selector: 'user-card',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<div class="card"><h3>{{ name() }}</h3></div>`
})
export class UserCardComponent {
  readonly name = input.required<string>();
  readonly select = output<string>();
}

Smart - Inject services, manage state, coordinate children:

@Component({
  selector: 'user-list-page',
  template: `@for (user of users(); track user.id) { <user-card [name]="user.name" /> }`
})
export class UserListPageComponent {
  private userService = inject(UserService);
  users = signal<User[]>([]);
}

Inputs and Outputs

// Signal inputs (recommended)
value = input(0);                                    // With default
userId = input.required<string>();                   // Required
label = input('', { transform: trimString });        // With transform
disabled = input(false, { transform: booleanAttribute }); // Built-in

// Model input (two-way binding)
value = model(0);  // Enables [(value)]="parentValue"

// Signal outputs
saved = output<void>();
valueChanged = output<number>();
this.valueChanged.emit(42);

Lifecycle Hooks

Order: constructor → ngOnChanges → ngOnInit → ngAfterContentInit → ngAfterViewInit

export class MyComponent implements OnInit {
  private destroyRef = inject(DestroyRef);

  ngOnInit() { this.loadData(); }

  constructor() {
    this.destroyRef.onDestroy(() => this.cleanup());
    afterNextRender(() => this.measureElement()); // DOM operations
  }
}

Change Detection

OnPush triggers only when: input reference changes, event occurs, signal changes, or markForCheck() called.

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OptimizedComponent {
  data = signal<Data | null>(null);
  displayName = computed(() => this.data()?.name ?? 'Unknown');
}

Content Projection

@Component({
  selector: 'card',
  template: `
    <ng-content select="[card-header]" />
    <ng-content select="[card-body]" />
    <ng-content>Fallback content</ng-content>
  `
})
export class CardComponent {}

Component Queries

// View queries (template elements)
header = viewChild(HeaderComponent);
header = viewChild.required(HeaderComponent);  // Guaranteed
items = viewChildren(ItemComponent);

// Content queries (projected content)
toggle = contentChild(ToggleComponent);
items = contentChildren(ItemComponent, { descendants: true });

Context7 Integration

For current Angular documentation, use Context7 MCP server:

context7_resolve-library-id: angular
context7_query-docs: libraryId: /angular/angular, query: "signal inputs"

Common Errors

ExpressionChangedAfterItHasBeenChecked - Schedule state changes:

// BAD: Mutating bound state during change detection
ngAfterViewInit() { this.title = 'Changed'; }

// GOOD: Schedule for next tick
constructor() {
  afterNextRender(() => this.title.set('Changed'));
}
// Or refactor to compute value earlier (constructor/initializer)

OnPush not updating - Create new references:

this.items = [...this.items, newItem]; // Not this.items.push()

Memory leaks - Use takeUntilDestroyed or toSignal:

data = toSignal(this.data$);
// Or: this.data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe();

Best Practices

  1. Use signals for component state
  2. Use OnPush for presentational components
  3. Use computed instead of getters
  4. Use inject() over constructor injection
  5. Use readonly for inputs, outputs, queries
  6. Track items in @for with unique IDs

Reactive Forms Integration

@Component({
  template: `<input [formControl]="nameControl">`
})
export class FormComponent {
  nameControl = new FormControl('', Validators.required);

  // Signal from form control value
  name = toSignal(this.nameControl.valueChanges, { initialValue: '' });
}

Router Integration

@Component({...})
export class UserComponent {
  private route = inject(ActivatedRoute);

  // Signal input from route (Angular 18+)
  userId = input.required<string>();  // With withComponentInputBinding()

  // Or from ActivatedRoute
  userId = toSignal(this.route.paramMap.pipe(
    map(params => params.get('id'))
  ));
}

Related Skills

| Skill | Use When | |-------|----------| | angular-state | Application state, stores, RxJS | | angular-testing | Testing components, mocking | | typescript-advanced | Generic components | | nx-workspace | Monorepo organization |

References

| Reference | Description | |-----------|-------------| | research.md | Detailed patterns and examples | | Angular Docs | Official documentation |