angular-component

安装量: 5.2K
排名: #578

安装

npx skills add https://github.com/analogjs/angular-skills --skill angular-component

Angular Component

Create standalone components for Angular v20+. Components are standalone by default—do NOT set standalone: true.

Component Structure import { Component, ChangeDetectionStrategy, input, output, computed } from '@angular/core';

@Component({ selector: 'app-user-card', changeDetection: ChangeDetectionStrategy.OnPush, host: { 'class': 'user-card', '[class.active]': 'isActive()', '(click)': 'handleClick()', }, template: <img [src]="avatarUrl()" [alt]="name() + ' avatar'" /> <h2>{{ name() }}</h2> @if (showEmail()) { <p>{{ email() }}</p> }, styles: :host { display: block; } :host.active { border: 2px solid blue; }, }) export class UserCardComponent { // Required input name = input.required();

// Optional input with default email = input(''); showEmail = input(false);

// Input with transform isActive = input(false, { transform: booleanAttribute });

// Computed from inputs avatarUrl = computed(() => https://api.example.com/avatar/${this.name()});

// Output selected = output();

handleClick() { this.selected.emit(this.name()); } }

Signal Inputs // Required - must be provided by parent name = input.required();

// Optional with default value count = input(0);

// Optional without default (undefined allowed) label = input();

// With alias for template binding size = input('medium', { alias: 'buttonSize' });

// With transform function disabled = input(false, { transform: booleanAttribute }); value = input(0, { transform: numberAttribute });

Signal Outputs import { output, outputFromObservable } from '@angular/core';

// Basic output clicked = output(); selected = output();

// With alias valueChange = output({ alias: 'change' });

// From Observable (for RxJS interop) scroll$ = new Subject(); scrolled = outputFromObservable(this.scroll$);

// Emit values this.clicked.emit(); this.selected.emit(item);

Host Bindings

Use the host object in @Component—do NOT use @HostBinding or @HostListener decorators.

@Component({ selector: 'app-button', host: { // Static attributes 'role': 'button',

// Dynamic class bindings
'[class.primary]': 'variant() === "primary"',
'[class.disabled]': 'disabled()',

// Dynamic style bindings
'[style.--btn-color]': 'color()',

// Attribute bindings
'[attr.aria-disabled]': 'disabled()',
'[attr.tabindex]': 'disabled() ? -1 : 0',

// Event listeners
'(click)': 'onClick($event)',
'(keydown.enter)': 'onClick($event)',
'(keydown.space)': 'onClick($event)',

}, template: <ng-content />, }) export class ButtonComponent { variant = input<'primary' | 'secondary'>('primary'); disabled = input(false, { transform: booleanAttribute }); color = input('#007bff');

clicked = output();

onClick(event: Event) { if (!this.disabled()) { this.clicked.emit(); } } }

Content Projection @Component({ selector: 'app-card', template: <header> <ng-content select="[card-header]" /> </header> <main> <ng-content /> </main> <footer> <ng-content select="[card-footer]" /> </footer>, }) export class CardComponent {}

// Usage: // //

Title

//

Main content

// //

Lifecycle Hooks import { AfterContentInit, AfterViewInit, OnDestroy, OnInit, afterNextRender, afterRender } from '@angular/core';

export class MyComponent implements OnInit, AfterContentInit, AfterViewInit, OnDestroy { constructor() { // For DOM manipulation after render (SSR-safe) afterNextRender(() => { // Runs once after first render });

afterRender(() => {
  // Runs after every render
});

}

ngOnInit() { / Component initialized / } ngAfterContentInit() { / Projected content ready / } ngAfterViewInit() { / View children ready / } ngOnDestroy() { / Cleanup / } }

Accessibility Requirements

Components MUST:

Pass AXE accessibility checks Meet WCAG AA standards Include proper ARIA attributes for interactive elements Support keyboard navigation Maintain visible focus indicators @Component({ selector: 'app-toggle', host: { 'role': 'switch', '[attr.aria-checked]': 'checked()', '[attr.aria-label]': 'label()', 'tabindex': '0', '(click)': 'toggle()', '(keydown.enter)': 'toggle()', '(keydown.space)': 'toggle(); $event.preventDefault()', }, template: <span class="toggle-track"><span class="toggle-thumb"></span></span>, }) export class ToggleComponent { label = input.required(); checked = input(false, { transform: booleanAttribute }); checkedChange = output();

toggle() { this.checkedChange.emit(!this.checked()); } }

Template Syntax

Use native control flow—do NOT use ngIf, ngFor, *ngSwitch.

@if (isLoading()) { } @else if (error()) { } @else { }

@for (item of items(); track item.id) { } @empty {

No items found

}

@switch (status()) { @case ('pending') { Pending } @case ('active') { Active } @default { Unknown } }

Class and Style Bindings

Do NOT use ngClass or ngStyle. Use direct bindings:

Single class
Class string
Styled text
With unit

Images

Use NgOptimizedImage for static images:

import { NgOptimizedImage } from '@angular/common';

@Component({ imports: [NgOptimizedImage], template: <img ngSrc="/assets/hero.jpg" width="800" height="600" priority /> <img [ngSrc]="imageUrl()" width="200" height="200" />, }) export class HeroComponent { imageUrl = input.required(); }

For detailed patterns, see references/component-patterns.md.

返回排行榜