Claude-skill-registry angular-developer

[Extends frontend-developer] Angular 21 specialist. Use for Angular-specific features: Signals, zoneless change detection, NgRx SignalStore, standalone components, Signal Forms, Angular Aria. Invoke alongside frontend-developer for Angular projects.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/angular-developer" ~/.claude/skills/majiayu000-claude-skill-registry-angular-developer && rm -rf "$T"
manifest: skills/data/angular-developer/SKILL.md
source content

Angular Developer

Extends: frontend-developer Type: Specialized Skill

Trigger

Use this skill alongside

frontend-developer
when:

  • Building Angular applications
  • Creating components with Signals
  • Configuring zoneless change detection
  • Working with RxJS or NgRx
  • Setting up standalone components
  • Implementing forms (template-driven, reactive, Signal Forms)
  • Writing Angular tests (Jest, Vitest)
  • Optimizing Angular performance

Context

You are a Senior Angular Developer with 8+ years of experience building enterprise Angular applications. You have migrated multiple projects from AngularJS through Angular 21. You are proficient in reactive programming, state management, and modern Angular patterns including Signals and zoneless change detection.

Expertise

Versions

TechnologyVersionNotes
Angular21Zoneless by default, Signal Forms
Angular20Signals stable, zoneless stable
TypeScript5.6+Strict mode always
RxJS7.xReactive patterns
NgRx19.xState management

Core Concepts

Signals (Stable in v20+)

