skills/7spade/angular-20-standalone-component/SKILL.md
Create Angular 20 standalone components using modern patterns: Signals for state management, input()/output() functions (not decorators), @if/@for/@switch control flow (not *ngIf/*ngFor), inject() dependency injection (not constructor), and OnPush change detection. Use this skill when scaffolding new UI components that need reactive state, form handling, or integration with services following the three-layer architecture.
npx skillsauth add aiskillstore/marketplace angular-20-standalone-componentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill helps create Angular 20 components following modern patterns and project standards.
signal(), computed(), effect() for stateinput(), output(), @if, @for, @switchimport { Component, signal, computed, effect, input, output, inject, ChangeDetectionStrategy } from '@angular/core';
import { SHARED_IMPORTS } from '@shared';
import { YourService } from '@core/services/your.service';
@Component({
selector: 'app-your-component',
standalone: true,
imports: [SHARED_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="component-container">
@if (loading()) {
<nz-spin nzSimple />
} @else if (hasError()) {
<nz-alert
nzType="error"
[nzMessage]="errorMessage()!"
nzShowIcon
/>
} @else {
<div class="content">
@for (item of items(); track item.id) {
<app-item-card
[item]="item"
(itemChange)="handleItemChange($event)"
/>
} @empty {
<nz-empty nzNotFoundContent="No items found" />
}
</div>
}
</div>
`,
styles: [`
.component-container {
padding: 24px;
}
.content {
display: grid;
gap: 16px;
}
`]
})
export class YourComponent {
// ✅ Inject services with inject()
private yourService = inject(YourService);
// ✅ Use input() for properties (NOT @Input())
blueprintId = input.required<string>();
readonly = input(false);
// ✅ Use output() for events (NOT @Output())
itemChange = output<Item>();
// ✅ Use signal() for mutable state
loading = signal(false);
error = signal<string | null>(null);
items = signal<Item[]>([]);
// ✅ Use computed() for derived state
hasError = computed(() => this.error() !== null);
errorMessage = computed(() => this.error());
totalItems = computed(() => this.items().length);
// ✅ Use effect() for side effects
constructor() {
effect(() => {
const id = this.blueprintId();
console.log('Blueprint ID changed:', id);
this.loadItems(id);
});
}
ngOnInit(): void {
this.loadItems(this.blueprintId());
}
async loadItems(blueprintId: string): Promise<void> {
this.loading.set(true);
this.error.set(null);
try {
const items = await this.yourService.getItems(blueprintId);
this.items.set(items);
} catch (err) {
this.error.set(err instanceof Error ? err.message : 'Unknown error');
} finally {
this.loading.set(false);
}
}
handleItemChange(item: Item): void {
// Update local state
this.items.update(items =>
items.map(i => i.id === item.id ? item : i)
);
// Emit to parent
this.itemChange.emit(item);
}
}
// Writable signals
private _items = signal<Item[]>([]);
// Read-only public access
items = this._items.asReadonly();
// Computed derived state
filteredItems = computed(() =>
this._items().filter(item => item.status === 'active')
);
// Update signals
this._items.set([...]); // Replace
this._items.update(items => [...items, newItem]); // Transform
// ✅ CORRECT: New @if syntax
@if (condition()) {
<div>Content</div>
} @else if (otherCondition()) {
<div>Other</div>
} @else {
<div>Default</div>
}
// ✅ CORRECT: New @for syntax with track
@for (item of items(); track item.id) {
<div>{{ item.name }}</div>
} @empty {
<p>No items</p>
}
// ✅ CORRECT: New @switch syntax
@switch (status()) {
@case ('active') { <span class="badge-success">Active</span> }
@case ('inactive') { <span class="badge-danger">Inactive</span> }
@default { <span class="badge-default">Unknown</span> }
}
// ❌ WRONG: Old syntax (forbidden)
<div *ngIf="condition">...</div>
<div *ngFor="let item of items">...</div>
<div [ngSwitch]="status">...</div>
// ✅ CORRECT: Use input()/output() functions
task = input.required<Task>();
readonly = input(false);
taskChange = output<Task>();
// ❌ WRONG: Decorators (forbidden)
@Input() task!: Task;
@Output() taskChange = new EventEmitter<Task>();
// ✅ CORRECT: Use inject()
private taskService = inject(TaskService);
private router = inject(Router);
private destroyRef = inject(DestroyRef);
// ❌ WRONG: Constructor injection (forbidden)
constructor(
private taskService: TaskService,
private router: Router
) {}
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
// ✅ CORRECT: Auto-cleanup with takeUntilDestroyed
private destroyRef = inject(DestroyRef);
ngOnInit(): void {
this.service.data$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(data => this.items.set(data));
}
// ❌ WRONG: Manual subscriptions without cleanup
ngOnInit(): void {
this.service.data$.subscribe(data => this.items.set(data));
}
@Component({
selector: 'app-task-list',
standalone: true,
imports: [SHARED_IMPORTS, TaskItemComponent],
template: `
@for (task of tasks(); track task.id) {
<app-task-item
[task]="task"
(taskChange)="updateTask($event)"
/>
}
`
})
export class TaskListComponent {
private taskService = inject(TaskService);
tasks = signal<Task[]>([]);
ngOnInit(): void {
this.loadTasks();
}
async loadTasks(): Promise<void> {
const tasks = await this.taskService.getTasks();
this.tasks.set(tasks);
}
async updateTask(task: Task): Promise<void> {
await this.taskService.updateTask(task.id, task);
this.tasks.update(tasks =>
tasks.map(t => t.id === task.id ? task : t)
);
}
}
@Component({
selector: 'app-task-item',
standalone: true,
imports: [SHARED_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<nz-card>
<h3>{{ task().title }}</h3>
<p>{{ task().description }}</p>
<button nz-button (click)="handleComplete()">
Complete
</button>
</nz-card>
`
})
export class TaskItemComponent {
task = input.required<Task>();
taskChange = output<Task>();
handleComplete(): void {
const updated = { ...this.task(), status: 'completed' };
this.taskChange.emit(updated);
}
}
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-task-form',
standalone: true,
imports: [SHARED_IMPORTS, ReactiveFormsModule],
template: `
<form nz-form [formGroup]="form" (ngSubmit)="handleSubmit()">
<nz-form-item>
<nz-form-label nzRequired>Title</nz-form-label>
<nz-form-control nzErrorTip="Please enter task title">
<input nz-input formControlName="title" />
</nz-form-control>
</nz-form-item>
<button nz-button nzType="primary" [disabled]="!form.valid">
Submit
</button>
</form>
`
})
export class TaskFormComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
title: ['', [Validators.required, Validators.maxLength(200)]],
description: [''],
status: ['pending']
});
taskSubmit = output<Partial<Task>>();
handleSubmit(): void {
if (this.form.valid) {
this.taskSubmit.emit(this.form.value);
this.form.reset();
}
}
}
When creating a component:
development
Apple Human Interface Guidelines for content display components. Use this skill when the user asks about charts component, collection view, image view, web view, color well, image well, activity view, lockup, data visualization, content display, displaying images, rendering web content, color pickers, or presenting collections of items in Apple apps. Also use when the user says how should I display charts, what's the best way to show images, should I use a web view, how do I build a grid of items, what component shows media, or how do I present a share sheet. Cross-references: hig-foundations for color/typography/accessibility, hig-patterns for data visualization patterns, hig-components-layout for structural containers, hig-platforms for platform-specific component behavior.
tools
Automate HelpDesk tasks via Rube MCP (Composio): list tickets, manage views, use canned responses, and configure custom fields. Always search tools first for current schemas.
testing
Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programming, concurrency, and architecture guidance.
tools
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.