AgentSkillsCN

angular-component-inputs

现代基于函数的组件输入与模型,采用 Angular Signals API。触发条件:在定义组件输入时,从装饰器迁移到 Signals 时,使用响应式组件 API 时,或实现双向绑定时。

SKILL.md
--- frontmatter
name: angular-component-inputs
description: >
  Modern function-based component inputs and models using Angular Signals API.
  Trigger: When defining component inputs, when migrating from decorators, when working with reactive component APIs, when implementing two-way binding.
metadata:
  version: "1.0.0"
  author: araujomartin
  angular:
    minVersion: "17.3.0"
    maxVersion: "21.0.0"
    supportedVersions: ["17", "18", "19", "20", "21"]

Version Support

Minimum Angular Version: 17.3.0
Maximum Angular Version: 21.0.0
Supported Versions: 17, 18, 19, 20, 21

Note: Signal inputs (input(), model()) were introduced as developer preview in v17.3. They became stable/production-ready in v19.0.

When to Use

Load this skill when:

  • Defining component or directive inputs
  • Migrating from @Input() decorators to function-based APIs
  • Implementing reactive component interfaces
  • Working with two-way binding using model()
  • Building components with signal-based reactivity

Critical Patterns

Pattern 1: Function-Based Inputs

Introduced in: v17.3 (developer preview) | Stable since: v19.0

Use the input() and input.required() functions instead of the @Input() decorator. These create InputSignal objects that integrate seamlessly with Angular's reactivity system.

typescript
// ✅ GOOD - Function-based inputs
import { Component, input, ChangeDetectionStrategy } from '@angular/core';

interface User {
  id: string;
  name: string;
}

@Component({
  selector: 'app-user-card',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="user-card">
      <h3>{{ user().name }}</h3>
      <p>ID: {{ user().id }}</p>
      @if (disabled()) {
        <span class="badge">Disabled</span>
      }
    </div>
  `
})
export class UserCardComponent {
  // Required input - will error if not provided
  readonly user = input.required<User>();
  
  // Optional input with default value
  readonly disabled = input(false);
  
  // Optional input without default (undefined initially)
  readonly userId = input<string>();
}
typescript
// ❌ BAD - Old decorator-based approach
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-user-card',
  standalone: true,
  template: `...`
})
export class UserCardComponent {
  @Input({ required: true }) user!: User;
  @Input() disabled = false;
  @Input() selected = false;
}

Pattern 2: Two-Way Binding with model()

Introduced in: v17.3 (developer preview) | Stable since: v19.0

Use the model() function for two-way binding. It creates both the input and its "Change" event automatically, replacing the [(value)] two-way binding pattern with decorators.

typescript
// ✅ GOOD - Model for two-way binding
import { Component, model, input, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-custom-checkbox',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <label>
      <input 
        type="checkbox"
        [checked]="checked()"
        (change)="toggle()"
      />
      {{ label() }}
    </label>
  `
})
export class CustomCheckboxComponent {
  readonly label = input.required<string>();
  
  // Creates both "checked" input and its "checkedChange" event
  readonly checked = model(false);
  
  toggle(): void {
    this.checked.set(!this.checked());
  }
}

Usage in parent:

typescript
@Component({
  selector: 'app-parent',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [CustomCheckboxComponent],
  template: `
    <!-- Two-way binding -->
    <app-custom-checkbox 
      label="Accept terms"
      [(checked)]="termsAccepted"
    />
    
    <!-- Or one-way binding -->
    <app-custom-checkbox 
      label="Subscribe"
      [checked]="subscribed"
      (checkedChange)="onSubscribeChange($event)"
    />
  `
})
export class ParentComponent {
  termsAccepted = signal(false);
  subscribed = signal(true);
  
  onSubscribeChange(value: boolean): void {
    this.subscribed.set(value);
  }
}

Pattern 3: Input Aliases

Introduced in: v17.3 (developer preview) | Stable since: v19.0

Use the alias option to provide a different name for the input property in templates while keeping a different name in the component class.

typescript
// ✅ GOOD - Input with alias
import { Component, input, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-user-profile',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <h2>{{ userName() }}</h2>
  `
})
export class UserProfileComponent {
  // Template uses "name", component uses "userName"
  readonly userName = input.required<string>({ alias: 'name' });
}

Usage:

html
<!-- Use the alias in templates -->
<app-user-profile name="John Doe" />

For detailed examples and common mistakes, see references/input-patterns.md.

Quick Reference

TaskPattern
Required inputreadonly user = input.required<User>()
Optional input with defaultreadonly disabled = input(false)
Optional input without defaultreadonly userId = input<string>()
Input with aliasreadonly userName = input.required<string>({ alias: 'name' })
Two-way bindingreadonly checked = model(false)
Required modelreadonly value = model.required<string>()
Access input valuethis.userName()
Update modelthis.checked.set(true)

Resources