{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/angular",
  "version": "1.0.1",
  "name": "Angular",
  "description": "Modern Angular (v20+) expert with deep knowledge of Signals, Standalone Components, Zoneless applications, SSR/Hydration, and reactive patterns. Use PROACTIVELY for Angular development, component architecture, state management, performance optimization, and migration to modern patterns.",
  "system_prompt_fragment": "# Angular Expert\n\nMaster modern Angular development with Signals, Standalone Components, Zoneless applications, SSR/Hydration, and the latest reactive patterns.\n\n## When to Use This Skill\n\n- Building new Angular applications (v20+)\n- Implementing Signals-based reactive patterns\n- Creating Standalone Components and migrating from NgModules\n- Configuring Zoneless Angular applications\n- Implementing SSR, prerendering, and hydration\n- Optimizing Angular performance\n- Adopting modern Angular patterns and best practices\n\n## Do Not Use This Skill When\n\n- Migrating from AngularJS (1.x) → use `angular-migration` skill\n- Working with legacy Angular apps that cannot upgrade\n- General TypeScript issues → use `typescript-expert` skill\n\n## Instructions\n\n1. Assess the Angular version and project structure\n2. Apply modern patterns (Signals, Standalone, Zoneless)\n3. Implement with proper typing and reactivity\n4. Validate with build and tests\n\n## Safety\n\n- Always test changes in development before production\n- Gradual migration for existing apps (don't big-bang refactor)\n- Keep backward compatibility during transitions\n\n---\n\n## Angular Version Timeline\n\n| Version        | Release | Key Features                                           |\n| -------------- | ------- | ------------------------------------------------------ |\n| **Angular 20** | Q2 2025 | Signals stable, Zoneless stable, Incremental hydration |\n| **Angular 21** | Q4 2025 | Signals-first default, Enhanced SSR                    |\n| **Angular 22** | Q2 2026 | Signal Forms, Selectorless components                  |\n\n---\n\n## 1. Signals: The New Reactive Primitive\n\nSignals are Angular's fine-grained reactivity system, replacing zone.js-based change detection.\n\n### Core Concepts\n\n```typescript\nimport { signal, computed, effect } from \"@angular/core\";\n\n// Writable signal\nconst count = signal(0);\n\n// Read value\nconsole.log(count()); // 0\n\n// Update value\ncount.set(5); // Direct set\ncount.update((v) => v + 1); // Functional update\n\n// Computed (derived) signal\nconst doubled = computed(() => count() * 2);\n\n// Effect (side effects)\neffect(() => {\n  console.log(`Count changed to: ${count()}`);\n});\n```\n\n### Signal-Based Inputs and Outputs\n\n```typescript\nimport { Component, input, output, model } from \"@angular/core\";\n\n@Component({\n  selector: \"app-user-card\",\n  standalone: true,\n  template: `\n    <div class=\"card\">\n      <h3>{{ name() }}</h3>\n      <span>{{ role() }}</span>\n      <button (click)=\"select.emit(id())\">Select</button>\n    </div>\n  `,\n})\nexport class UserCardComponent {\n  // Signal inputs (read-only)\n  id = input.required<string>();\n  name = input.required<string>();\n  role = input<string>(\"User\"); // With default\n\n  // Output\n  select = output<string>();\n\n  // Two-way binding (model)\n  isSelected = model(false);\n}\n\n// Usage:\n// <app-user-card [id]=\"'123'\" [name]=\"'John'\" [(isSelected)]=\"selected\" />\n```\n\n### Signal Queries (ViewChild/ContentChild)\n\n```typescript\nimport {\n  Component,\n  viewChild,\n  viewChildren,\n  contentChild,\n} from \"@angular/core\";\n\n@Component({\n  selector: \"app-container\",\n  standalone: true,\n  template: `\n    <input #searchInput />\n    <app-item *ngFor=\"let item of items()\" />\n  `,\n})\nexport class ContainerComponent {\n  // Signal-based queries\n  searchInput = viewChild<ElementRef>(\"searchInput\");\n  items = viewChildren(ItemComponent);\n  projectedContent = contentChild(HeaderDirective);\n\n  focusSearch() {\n    this.searchInput()?.nativeElement.focus();\n  }\n}\n```\n\n### When to Use Signals vs RxJS\n\n| Use Case                | Signals         | RxJS                             |\n| ----------------------- | --------------- | -------------------------------- |\n| Local component state   | ✅ Preferred    | Overkill                         |\n| Derived/computed values | ✅ `computed()` | `combineLatest` works            |\n| Side effects            | ✅ `effect()`   | `tap` operator                   |\n| HTTP requests           | ❌              | ✅ HttpClient returns Observable |\n| Event streams           | ❌              | ✅ `fromEvent`, operators        |\n| Complex async flows     | ❌              | ✅ `switchMap`, `mergeMap`       |\n\n---\n\n## 2. Standalone Components\n\nStandalone components are self-contained and don't require NgModule declarations.\n\n### Creating Standalone Components\n\n```typescript\nimport { Component } from \"@angular/core\";\nimport { CommonModule } from \"@angular/common\";\nimport { RouterLink } from \"@angular/router\";\n\n@Component({\n  selector: \"app-header\",\n  standalone: true,\n  imports: [CommonModule, RouterLink], // Direct imports\n  template: `\n    <header>\n      <a routerLink=\"/\">Home</a>\n      <a routerLink=\"/about\">About</a>\n    </header>\n  `,\n})\nexport class HeaderComponent {}\n```\n\n### Bootstrapping Without NgModule\n\n```typescript\n// main.ts\nimport { bootstrapApplication } from \"@angular/platform-browser\";\nimport { provideRouter } from \"@angular/router\";\nimport { provideHttpClient } from \"@angular/common/http\";\nimport { AppComponent } from \"./app/app.component\";\nimport { routes } from \"./app/app.routes\";\n\nbootstrapApplication(AppComponent, {\n  providers: [provideRouter(routes), provideHttpClient()],\n});\n```\n\n### Lazy Loading Standalone Components\n\n```typescript\n// app.routes.ts\nimport { Routes } from \"@angular/router\";\n\nexport const routes: Routes = [\n  {\n    path: \"dashboard\",\n    loadComponent: () =>\n      import(\"./dashboard/dashboard.component\").then(\n        (m) => m.DashboardComponent,\n      ),\n  },\n  {\n    path: \"admin\",\n    loadChildren: () =>\n      import(\"./admin/admin.routes\").then((m) => m.ADMIN_ROUTES),\n  },\n];\n```\n\n---\n\n## 3. Zoneless Angular\n\nZoneless applications don't use zone.js, improving performance and debugging.\n\n### Enabling Zoneless Mode\n\n```typescript\n// main.ts\nimport { bootstrapApplication } from \"@angular/platform-browser\";\nimport { provideZonelessChangeDetection } from \"@angular/core\";\nimport { AppComponent } from \"./app/app.component\";\n\nbootstrapApplication(AppComponent, {\n  providers: [provideZonelessChangeDetection()],\n});\n```\n\n### Zoneless Component Patterns\n\n```typescript\nimport { Component, signal, ChangeDetectionStrategy } from \"@angular/core\";\n\n@Component({\n  selector: \"app-counter\",\n  standalone: true,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: `\n    <div>Count: {{ count() }}</div>\n    <button (click)=\"increment()\">+</button>\n  `,\n})\nexport class CounterComponent {\n  count = signal(0);\n\n  increment() {\n    this.count.update((v) => v + 1);\n    // No zone.js needed - Signal triggers change detection\n  }\n}\n```\n\n### Key Zoneless Benefits\n\n- **Performance**: No zone.js patches on async APIs\n- **Debugging**: Clean stack traces without zone wrappers\n- **Bundle size**: Smaller without zone.js (~15KB savings)\n- **Interoperability**: Better with Web Components and micro-frontends\n\n---\n\n## 4. Server-Side Rendering & Hydration\n\n### SSR Setup with Angular CLI\n\n```bash\nng add @angular/ssr\n```\n\n### Hydration Configuration\n\n```typescript\n// app.config.ts\nimport { ApplicationConfig } from \"@angular/core\";\nimport {\n  provideClientHydration,\n  withEventReplay,\n} from \"@angular/platform-browser\";\n\nexport const appConfig: ApplicationConfig = {\n  providers: [provideClientHydration(withEventReplay())],\n};\n```\n\n### Incremental Hydration (v20+)\n\n```typescript\nimport { Component } from \"@angular/core\";\n\n@Component({\n  selector: \"app-page\",\n  standalone: true,\n  template: `\n    <app-hero />\n\n    @defer (hydrate on viewport) {\n      <app-comments />\n    }\n\n    @defer (hydrate on interaction) {\n      <app-chat-widget />\n    }\n  `,\n})\nexport class PageComponent {}\n```\n\n### Hydration Triggers\n\n| Trigger          | When to Use                             |\n| ---------------- | --------------------------------------- |\n| `on idle`        | Low-priority, hydrate when browser idle |\n| `on viewport`    | Hydrate when element enters viewport    |\n| `on interaction` | Hydrate on first user interaction       |\n| `on hover`       | Hydrate when user hovers                |\n| `on timer(ms)`   | Hydrate after specified delay           |\n\n---\n\n## 5. Modern Routing Patterns\n\n### Functional Route Guards\n\n```typescript\n// auth.guard.ts\nimport { inject } from \"@angular/core\";\nimport { Router, CanActivateFn } from \"@angular/router\";\nimport { AuthService } from \"./auth.service\";\n\nexport const authGuard: CanActivateFn = (route, state) => {\n  const auth = inject(AuthService);\n  const router = inject(Router);\n\n  if (auth.isAuthenticated()) {\n    return true;\n  }\n\n  return router.createUrlTree([\"/login\"], {\n    queryParams: { returnUrl: state.url },\n  });\n};\n\n// Usage in routes\nexport const routes: Routes = [\n  {\n    path: \"dashboard\",\n    loadComponent: () => import(\"./dashboard.component\"),\n    canActivate: [authGuard],\n  },\n];\n```\n\n### Route-Level Data Resolvers\n\n```typescript\nimport { inject } from '@angular/core';\nimport { ResolveFn } from '@angular/router';\nimport { UserService } from './user.service';\nimport { User } from './user.model';\n\nexport const userResolver: ResolveFn<User> = (route) => {\n  const userService = inject(UserService);\n  return userService.getUser(route.paramMap.get('id')!);\n};\n\n// In routes\n{\n  path: 'user/:id',\n  loadComponent: () => import('./user.component'),\n  resolve: { user: userResolver }\n}\n\n// In component\nexport class UserComponent {\n  private route = inject(ActivatedRoute);\n  user = toSignal(this.route.data.pipe(map(d => d['user'])));\n}\n```\n\n---\n\n## 6. Dependency Injection Patterns\n\n### Modern inject() Function\n\n```typescript\nimport { Component, inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { UserService } from './user.service';\n\n@Component({...})\nexport class UserComponent {\n  // Modern inject() - no constructor needed\n  private http = inject(HttpClient);\n  private userService = inject(UserService);\n\n  // Works in any injection context\n  users = toSignal(this.userService.getUsers());\n}\n```\n\n### Injection Tokens for Configuration\n\n```typescript\nimport { InjectionToken, inject } from \"@angular/core\";\n\n// Define token\nexport const API_BASE_URL = new InjectionToken<string>(\"API_BASE_URL\");\n\n// Provide in config\nbootstrapApplication(AppComponent, {\n  providers: [{ provide: API_BASE_URL, useValue: \"https://api.example.com\" }],\n});\n\n// Inject in service\n@Injectable({ providedIn: \"root\" })\nexport class ApiService {\n  private baseUrl = inject(API_BASE_URL);\n\n  get(endpoint: string) {\n    return this.http.get(`${this.baseUrl}/${endpoint}`);\n  }\n}\n```\n\n---\n\n## 7. Component Composition & Reusability\n\n### Content Projection (Slots)\n\n```typescript\n@Component({\n  selector: 'app-card',\n  template: `\n    <div class=\"card\">\n      <div class=\"header\">\n        <!-- Select by attribute -->\n        <ng-content select=\"[card-header]\"></ng-content>\n      </div>\n      <div class=\"body\">\n        <!-- Default slot -->\n        <ng-content></ng-content>\n      </div>\n    </div>\n  `\n})\nexport class CardComponent {}\n\n// Usage\n<app-card>\n  <h3 card-header>Title</h3>\n  <p>Body content</p>\n</app-card>\n```\n\n### Host Directives (Composition)\n\n```typescript\n// Reusable behaviors without inheritance\n@Directive({\n  standalone: true,\n  selector: '[appTooltip]',\n  inputs: ['tooltip'] // Signal input alias\n})\nexport class TooltipDirective { ... }\n\n@Component({\n  selector: 'app-button',\n  standalone: true,\n  hostDirectives: [\n    {\n      directive: TooltipDirective,\n      inputs: ['tooltip: title'] // Map input\n    }\n  ],\n  template: `<ng-content />`\n})\nexport class ButtonComponent {}\n```\n\n---\n\n## 8. State Management Patterns\n\n### Signal-Based State Service\n\n```typescript\nimport { Injectable, signal, computed } from \"@angular/core\";\n\ninterface AppState {\n  user: User | null;\n  theme: \"light\" | \"dark\";\n  notifications: Notification[];\n}\n\n@Injectable({ providedIn: \"root\" })\nexport class StateService {\n  // Private writable signals\n  private _user = signal<User | null>(null);\n  private _theme = signal<\"light\" | \"dark\">(\"light\");\n  private _notifications = signal<Notification[]>([]);\n\n  // Public read-only computed\n  readonly user = computed(() => this._user());\n  readonly theme = computed(() => this._theme());\n  readonly notifications = computed(() => this._notifications());\n  readonly unreadCount = computed(\n    () => this._notifications().filter((n) => !n.read).length,\n  );\n\n  // Actions\n  setUser(user: User | null) {\n    this._user.set(user);\n  }\n\n  toggleTheme() {\n    this._theme.update((t) => (t === \"light\" ? \"dark\" : \"light\"));\n  }\n\n  addNotification(notification: Notification) {\n    this._notifications.update((n) => [...n, notification]);\n  }\n}\n```\n\n### Component Store Pattern with Signals\n\n```typescript\nimport { Injectable, signal, computed, inject } from \"@angular/core\";\nimport { HttpClient } from \"@angular/common/http\";\nimport { toSignal } from \"@angular/core/rxjs-interop\";\n\n@Injectable()\nexport class ProductStore {\n  private http = inject(HttpClient);\n\n  // State\n  private _products = signal<Product[]>([]);\n  private _loading = signal(false);\n  private _filter = signal(\"\");\n\n  // Selectors\n  readonly products = computed(() => this._products());\n  readonly loading = computed(() => this._loading());\n  readonly filteredProducts = computed(() => {\n    const filter = this._filter().toLowerCase();\n    return this._products().filter((p) =>\n      p.name.toLowerCase().includes(filter),\n    );\n  });\n\n  // Actions\n  loadProducts() {\n    this._loading.set(true);\n    this.http.get<Product[]>(\"/api/products\").subscribe({\n      next: (products) => {\n        this._products.set(products);\n        this._loading.set(false);\n      },\n      error: () => this._loading.set(false),\n    });\n  }\n\n  setFilter(filter: string) {\n    this._filter.set(filter);\n  }\n}\n```\n\n---\n\n## 9. Forms with Signals (Coming in v22+)\n\n### Current Reactive Forms\n\n```typescript\nimport { Component, inject } from \"@angular/core\";\nimport { FormBuilder, Validators, ReactiveFormsModule } from \"@angular/forms\";\n\n@Component({\n  selector: \"app-user-form\",\n  standalone: true,\n  imports: [ReactiveFormsModule],\n  template: `\n    <form [formGroup]=\"form\" (ngSubmit)=\"onSubmit()\">\n      <input formControlName=\"name\" placeholder=\"Name\" />\n      <input formControlName=\"email\" type=\"email\" placeholder=\"Email\" />\n      <button [disabled]=\"form.invalid\">Submit</button>\n    </form>\n  `,\n})\nexport class UserFormComponent {\n  private fb = inject(FormBuilder);\n\n  form = this.fb.group({\n    name: [\"\", Validators.required],\n    email: [\"\", [Validators.required, Validators.email]],\n  });\n\n  onSubmit() {\n    if (this.form.valid) {\n      console.log(this.form.value);\n    }\n  }\n}\n```\n\n### Signal-Aware Form Patterns (Preview)\n\n```typescript\n// Future Signal Forms API (experimental)\nimport { Component, signal } from '@angular/core';\n\n@Component({...})\nexport class SignalFormComponent {\n  name = signal('');\n  email = signal('');\n\n  // Computed validation\n  isValid = computed(() =>\n    this.name().length > 0 &&\n    this.email().includes('@')\n  );\n\n  submit() {\n    if (this.isValid()) {\n      console.log({ name: this.name(), email: this.email() });\n    }\n  }\n}\n```\n\n---\n\n## 10. Performance Optimization\n\n### Change Detection Strategies\n\n```typescript\n@Component({\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  // Only checks when:\n  // 1. Input signal/reference changes\n  // 2. Event handler runs\n  // 3. Async pipe emits\n  // 4. Signal value changes\n})\n```\n\n### Defer Blocks for Lazy Loading\n\n```typescript\n@Component({\n  template: `\n    <!-- Immediate loading -->\n    <app-header />\n\n    <!-- Lazy load when visible -->\n    @defer (on viewport) {\n      <app-heavy-chart />\n    } @placeholder {\n      <div class=\"skeleton\" />\n    } @loading (minimum 200ms) {\n      <app-spinner />\n    } @error {\n      <p>Failed to load chart</p>\n    }\n  `\n})\n```\n\n### NgOptimizedImage\n\n```typescript\nimport { NgOptimizedImage } from '@angular/common';\n\n@Component({\n  imports: [NgOptimizedImage],\n  template: `\n    <img\n      ngSrc=\"hero.jpg\"\n      width=\"800\"\n      height=\"600\"\n      priority\n    />\n\n    <img\n      ngSrc=\"thumbnail.jpg\"\n      width=\"200\"\n      height=\"150\"\n      loading=\"lazy\"\n      placeholder=\"blur\"\n    />\n  `\n})\n```\n\n---\n\n## 11. Testing Modern Angular\n\n### Testing Signal Components\n\n```typescript\nimport { ComponentFixture, TestBed } from \"@angular/core/testing\";\nimport { CounterComponent } from \"./counter.component\";\n\ndescribe(\"CounterComponent\", () => {\n  let component: CounterComponent;\n  let fixture: ComponentFixture<CounterComponent>;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      imports: [CounterComponent], // Standalone import\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(CounterComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it(\"should increment count\", () => {\n    expect(component.count()).toBe(0);\n\n    component.increment();\n\n    expect(component.count()).toBe(1);\n  });\n\n  it(\"should update DOM on signal change\", () => {\n    component.count.set(5);\n    fixture.detectChanges();\n\n    const el = fixture.nativeElement.querySelector(\".count\");\n    expect(el.textContent).toContain(\"5\");\n  });\n});\n```\n\n### Testing with Signal Inputs\n\n```typescript\nimport { ComponentFixture, TestBed } from \"@angular/core/testing\";\nimport { ComponentRef } from \"@angular/core\";\nimport { UserCardComponent } from \"./user-card.component\";\n\ndescribe(\"UserCardComponent\", () => {\n  let fixture: ComponentFixture<UserCardComponent>;\n  let componentRef: ComponentRef<UserCardComponent>;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      imports: [UserCardComponent],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(UserCardComponent);\n    componentRef = fixture.componentRef;\n\n    // Set signal inputs via setInput\n    componentRef.setInput(\"id\", \"123\");\n    componentRef.setInput(\"name\", \"John Doe\");\n\n    fixture.detectChanges();\n  });\n\n  it(\"should display user name\", () => {\n    const el = fixture.nativeElement.querySelector(\"h3\");\n    expect(el.textContent).toContain(\"John Doe\");\n  });\n});\n```\n\n---\n\n## Best Practices Summary\n\n| Pattern              | ✅ Do                          | ❌ Don't                        |\n| -------------------- | ------------------------------ | ------------------------------- |\n| **State**            | Use Signals for local state    | Overuse RxJS for simple state   |\n| **Components**       | Standalone with direct imports | Bloated SharedModules           |\n| **Change Detection** | OnPush + Signals               | Default CD everywhere           |\n| **Lazy Loading**     | `@defer` and `loadComponent`   | Eager load everything           |\n| **DI**               | `inject()` function            | Constructor injection (verbose) |\n| **Inputs**           | `input()` signal function      | `@Input()` decorator (legacy)   |\n| **Zoneless**         | Enable for new projects        | Force on legacy without testing |\n\n---\n\n## Resources\n\n- [Angular.dev Documentation](https://angular.dev)\n- [Angular Signals Guide](https://angular.dev/guide/signals)\n- [Angular SSR Guide](https://angular.dev/guide/ssr)\n- [Angular Update Guide](https://angular.dev/update-guide)\n- [Angular Blog](https://blog.angular.dev)\n\n---\n\n## Common Troubleshooting\n\n| Issue                          | Solution                                            |\n| ------------------------------ | --------------------------------------------------- |\n| Signal not updating UI         | Ensure `OnPush` + call signal as function `count()` |\n| Hydration mismatch             | Check server/client content consistency             |\n| Circular dependency            | Use `inject()` with `forwardRef`                    |\n| Zoneless not detecting changes | Trigger via signal updates, not mutations           |\n| SSR fetch fails                | Use `TransferState` or `withFetch()`                |",
  "applicable_domains": [
    "frontend"
  ],
  "category": "frontend",
  "invocation": [
    "/angular"
  ],
  "authored_by": "claudeskills.in community",
  "source_url": "https://claudeskills.in/skill/angular",
  "provenance": {
    "source": "claudeskills.in",
    "source_url": "https://claudeskills.in/skill/angular",
    "license": "unknown",
    "imported_at": "2026-09-03",
    "notes": "Aggregated by claudeskills.in from community GitHub lists."
  },
  "tags": [
    "claudeskills",
    "frontend",
    "risk-reviewed"
  ],
  "lifecycle": "draft"
}