
Angular Component
- 1 installs
- 404 repo stars
- Updated August 5, 2026
- aiskillstore/marketplace
This is a copy of angular-component by analogjs - installs and ranking accrue to the original listing.
angular-component is a Claude Code skill that creates modern Angular v20+ standalone components using signal inputs/outputs, OnPush change detection, host bindings, and native control flow.
About
angular-component is a skill for creating modern Angular v20+ standalone components following current best practices. A developer uses it to build UI components with signal-based inputs and outputs, OnPush change detection, host bindings, and content projection. It also enforces accessibility requirements like WCAG AA and keyboard support.
- Creates modern Angular v20+ standalone components with signal inputs/outputs
- Uses OnPush change detection, host bindings, content projection, and native control flow
- Enforces WCAG AA accessibility with ARIA and keyboard support
Angular Component by the numbers
- 1 all-time installs (skills.sh)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
angular-component capabilities & compatibility
Free; a code-template skill with no external service or key.
- Capabilities
- angular component generation · signal inputs · accessibility · host bindings
- Use cases
- frontend · ui design
- IDEs
- vscode
- Pricing
- Free
What angular-component says it does
Create standalone components for Angular v20+. Components are standalone by default—do NOT set `standalone: true`.
Use native control flow—do NOT use `*ngIf`, `*ngFor`, `*ngSwitch`.
npx skills add https://github.com/aiskillstore/marketplace --skill angular-componentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 404 |
| Last updated | August 5, 2026 |
| Repository | aiskillstore/marketplace ↗ |
What it does
Build a modern Angular v20+ standalone component using signals, OnPush, and native control flow.
Who is it for?
Angular v20+ developers building signal-based, accessible standalone components.
Skip if: Legacy Angular patterns; it explicitly avoids *ngIf/*ngFor, ngClass/ngStyle, and @HostBinding decorators.
When should I use this skill?
Creating a component, refactoring class-based inputs to signals, adding host bindings, or building accessible interactive components.
What you get
A standalone Angular v20+ component using signals, OnPush, host bindings, and accessible markup is produced.
- Standalone Angular components with signal inputs/outputs
- Accessible, OnPush, host-bound component code
By the numbers
- Targets Angular v20+
- Components MUST meet WCAG AA standards
Files
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.
Angular Component Patterns
Table of Contents
- Model Inputs (Two-Way Binding)
- View Queries
- Content Queries
- Dependency Injection in Components
- Component Communication Patterns
- Dynamic Components
Model Inputs (Two-Way Binding)
For two-way binding with [(value)] syntax:
import { Component, model } from '@angular/core';
@Component({
selector: 'app-slider',
host: {
'(input)': 'onInput($event)',
},
template: `
<input
type="range"
[value]="value()"
[min]="min()"
[max]="max()"
/>
<span>{{ value() }}</span>
`,
})
export class Slider {
// Model creates both input and output
value = model(0);
min = input(0);
max = input(100);
onInput(event: Event) {
const target = event.target as HTMLInputElement;
this.value.set(Number(target.value));
}
}
// Usage: <app-slider [(value)]="sliderValue" />Required model:
value = model.required<number>();View Queries
Query elements and components in the template:
import { Component, viewChild, viewChildren, ElementRef } from '@angular/core';
@Component({
selector: 'app-gallery',
template: `
<div #container class="gallery">
@for (image of images(); track image.id) {
<app-image-card [image]="image" />
}
</div>
`,
})
export class Gallery {
images = input.required<Image[]>();
// Query single element
container = viewChild.required<ElementRef<HTMLDivElement>>('container');
// Query single component (optional)
firstCard = viewChild(ImageCard);
// Query all matching components
allCards = viewChildren(ImageCard);
}Content Queries
Query projected content:
import { Component, contentChild, contentChildren, effect, signal } from '@angular/core';
@Component({
selector: 'app-tabs',
template: `
<div class="tab-headers">
@for (tab of tabs(); track tab.label()) {
<button
[class.active]="tab === activeTab()"
(click)="selectTab(tab)"
>
{{ tab.label() }}
</button>
}
</div>
<div class="tab-content">
<ng-content />
</div>
`,
})
export class Tabs {
// Query all projected Tab children
tabs = contentChildren(Tab);
// Query single projected element
header = contentChild('tabHeader');
activeTab = signal<Tab | undefined>(undefined);
constructor() {
// Set first tab as active when tabs are available
effect(() => {
const firstTab = this.tabs()[0];
if (firstTab && !this.activeTab()) {
this.activeTab.set(firstTab);
}
});
}
selectTab(tab: Tab) {
this.activeTab.set(tab);
}
}
@Component({
selector: 'app-tab',
template: `<ng-content />`,
host: {
'[class.active]': 'isActive()',
'[style.display]': 'isActive() ? "block" : "none"',
},
})
export class Tab {
label = input.required<string>();
isActive = input(false);
}Dependency Injection in Components
Use inject() function instead of constructor injection:
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-dashboard',
template: `...`,
})
export class Dashboard {
private router = inject(Router);
private userService = inject(User);
private config = inject(APP_CONFIG);
// Optional injection
private analytics = inject(Analytics, { optional: true });
// Self-only injection
private localService = inject(Local, { self: true });
navigateToProfile() {
this.router.navigate(['/profile']);
}
}Component Communication Patterns
Parent to Child (Inputs)
// Parent
@Component({
template: `<app-child [data]="parentData()" [config]="config" />`,
})
export class Parent {
parentData = signal({ name: 'Test' });
config = { theme: 'dark' };
}
// Child
@Component({ selector: 'app-child' })
export class Child {
data = input.required<Data>();
config = input<Config>();
}Child to Parent (Outputs)
// Child
@Component({
selector: 'app-child',
template: `<button (click)="save()">Save</button>`,
})
export class Child {
saved = output<Data>();
save() {
this.saved.emit({ id: 1, name: 'Item' });
}
}
// Parent
@Component({
template: `<app-child (saved)="onSaved($event)" />`,
})
export class Parent {
onSaved(data: Data) {
console.log('Saved:', data);
}
}Shared Service Pattern
// Shared state service
@Injectable({ providedIn: 'root' })
export class Cart {
private items = signal<CartItem[]>([]);
readonly items$ = this.items.asReadonly();
readonly total = computed(() =>
this.items().reduce((sum, item) => sum + item.price, 0)
);
addItem(item: CartItem) {
this.items.update(items => [...items, item]);
}
removeItem(id: string) {
this.items.update(items => items.filter(i => i.id !== id));
}
}
// Component A
@Component({ template: `<button (click)="add()">Add</button>` })
export class Product {
private cart = inject(Cart);
product = input.required<Product>();
add() {
this.cart.addItem({ ...this.product(), quantity: 1 });
}
}
// Component B
@Component({ template: `<span>Total: {{ cart.total() }}</span>` })
export class CartSummary {
cart = inject(Cart);
}Dynamic Components
Using @defer for lazy loading:
@Component({
template: `
@defer (on viewport) {
<app-heavy-chart [data]="chartData()" />
} @placeholder {
<div class="chart-placeholder">Loading chart...</div>
} @loading (minimum 500ms) {
<app-spinner />
} @error {
<p>Failed to load chart</p>
}
`,
})
export class Dashboard {
chartData = input.required<ChartData>();
}Defer triggers:
on viewport- When element enters viewporton idle- When browser is idleon interaction- On user interaction (click, focus)on hover- On mouse hoveron immediate- Immediately after non-deferred contenton timer(500ms)- After specified delaywhen condition- When expression becomes true
@Component({
template: `
@defer (on interaction; prefetch on idle) {
<app-comments [postId]="postId()" />
} @placeholder {
<button>Load Comments</button>
}
`,
})
export class Post {
postId = input.required<string>();
}Attribute Directives on Components
@Directive({
selector: '[appHighlight]',
host: {
'[style.backgroundColor]': 'color()',
},
})
export class Highlight {
color = input('yellow', { alias: 'appHighlight' });
}
// Usage on component
@Component({
imports: [Highlight],
template: `<app-card appHighlight="lightblue" />`,
})
export class Page {}Error Boundaries
@Component({
selector: 'app-error-boundary',
template: `
@if (hasError()) {
<div class="error">
<h3>Something went wrong</h3>
<button (click)="retry()">Retry</button>
</div>
} @else {
<ng-content />
}
`,
})
export class ErrorBoundary {
hasError = signal(false);
private errorHandler = inject(ErrorHandler);
retry() {
this.hasError.set(false);
}
}{
"schema_version": "2.0",
"meta": {
"generated_at": "2026-02-15T08:42:04.659Z",
"slug": "analogjs-angular-component",
"source_url": "https://github.com/analogjs/angular-skills/tree/main/skills/angular-component/",
"source_ref": "main",
"model": "claude",
"analysis_version": "3.0.0",
"source_type": "community",
"content_hash": "1389b79cb630e4f67574ffc073d89eb80fa2e64d93e4b81a873e67298a21d72d",
"tree_hash": "92ac820f624dcd8a7ace242009b60bae2b38a0530036eccfc9d11a14c7ff95ab"
},
"skill": {
"name": "angular-component",
"description": "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.",
"summary": "Create modern Angular standalone components with signals, OnPush change detection, and v20+ patterns",
"icon": "📦",
"version": "1.0.0",
"author": "analogjs",
"license": "MIT",
"tags": [
"angular",
"components",
"signals",
"frontend",
"web-development"
],
"supported_tools": [
"claude",
"codex",
"claude-code"
],
"risk_factors": []
},
"security_audit": {
"risk_level": "safe",
"is_blocked": false,
"safe_to_publish": true,
"summary": "All static findings are false positives. The skill contains legitimate Angular v20+ component documentation with signal-based inputs, outputs, host bindings, content projection, and lifecycle hooks. The 91 flagged patterns are markdown code fences for TypeScript examples, not actual shell commands or security vulnerabilities.",
"critical_findings": [],
"high_findings": [],
"medium_findings": [],
"low_findings": [
{
"title": "External Commands False Positive",
"description": "Static analyzer flagged markdown backticks as shell command execution. These are code fences for TypeScript/HTML examples in documentation, not actual command execution.",
"locations": [
{
"file": "SKILL.md",
"line_start": 8,
"line_end": 286
},
{
"file": "references/component-patterns.md",
"line_start": 13,
"line_end": 358
}
],
"verdict": "FALSE_POSITIVE",
"confidence": 0.95,
"confidence_reasoning": "The backticks delimit TypeScript code blocks in markdown documentation, not shell commands"
},
{
"title": "Hardcoded URL False Positive",
"description": "Example URL in documentation flagged as hardcoded URL. This is a legitimate example domain for avatar URL construction.",
"locations": [
{
"file": "SKILL.md",
"line_start": 47,
"line_end": 47
}
],
"verdict": "FALSE_POSITIVE",
"confidence": 0.95,
"confidence_reasoning": "Example URL uses api.example.com which is a standard documentation placeholder"
},
{
"title": "Weak Cryptographic Algorithm False Positive",
"description": "Analyzer incorrectly flagged lifecycle method names and import statements as weak crypto algorithms.",
"locations": [
{
"file": "SKILL.md",
"line_start": 3,
"line_end": 188
}
],
"verdict": "FALSE_POSITIVE",
"confidence": 0.98,
"confidence_reasoning": "Keywords like 'init', 'destroy' matched incorrectly - no cryptographic code present"
},
{
"title": "System Reconnaissance False Positive",
"description": "Analyzer incorrectly flagged Angular component patterns as system reconnaissance.",
"locations": [
{
"file": "SKILL.md",
"line_start": 32,
"line_end": 32
},
{
"file": "references/component-patterns.md",
"line_start": 244,
"line_end": 244
}
],
"verdict": "FALSE_POSITIVE",
"confidence": 0.98,
"confidence_reasoning": "Angular dependency injection patterns flagged incorrectly - legitimate framework code"
}
],
"dangerous_patterns": [],
"files_scanned": 2,
"total_lines": 648,
"audit_model": "claude",
"audited_at": "2026-02-15T08:42:04.659Z",
"risk_factors": [],
"risk_factor_evidence": []
},
"content": {
"user_title": "Build Angular v20+ Standalone Components",
"value_statement": "Create high-performance Angular components with signal-based reactivity, OnPush change detection, and modern v20 patterns for optimal rendering performance.",
"seo_keywords": [
"Angular",
"Angular components",
"Angular signals",
"Claude",
"Codex",
"Claude Code",
"standalone components",
"OnPush change detection",
"Angular v20",
"signal inputs"
],
"actual_capabilities": [
"Create standalone Angular components with signal-based inputs and outputs",
"Implement OnPush change detection strategy for optimal performance",
"Use host bindings for component metadata without decorators",
"Build accessible components with ARIA attributes and keyboard support",
"Implement content projection with multi-slot support",
"Apply modern control flow syntax (@if, @for, @switch)"
],
"limitations": [
"Does not generate complete application scaffolding",
"Does not include routing configuration",
"Does not provide state management solutions like NgRx",
"Requires Angular v17+ for signal-based APIs"
],
"use_cases": [
{
"title": "Build reusable UI component library",
"description": "Create a set of consistent, well-typed components following Angular best practices for use across multiple projects",
"target_user": "Frontend developers building design systems"
},
{
"title": "Refactor legacy components to signals",
"description": "Convert class-based @Input/@Output to modern signal-based inputs/outputs for better performance",
"target_user": "Angular developers migrating to v17+"
},
{
"title": "Implement accessible interactive components",
"description": "Build form controls and interactive elements with proper ARIA support and keyboard navigation",
"target_user": "Accessibility-focused developers"
}
],
"prompt_templates": [
{
"title": "Create basic component",
"prompt": "Create an Angular standalone component called [ComponentName] with required input [inputName] of type [Type], optional input [optInput] with default value [default], and an output for [eventName].",
"scenario": "Generating a simple component with inputs and outputs"
},
{
"title": "Build form control component",
"prompt": "Create an Angular component with two-way binding using model() for [value]. Include validation, error messages, and accessibility attributes.",
"scenario": "Building reusable form control with validation"
},
{
"title": "Implement complex content projection",
"prompt": "Create a card component with header, body, and footer content projection slots. Use ng-content with selectors for each slot.",
"scenario": "Creating components with multiple projection areas"
},
{
"title": "Build lazy-loaded feature component",
"prompt": "Create a dashboard component that uses @defer to lazy-load a heavy chart component when it enters the viewport.",
"scenario": "Implementing deferred loading for performance"
}
],
"output_examples": [
{
"input": "Create a button component with primary/secondary variants, disabled state, and click output",
"output": "Component with signal inputs for variant and disabled, host bindings for classes, and output for click events"
},
{
"input": "Build a tabs component with projected tab children and active tab tracking",
"output": "Component using contentChildren to query projected tabs, signal for active state, and effect for initialization"
},
{
"input": "Create a slider with two-way binding using model",
"output": "Component with model() for value, host listener for input events, and computed properties for min/max"
}
],
"best_practices": [
"Use signal-based inputs and outputs instead of class-based @Input/@Output decorators",
"Always use OnPush change detection strategy for optimal performance",
"Use host object instead of @HostBinding and @HostListener decorators",
"Implement accessibility with proper ARIA attributes and keyboard support"
],
"anti_patterns": [
"Do NOT use standalone: true in Angular v20+ (components are standalone by default)",
"Do NOT use *ngIf, *ngFor, *ngSwitch (use @if, @for, @switch control flow)",
"Do NOT use ngClass or ngStyle (use direct class and style bindings)",
"Do NOT use constructor injection (use inject() function instead)"
],
"faq": [
{
"question": "What Angular version is required for this skill?",
"answer": "Angular v17 or later is required for signal-based APIs. Angular v20+ is recommended for the latest patterns."
},
{
"question": "Are the components SSR-compatible?",
"answer": "Yes, the skill includes afterNextRender and afterRender for SSR-safe DOM manipulation."
},
{
"question": "Can I use RxJS with these components?",
"answer": "Yes, use outputFromObservable for RxJS interop with signal outputs."
},
{
"question": "How do I handle two-way binding?",
"answer": "Use the model() function which creates both input and output for [(value)] syntax."
},
{
"question": "Do components support lazy loading?",
"answer": "Yes, use @defer blocks with triggers like on viewport, on idle, on interaction."
},
{
"question": "How do I query elements in the template?",
"answer": "Use viewChild and viewChildren for querying elements and components in the template."
}
]
},
"file_structure": [
{
"name": "references",
"type": "dir",
"path": "references",
"children": [
{
"name": "component-patterns.md",
"type": "file",
"path": "references/component-patterns.md",
"lines": 359
}
]
},
{
"name": "SKILL.md",
"type": "file",
"path": "SKILL.md",
"lines": 289
}
]
}
Related skills
FAQ
Should I set standalone: true?
No; in Angular v20+ components are standalone by default, so do not set standalone: true.
Which control flow should templates use?
Native @if, @for, and @switch, not *ngIf, *ngFor, or *ngSwitch.