Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    sickn33

    angular

    sickn33/angular
    Coding
    8,021
    2 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Modern Angular (v20+) expert with deep knowledge of Signals, Standalone Components, Zoneless applications, SSR/Hydration, and reactive patterns...

    SKILL.md

    Angular Expert

    Master modern Angular development with Signals, Standalone Components, Zoneless applications, SSR/Hydration, and the latest reactive patterns.

    When to Use This Skill

    • Building new Angular applications (v20+)
    • Implementing Signals-based reactive patterns
    • Creating Standalone Components and migrating from NgModules
    • Configuring Zoneless Angular applications
    • Implementing SSR, prerendering, and hydration
    • Optimizing Angular performance
    • Adopting modern Angular patterns and best practices

    Do Not Use This Skill When

    • Migrating from AngularJS (1.x) → use angular-migration skill
    • Working with legacy Angular apps that cannot upgrade
    • General TypeScript issues → use typescript-expert skill

    Instructions

    1. Assess the Angular version and project structure
    2. Apply modern patterns (Signals, Standalone, Zoneless)
    3. Implement with proper typing and reactivity
    4. Validate with build and tests

    Safety

    • Always test changes in development before production
    • Gradual migration for existing apps (don't big-bang refactor)
    • Keep backward compatibility during transitions

    Angular Version Timeline

    Version Release Key Features
    Angular 20 Q2 2025 Signals stable, Zoneless stable, Incremental hydration
    Angular 21 Q4 2025 Signals-first default, Enhanced SSR
    Angular 22 Q2 2026 Signal Forms, Selectorless components

    1. Signals: The New Reactive Primitive

    Signals are Angular's fine-grained reactivity system, replacing zone.js-based change detection.

    Core Concepts

    import { signal, computed, effect } from "@angular/core";
    
    // Writable signal
    const count = signal(0);
    
    // Read value
    console.log(count()); // 0
    
    // Update value
    count.set(5); // Direct set
    count.update((v) => v + 1); // Functional update
    
    // Computed (derived) signal
    const doubled = computed(() => count() * 2);
    
    // Effect (side effects)
    effect(() => {
      console.log(`Count changed to: ${count()}`);
    });
    

    Signal-Based Inputs and Outputs

    import { Component, input, output, model } from "@angular/core";
    
    @Component({
      selector: "app-user-card",
      standalone: true,
      template: `
        <div class="card">
          <h3>{{ name() }}</h3>
          <span>{{ role() }}</span>
          <button (click)="select.emit(id())">Select</button>
        </div>
      `,
    })
    export class UserCardComponent {
      // Signal inputs (read-only)
      id = input.required<string>();
      name = input.required<string>();
      role = input<string>("User"); // With default
    
      // Output
      select = output<string>();
    
      // Two-way binding (model)
      isSelected = model(false);
    }
    
    // Usage:
    // <app-user-card [id]="'123'" [name]="'John'" [(isSelected)]="selected" />
    

    Signal Queries (ViewChild/ContentChild)

    import {
      Component,
      viewChild,
      viewChildren,
      contentChild,
    } from "@angular/core";
    
    @Component({
      selector: "app-container",
      standalone: true,
      template: `
        <input #searchInput />
        <app-item *ngFor="let item of items()" />
      `,
    })
    export class ContainerComponent {
      // Signal-based queries
      searchInput = viewChild<ElementRef>("searchInput");
      items = viewChildren(ItemComponent);
      projectedContent = contentChild(HeaderDirective);
    
      focusSearch() {
        this.searchInput()?.nativeElement.focus();
      }
    }
    

    When to Use Signals vs RxJS

    Use Case Signals RxJS
    Local component state ✅ Preferred Overkill
    Derived/computed values ✅ computed() combineLatest works
    Side effects ✅ effect() tap operator
    HTTP requests ❌ ✅ HttpClient returns Observable
    Event streams ❌ ✅ fromEvent, operators
    Complex async flows ❌ ✅ switchMap, mergeMap

    2. Standalone Components

    Standalone components are self-contained and don't require NgModule declarations.

    Creating Standalone Components

    import { Component } from "@angular/core";
    import { CommonModule } from "@angular/common";
    import { RouterLink } from "@angular/router";
    
    @Component({
      selector: "app-header",
      standalone: true,
      imports: [CommonModule, RouterLink], // Direct imports
      template: `
        <header>
          <a routerLink="/">Home</a>
          <a routerLink="/about">About</a>
        </header>
      `,
    })
    export class HeaderComponent {}
    

    Bootstrapping Without NgModule

    // main.ts
    import { bootstrapApplication } from "@angular/platform-browser";
    import { provideRouter } from "@angular/router";
    import { provideHttpClient } from "@angular/common/http";
    import { AppComponent } from "./app/app.component";
    import { routes } from "./app/app.routes";
    
    bootstrapApplication(AppComponent, {
      providers: [provideRouter(routes), provideHttpClient()],
    });
    

    Lazy Loading Standalone Components

    // app.routes.ts
    import { Routes } from "@angular/router";
    
    export const routes: Routes = [
      {
        path: "dashboard",
        loadComponent: () =>
          import("./dashboard/dashboard.component").then(
            (m) => m.DashboardComponent,
          ),
      },
      {
        path: "admin",
        loadChildren: () =>
          import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
      },
    ];
    

    3. Zoneless Angular

    Zoneless applications don't use zone.js, improving performance and debugging.

    Enabling Zoneless Mode

    // main.ts
    import { bootstrapApplication } from "@angular/platform-browser";
    import { provideZonelessChangeDetection } from "@angular/core";
    import { AppComponent } from "./app/app.component";
    
    bootstrapApplication(AppComponent, {
      providers: [provideZonelessChangeDetection()],
    });
    

    Zoneless Component Patterns

    import { Component, signal, ChangeDetectionStrategy } from "@angular/core";
    
    @Component({
      selector: "app-counter",
      standalone: true,
      changeDetection: ChangeDetectionStrategy.OnPush,
      template: `
        <div>Count: {{ count() }}</div>
        <button (click)="increment()">+</button>
      `,
    })
    export class CounterComponent {
      count = signal(0);
    
      increment() {
        this.count.update((v) => v + 1);
        // No zone.js needed - Signal triggers change detection
      }
    }
    

    Key Zoneless Benefits

    • Performance: No zone.js patches on async APIs
    • Debugging: Clean stack traces without zone wrappers
    • Bundle size: Smaller without zone.js (~15KB savings)
    • Interoperability: Better with Web Components and micro-frontends

    4. Server-Side Rendering & Hydration

    SSR Setup with Angular CLI

    ng add @angular/ssr
    

    Hydration Configuration

    // app.config.ts
    import { ApplicationConfig } from "@angular/core";
    import {
      provideClientHydration,
      withEventReplay,
    } from "@angular/platform-browser";
    
    export const appConfig: ApplicationConfig = {
      providers: [provideClientHydration(withEventReplay())],
    };
    

    Incremental Hydration (v20+)

    import { Component } from "@angular/core";
    
    @Component({
      selector: "app-page",
      standalone: true,
      template: `
        <app-hero />
    
        @defer (hydrate on viewport) {
          <app-comments />
        }
    
        @defer (hydrate on interaction) {
          <app-chat-widget />
        }
      `,
    })
    export class PageComponent {}
    

    Hydration Triggers

    Trigger When to Use
    on idle Low-priority, hydrate when browser idle
    on viewport Hydrate when element enters viewport
    on interaction Hydrate on first user interaction
    on hover Hydrate when user hovers
    on timer(ms) Hydrate after specified delay

    5. Modern Routing Patterns

    Functional Route Guards

    // auth.guard.ts
    import { inject } from "@angular/core";
    import { Router, CanActivateFn } from "@angular/router";
    import { AuthService } from "./auth.service";
    
    export const authGuard: CanActivateFn = (route, state) => {
      const auth = inject(AuthService);
      const router = inject(Router);
    
      if (auth.isAuthenticated()) {
        return true;
      }
    
      return router.createUrlTree(["/login"], {
        queryParams: { returnUrl: state.url },
      });
    };
    
    // Usage in routes
    export const routes: Routes = [
      {
        path: "dashboard",
        loadComponent: () => import("./dashboard.component"),
        canActivate: [authGuard],
      },
    ];
    

    Route-Level Data Resolvers

    import { inject } from '@angular/core';
    import { ResolveFn } from '@angular/router';
    import { UserService } from './user.service';
    import { User } from './user.model';
    
    export const userResolver: ResolveFn<User> = (route) => {
      const userService = inject(UserService);
      return userService.getUser(route.paramMap.get('id')!);
    };
    
    // In routes
    {
      path: 'user/:id',
      loadComponent: () => import('./user.component'),
      resolve: { user: userResolver }
    }
    
    // In component
    export class UserComponent {
      private route = inject(ActivatedRoute);
      user = toSignal(this.route.data.pipe(map(d => d['user'])));
    }
    

    6. Dependency Injection Patterns

    Modern inject() Function

    import { Component, inject } from '@angular/core';
    import { HttpClient } from '@angular/common/http';
    import { UserService } from './user.service';
    
    @Component({...})
    export class UserComponent {
      // Modern inject() - no constructor needed
      private http = inject(HttpClient);
      private userService = inject(UserService);
    
      // Works in any injection context
      users = toSignal(this.userService.getUsers());
    }
    

    Injection Tokens for Configuration

    import { InjectionToken, inject } from "@angular/core";
    
    // Define token
    export const API_BASE_URL = new InjectionToken<string>("API_BASE_URL");
    
    // Provide in config
    bootstrapApplication(AppComponent, {
      providers: [{ provide: API_BASE_URL, useValue: "https://api.example.com" }],
    });
    
    // Inject in service
    @Injectable({ providedIn: "root" })
    export class ApiService {
      private baseUrl = inject(API_BASE_URL);
    
      get(endpoint: string) {
        return this.http.get(`${this.baseUrl}/${endpoint}`);
      }
    }
    

    7. Component Composition & Reusability

    Content Projection (Slots)

    @Component({
      selector: 'app-card',
      template: `
        <div class="card">
          <div class="header">
            <!-- Select by attribute -->
            <ng-content select="[card-header]"></ng-content>
          </div>
          <div class="body">
            <!-- Default slot -->
            <ng-content></ng-content>
          </div>
        </div>
      `
    })
    export class CardComponent {}
    
    // Usage
    <app-card>
      <h3 card-header>Title</h3>
      <p>Body content</p>
    </app-card>
    

    Host Directives (Composition)

    // Reusable behaviors without inheritance
    @Directive({
      standalone: true,
      selector: '[appTooltip]',
      inputs: ['tooltip'] // Signal input alias
    })
    export class TooltipDirective { ... }
    
    @Component({
      selector: 'app-button',
      standalone: true,
      hostDirectives: [
        {
          directive: TooltipDirective,
          inputs: ['tooltip: title'] // Map input
        }
      ],
      template: `<ng-content />`
    })
    export class ButtonComponent {}
    

    8. State Management Patterns

    Signal-Based State Service

    import { Injectable, signal, computed } from "@angular/core";
    
    interface AppState {
      user: User | null;
      theme: "light" | "dark";
      notifications: Notification[];
    }
    
    @Injectable({ providedIn: "root" })
    export class StateService {
      // Private writable signals
      private _user = signal<User | null>(null);
      private _theme = signal<"light" | "dark">("light");
      private _notifications = signal<Notification[]>([]);
    
      // Public read-only computed
      readonly user = computed(() => this._user());
      readonly theme = computed(() => this._theme());
      readonly notifications = computed(() => this._notifications());
      readonly unreadCount = computed(
        () => this._notifications().filter((n) => !n.read).length,
      );
    
      // Actions
      setUser(user: User | null) {
        this._user.set(user);
      }
    
      toggleTheme() {
        this._theme.update((t) => (t === "light" ? "dark" : "light"));
      }
    
      addNotification(notification: Notification) {
        this._notifications.update((n) => [...n, notification]);
      }
    }
    

    Component Store Pattern with Signals

    import { Injectable, signal, computed, inject } from "@angular/core";
    import { HttpClient } from "@angular/common/http";
    import { toSignal } from "@angular/core/rxjs-interop";
    
    @Injectable()
    export class ProductStore {
      private http = inject(HttpClient);
    
      // State
      private _products = signal<Product[]>([]);
      private _loading = signal(false);
      private _filter = signal("");
    
      // Selectors
      readonly products = computed(() => this._products());
      readonly loading = computed(() => this._loading());
      readonly filteredProducts = computed(() => {
        const filter = this._filter().toLowerCase();
        return this._products().filter((p) =>
          p.name.toLowerCase().includes(filter),
        );
      });
    
      // Actions
      loadProducts() {
        this._loading.set(true);
        this.http.get<Product[]>("/api/products").subscribe({
          next: (products) => {
            this._products.set(products);
            this._loading.set(false);
          },
          error: () => this._loading.set(false),
        });
      }
    
      setFilter(filter: string) {
        this._filter.set(filter);
      }
    }
    

    9. Forms with Signals (Coming in v22+)

    Current Reactive Forms

    import { Component, inject } from "@angular/core";
    import { FormBuilder, Validators, ReactiveFormsModule } from "@angular/forms";
    
    @Component({
      selector: "app-user-form",
      standalone: true,
      imports: [ReactiveFormsModule],
      template: `
        <form [formGroup]="form" (ngSubmit)="onSubmit()">
          <input formControlName="name" placeholder="Name" />
          <input formControlName="email" type="email" placeholder="Email" />
          <button [disabled]="form.invalid">Submit</button>
        </form>
      `,
    })
    export class UserFormComponent {
      private fb = inject(FormBuilder);
    
      form = this.fb.group({
        name: ["", Validators.required],
        email: ["", [Validators.required, Validators.email]],
      });
    
      onSubmit() {
        if (this.form.valid) {
          console.log(this.form.value);
        }
      }
    }
    

    Signal-Aware Form Patterns (Preview)

    // Future Signal Forms API (experimental)
    import { Component, signal } from '@angular/core';
    
    @Component({...})
    export class SignalFormComponent {
      name = signal('');
      email = signal('');
    
      // Computed validation
      isValid = computed(() =>
        this.name().length > 0 &&
        this.email().includes('@')
      );
    
      submit() {
        if (this.isValid()) {
          console.log({ name: this.name(), email: this.email() });
        }
      }
    }
    

    10. Performance Optimization

    Change Detection Strategies

    @Component({
      changeDetection: ChangeDetectionStrategy.OnPush,
      // Only checks when:
      // 1. Input signal/reference changes
      // 2. Event handler runs
      // 3. Async pipe emits
      // 4. Signal value changes
    })
    

    Defer Blocks for Lazy Loading

    @Component({
      template: `
        <!-- Immediate loading -->
        <app-header />
    
        <!-- Lazy load when visible -->
        @defer (on viewport) {
          <app-heavy-chart />
        } @placeholder {
          <div class="skeleton" />
        } @loading (minimum 200ms) {
          <app-spinner />
        } @error {
          <p>Failed to load chart</p>
        }
      `
    })
    

    NgOptimizedImage

    import { NgOptimizedImage } from '@angular/common';
    
    @Component({
      imports: [NgOptimizedImage],
      template: `
        <img
          ngSrc="hero.jpg"
          width="800"
          height="600"
          priority
        />
    
        <img
          ngSrc="thumbnail.jpg"
          width="200"
          height="150"
          loading="lazy"
          placeholder="blur"
        />
      `
    })
    

    11. Testing Modern Angular

    Testing Signal Components

    import { ComponentFixture, TestBed } from "@angular/core/testing";
    import { CounterComponent } from "./counter.component";
    
    describe("CounterComponent", () => {
      let component: CounterComponent;
      let fixture: ComponentFixture<CounterComponent>;
    
      beforeEach(async () => {
        await TestBed.configureTestingModule({
          imports: [CounterComponent], // Standalone import
        }).compileComponents();
    
        fixture = TestBed.createComponent(CounterComponent);
        component = fixture.componentInstance;
        fixture.detectChanges();
      });
    
      it("should increment count", () => {
        expect(component.count()).toBe(0);
    
        component.increment();
    
        expect(component.count()).toBe(1);
      });
    
      it("should update DOM on signal change", () => {
        component.count.set(5);
        fixture.detectChanges();
    
        const el = fixture.nativeElement.querySelector(".count");
        expect(el.textContent).toContain("5");
      });
    });
    

    Testing with Signal Inputs

    import { ComponentFixture, TestBed } from "@angular/core/testing";
    import { ComponentRef } from "@angular/core";
    import { UserCardComponent } from "./user-card.component";
    
    describe("UserCardComponent", () => {
      let fixture: ComponentFixture<UserCardComponent>;
      let componentRef: ComponentRef<UserCardComponent>;
    
      beforeEach(async () => {
        await TestBed.configureTestingModule({
          imports: [UserCardComponent],
        }).compileComponents();
    
        fixture = TestBed.createComponent(UserCardComponent);
        componentRef = fixture.componentRef;
    
        // Set signal inputs via setInput
        componentRef.setInput("id", "123");
        componentRef.setInput("name", "John Doe");
    
        fixture.detectChanges();
      });
    
      it("should display user name", () => {
        const el = fixture.nativeElement.querySelector("h3");
        expect(el.textContent).toContain("John Doe");
      });
    });
    

    Best Practices Summary

    Pattern ✅ Do ❌ Don't
    State Use Signals for local state Overuse RxJS for simple state
    Components Standalone with direct imports Bloated SharedModules
    Change Detection OnPush + Signals Default CD everywhere
    Lazy Loading @defer and loadComponent Eager load everything
    DI inject() function Constructor injection (verbose)
    Inputs input() signal function @Input() decorator (legacy)
    Zoneless Enable for new projects Force on legacy without testing

    Resources

    • Angular.dev Documentation
    • Angular Signals Guide
    • Angular SSR Guide
    • Angular Update Guide
    • Angular Blog

    Common Troubleshooting

    Issue Solution
    Signal not updating UI Ensure OnPush + call signal as function count()
    Hydration mismatch Check server/client content consistency
    Circular dependency Use inject() with forwardRef
    Zoneless not detecting changes Trigger via signal updates, not mutations
    SSR fetch fails Use TransferState or withFetch()

    Limitations

    • Use this skill only when the task clearly matches the scope described above.
    • Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
    • Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
    Recommended Servers
    Svelte
    Svelte
    Vercel Grep
    Vercel Grep
    Astro Docs
    Astro Docs
    Repository
    sickn33/antigravity-awesome-skills
    Files