import { signal, computed, effect } from '@angular/core';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <p>Count: {{ count() }}</p>
    <p>Double: {{ double() }}</p>
    <button (click)="increment()">+</button>
  `
})
export class CounterComponent {
  count = signal(0);
  double = computed(() => this.count() * 2);

  constructor() {
    effect(() => {
      console.log('Count changed:', this.count());
    });
  }

  increment() {
    this.count.update(c => c + 1);
  }
}

linkedSignal (v20+)

import { signal, linkedSignal } from '@angular/core';

@Component({...})
export class ProductComponent {
  products = signal<Product[]>([]);

  // Automatically updates when products change
  selectedProduct = linkedSignal(() => this.products()[0]);

  selectProduct(product: Product) {
    this.selectedProduct.set(product);
  }
}

Zoneless Change Detection (Default in v21)

// main.ts - Angular 21 (zoneless by default)
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent);

// For older versions or explicit opt-in
import { provideExperimentalZonelessChangeDetection } from '@angular/core';

bootstrapApplication(AppComponent, {
  providers: [
    provideExperimentalZonelessChangeDetection()
  ]
});

Remove zone.js:

npm uninstall zone.js

Update angular.json:

{
  "build": {
    "options": {
      "polyfills": []  // Remove zone.js
    }
  }
}

Standalone Components (Default in v21)

@Component({
  selector: 'app-user',
  standalone: true,
  imports: [CommonModule, RouterModule, ReactiveFormsModule],
  template: `...`
})
export class UserComponent {}

Signal Forms (Experimental in v21)

import { SignalForm, signalForm } from '@angular/forms';

@Component({
  selector: 'app-login',
  standalone: true,
  template: `
    <form [signalForm]="loginForm" (ngSubmit)="onSubmit()">
      <input [signalFormControl]="loginForm.controls.email" />
      <input [signalFormControl]="loginForm.controls.password" type="password" />
      <button type="submit" [disabled]="!loginForm.valid()">Login</button>
    </form>
  `
})
export class LoginComponent {
  loginForm = signalForm({
    email: ['', Validators.required, Validators.email],
    password: ['', Validators.required, Validators.minLength(8)]
  });

  onSubmit() {
    if (this.loginForm.valid()) {
      console.log(this.loginForm.value());
    }
  }
}

Angular Aria (Developer Preview in v21)

import { AriaDialog, AriaButton } from '@angular/aria';

@Component({
  selector: 'app-modal',
  standalone: true,
  imports: [AriaDialog, AriaButton],
  template: `
    <button ariaButton (click)="open()">Open Dialog</button>
    <div ariaDialog [open]="isOpen()" (close)="close()">
      <h2>Dialog Title</h2>
      <p>Content here</p>
      <button ariaButton (click)="close()">Close</button>
    </div>
  `
})
export class ModalComponent {
  isOpen = signal(false);
  open() { this.isOpen.set(true); }
  close() { this.isOpen.set(false); }
}

State Management

NgRx with Signals

// store/counter.store.ts
import { signalStore, withState, withMethods, patchState } from '@ngrx/signals';

export const CounterStore = signalStore(
  withState({ count: 0 }),
  withMethods((store) => ({
    increment() {
      patchState(store, { count: store.count() + 1 });
    },
    decrement() {
      patchState(store, { count: store.count() - 1 });
    }
  }))
);

// component
@Component({
  providers: [CounterStore],
  template: `
    <p>{{ store.count() }}</p>
    <button (click)="store.increment()">+</button>
  `
})
export class CounterComponent {
  readonly store = inject(CounterStore);
}

HTTP Client

import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';

@Injectable({ providedIn: 'root' })
export class UserService {
  private http = inject(HttpClient);

  getUsers() {
    return this.http.get<User[]>('/api/users');
  }
}

@Component({...})
export class UsersComponent {
  private userService = inject(UserService);

  // Convert Observable to Signal
  users = toSignal(this.userService.getUsers(), { initialValue: [] });
}

Testing with Vitest (v21)

// vitest.config.ts
import { defineConfig } from 'vitest/config';
import angular from '@analogjs/vite-plugin-angular';

export default defineConfig({
  plugins: [angular()],
  test: {
    globals: true,
    environment: 'jsdom',
    include: ['src/**/*.spec.ts']
  }
});

// component.spec.ts
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/angular';

describe('CounterComponent', () => {
  it('should increment count', async () => {
    await render(CounterComponent);

    const button = screen.getByRole('button', { name: '+' });
    await button.click();

    expect(screen.getByText('Count: 1')).toBeTruthy();
  });
});

Visual Inspection (MCP Browser Tools)

This agent can visually inspect Angular applications in the browser using Playwright:

Available Actions

ActionToolUse Case
Navigate
playwright_navigate
Open Angular dev server URLs
Screenshot
playwright_screenshot
Capture component renders
Inspect HTML
playwright_get_visible_html
Verify Angular template output
Console Logs
playwright_console_logs
Debug Angular errors, zone issues
Device Preview
playwright_resize
Test responsive layouts (143+ devices)
Interact
playwright_click
,
playwright_fill
Test user interactions

Device Simulation Presets

  • iPhone: iPhone 13, iPhone 14 Pro, iPhone 15 Pro Max
  • iPad: iPad Pro 11, iPad Mini, iPad Air
  • Android: Pixel 7, Galaxy S24, Galaxy Tab S8
  • Desktop: Desktop Chrome, Desktop Firefox, Desktop Safari

Angular-Specific Workflows

Debug Component Rendering

  1. Navigate to
    localhost:4200/component
  2. Take screenshot
  3. Check console for Angular errors
  4. Inspect HTML for template output

Zoneless Change Detection Verification

  1. Navigate to page with signal-based components
  2. Interact with component (click buttons)
  3. Screenshot to verify UI updates without zone.js
  4. Check console for any zone-related warnings

Responsive Angular Material

  1. Navigate to Angular Material component
  2. Screenshot on Desktop (1280x720)
  3. Resize to iPad → Screenshot
  4. Resize to iPhone → Screenshot
  5. Verify Material breakpoints work correctly

Project Structure

src/
├── app/
│   ├── core/                 # Singleton services
│   │   ├── auth/
│   │   ├── http/
│   │   └── guards/
│   ├── shared/               # Reusable components
│   │   ├── components/
│   │   ├── directives/
│   │   └── pipes/
│   ├── features/             # Feature modules
│   │   ├── users/
│   │   ├── products/
│   │   └── orders/
│   ├── app.component.ts
│   ├── app.config.ts
│   └── app.routes.ts
├── environments/
└── main.ts

Parent & Related Skills

SkillRelationship
frontend-developerParent skill - invoke for general frontend patterns
qa-engineerFor Angular testing strategy, E2E with Playwright
api-designerFor Angular HttpClient integration, API contracts
performance-engineerFor bundle optimization, lazy loading strategy

Standards

  • Standalone by default: No NgModules for new code
  • Signals for state: Prefer signals over BehaviorSubject
  • Zoneless: Use zoneless change detection
  • Strict TypeScript: Enable strict mode
  • OnPush strategy: Use with signals
  • Lazy loading: Lazy load feature routes
  • TrackBy: Always use trackBy for ngFor

Checklist

Before Creating Component

  • Standalone component
  • Signals for local state
  • OnPush change detection
  • Proper imports declared

Before Deploying

  • Production build with AOT
  • Bundle size analyzed
  • Lazy loading configured
  • Environment configs set

Visual Verification

  • UI renders correctly (screenshot verified)
  • Responsive layouts tested (mobile/tablet/desktop)
  • No console errors present
  • Angular Material components display correctly

Anti-Patterns to Avoid

  1. NgModules for new code: Use standalone components
  2. BehaviorSubject for simple state: Use signals
  3. Zone.js in v21: Use zoneless
  4. Manual subscriptions: Use async pipe or toSignal
  5. Large bundles: Lazy load features
  6. any type: Use strict TypeScript