Agent skill
angular-component
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.
Install this agent skill to your Project
npx add-skill https://github.com/aiskillstore/marketplace/tree/main/skills/analogjs/angular-component
SKILL.md
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 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
// Required - must be provided by parent
name = input.required<string>();
// Optional with default value
count = input(0);
// Optional without default (undefined allowed)
label = input<string>();
// 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<void>();
selected = output<Item>();
// With alias
valueChange = output<number>({ alias: 'change' });
// From Observable (for RxJS interop)
scroll$ = new Subject<number>();
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 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
@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
import { 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
@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.
<!-- 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:
<!-- 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:
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 Hero {
imageUrl = input.required<string>();
}
For detailed patterns, see references/component-patterns.md.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
perigon-backend
Perigon ASP.NET Core + EF Core + Aspire conventions
perigon-agent
Pointers for Copilot/agents to apply Perigon conventions
perigon-angular
Angular 21+ standalone/Material/signal conventions for Perigon WebApp
fastapi-mastery
Comprehensive FastAPI development skill covering REST API creation, routing, request/response handling, validation, authentication, database integration, middleware, and deployment. Use when working with FastAPI projects, building APIs, implementing CRUD operations, setting up authentication/authorization, integrating databases (SQL/NoSQL), adding middleware, handling WebSockets, or deploying FastAPI applications. Triggered by requests involving .py files with FastAPI code, API endpoint creation, Pydantic models, or FastAPI-specific features.
context7-efficient
Token-efficient library documentation fetcher using Context7 MCP with 86.8% token savings through intelligent shell pipeline filtering. Fetches code examples, API references, and best practices for JavaScript, Python, Go, Rust, and other libraries. Use when users ask about library documentation, need code examples, want API usage patterns, are learning a new framework, need syntax reference, or troubleshooting with library-specific information. Triggers include questions like "Show me React hooks", "How do I use Prisma", "What's the Next.js routing syntax", or any request for library/framework documentation.
browser-use
Browser automation using Playwright MCP. Navigate websites, fill forms, click elements, take screenshots, and extract data. Use when tasks require web browsing, form submission, web scraping, UI testing, or any browser interaction.
Didn't find tool you were looking for?