Skip to main content

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.

الانتقال إلى التثبيت

معلومات المصدر

المستودع
mediar-ai/skillhubz
آخر نشاط في المصدر
١ مارس ٢٠٢٦ في ٢٢:٤٦
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٧
التفرعات
٤

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
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.
# Angular Component Create standalone components for Angular v20+. Components are standalone by default—do NOT set `standalone: true`. ## Component Structure ```typescript 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 ```typescript // 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 ```typescript 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. ```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 ```typescript 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 ```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: ```typescript 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](references/component-patterns.md).
عرض على GitHub