npx skills add https://github.com/analogjs/angular-skills --skill angular-componentHow Angular Component fits into a Paperclip company.
Angular Component drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.
Pre-configured AI company — 18 agents, 18 skills, one-time purchase.
SKILL.md288 linesExpandCollapse
---name: angular-componentdescription: Create modern Angular standalone components following v20+ best practices. Use for building UI components with signal-based inputs/outputs, OnPush change detection, host bindings, content projection, and lifecycle hooks. Triggers on component creation, refactoring class-based inputs to signals, adding host bindings, or implementing accessible interactive components.--- # Angular Component Create standalone components for Angular v20+. Components are standalone by default—do NOT set `standalone: true`. ## Component Structure ```typescriptimport { 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 UserCard { // Required input name = input.required<string>(); // Optional input with default email = input<string>(''); 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<string>(); handleClick() { this.selected.emit(this.name()); }}``` ## Signal Inputs ```typescript// Required - must be provided by parentname = input.required<string>(); // Optional with default valuecount = input(0); // Optional without default (undefined allowed)label = input<string>(); // With alias for template bindingsize = input('medium', { alias: 'buttonSize' }); // With transform functiondisabled = input(false, { transform: booleanAttribute });value = input(0, { transform: numberAttribute });``` ## Signal Outputs ```typescriptimport { output, outputFromObservable } from '@angular/core'; // Basic outputclicked = output<void>();selected = output<Item>(); // With aliasvalueChange = output<number>({ alias: 'change' }); // From Observable (for RxJS interop)scroll$ = new Subject<number>();scrolled = outputFromObservable(this.scroll$); // Emit valuesthis.clicked.emit();this.selected.emit(item);``` ## Host Bindings Use the `host` object in `@Component`—do NOT use `@HostBinding` or `@HostListener` decorators. ```typescript@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 Button { variant = input<'primary' | 'secondary'>('primary'); disabled = input(false, { transform: booleanAttribute }); color = input('#007bff'); clicked = output<void>(); onClick(event: Event) { if (!this.disabled()) { this.clicked.emit(); } }}``` ## Content Projection ```typescript@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 Card {} // Usage:// <app-card>// <h2 card-header>Title</h2>// <p>Main content</p>// <button card-footer>Action</button>// </app-card>``` ## Lifecycle Hooks ```typescriptimport { OnDestroy, OnInit, afterNextRender, afterRender } from '@angular/core'; export class My implements OnInit, OnDestroy { constructor() { // For DOM manipulation after render (SSR-safe) afterNextRender(() => { // Runs once after first render }); afterRender(() => { // Runs after every render }); } ngOnInit() { /* Component initialized */ } 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 ```typescript@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 Toggle { label = input.required<string>(); checked = input(false, { transform: booleanAttribute }); checkedChange = output<boolean>(); toggle() { this.checkedChange.emit(!this.checked()); }}``` ## Template Syntax Use native control flow—do NOT use `*ngIf`, `*ngFor`, `*ngSwitch`. ```html<!-- Conditionals -->@if (isLoading()) { <app-spinner />} @else if (error()) { <app-error [message]="error()" />} @else { <app-content [data]="data()" />} <!-- Loops -->@for (item of items(); track item.id) { <app-item [item]="item" />} @empty { <p>No items found</p>} <!-- Switch -->@switch (status()) { @case ('pending') { <span>Pending</span> } @case ('active') { <span>Active</span> } @default { <span>Unknown</span> }}``` ## Class and Style Bindings Do NOT use `ngClass` or `ngStyle`. Use direct bindings: ```html<!-- Class bindings --><div [class.active]="isActive()">Single class</div><div [class]="classString()">Class string</div> <!-- Style bindings --><div [style.color]="textColor()">Styled text</div><div [style.width.px]="width()">With unit</div>``` ## Images Use `NgOptimizedImage` for static images: ```typescriptimport { 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 Hero { imageUrl = input.required<string>();}``` For detailed patterns, see [references/component-patterns.md](references/component-patterns.md).Angular Di
Covers Angular v20+ dependency injection patterns with modern inject() syntax instead of constructor injection. Shows provider configuration at root, component,
Angular Directives
Solid reference for building Angular directives the modern way with v20+ patterns. Covers attribute directives using the new `input()` function and `host` prope
Angular Forms
The angular-forms skill provides developers building Angular v21+ applications with a Signal Forms API for creating type-safe, reactive forms featuring automati