Awesome-omni-skills angular
Angular Expert workflow skill. Use this skill when the user needs Modern Angular (v20+) expert with deep knowledge of Signals, Standalone Components, Zoneless applications, SSR/Hydration, and reactive patterns and the operator should preserve the upstream workflow, copied support files, and provenance before merging or handing off.
git clone https://github.com/diegosouzapw/awesome-omni-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/angular" ~/.claude/skills/diegosouzapw-awesome-omni-skills-angular && rm -rf "$T"
skills/angular/SKILL.mdAngular Expert
Overview
This public intake copy packages
plugins/antigravity-awesome-skills-claude/skills/angular from https://github.com/sickn33/antigravity-awesome-skills into the native Omni Skills editorial shape without hiding its origin.
Use it when the operator needs the upstream workflow, support files, and repository context to stay intact while the public validator and private enhancer continue their normal downstream flow.
This intake keeps the copied upstream files intact and uses
metadata.json plus ORIGIN.md as the provenance anchor for review.
Angular Expert Master modern Angular development with Signals, Standalone Components, Zoneless applications, SSR/Hydration, and the latest reactive patterns.
Imported source sections that did not map cleanly to the public headings are still preserved below or in the support files. Notable imported sections: Safety, Angular Version Timeline, 1. Signals: The New Reactive Primitive, 2. Standalone Components, 3. Zoneless Angular, 4. Server-Side Rendering & Hydration.
When to Use This Skill
Use this section as the trigger filter. It should make the activation boundary explicit before the operator loads files, runs commands, or opens a pull request.
- 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
Operating Table
| Situation | Start here | Why it matters |
|---|---|---|
| First-time use | | Confirms repository, branch, commit, and imported path before touching the copied workflow |
| Provenance review | | Gives reviewers a plain-language audit trail for the imported source |
| Workflow execution | | Starts with the smallest copied file that materially changes execution |
| Supporting context | | Adds the next most relevant copied source file without loading the entire package |
| Handoff decision | | Helps the operator switch to a stronger native skill when the task drifts |
Workflow
This workflow is intentionally editorial and operational at the same time. It keeps the imported source useful to the operator while still satisfying the public intake standards that feed the downstream enhancer flow.
- Assess the Angular version and project structure
- Apply modern patterns (Signals, Standalone, Zoneless)
- Implement with proper typing and reactivity
- Validate with build and tests
- Confirm the user goal, the scope of the imported workflow, and whether this skill is still the right router for the task.
- Read the overview and provenance files before loading any copied upstream support files.
- Load only the references, examples, prompts, or scripts that materially change the outcome for the current request.
Imported Workflow Notes
Imported: Instructions
- Assess the Angular version and project structure
- Apply modern patterns (Signals, Standalone, Zoneless)
- Implement with proper typing and reactivity
- Validate with build and tests
Imported: Safety
- Always test changes in development before production
- Gradual migration for existing apps (don't big-bang refactor)
- Keep backward compatibility during transitions
Examples
Example 1: Ask for the upstream workflow directly
Use @angular to handle <task>. Start from the copied upstream workflow, load only the files that change the outcome, and keep provenance visible in the answer.
Explanation: This is the safest starting point when the operator needs the imported workflow, but not the entire repository.
Example 2: Ask for a provenance-grounded review
Review @angular against metadata.json and ORIGIN.md, then explain which copied upstream files you would load first and why.
Explanation: Use this before review or troubleshooting when you need a precise, auditable explanation of origin and file selection.
Example 3: Narrow the copied support files before execution
Use @angular for <task>. Load only the copied references, examples, or scripts that change the outcome, and name the files explicitly before proceeding.
Explanation: This keeps the skill aligned with progressive disclosure instead of loading the whole copied package by default.
Example 4: Build a reviewer packet
Review @angular using the copied upstream files plus provenance, then summarize any gaps before merge.
Explanation: This is useful when the PR is waiting for human review and you want a repeatable audit packet.
Best Practices
Treat the generated public skill as a reviewable packaging layer around the upstream repository. The goal is to keep provenance explicit and load only the copied source material that materially improves execution.
- Keep the imported skill grounded in the upstream repository; do not invent steps that the source material cannot support.
- Prefer the smallest useful set of support files so the workflow stays auditable and fast to review.
- Keep provenance, source commit, and imported file paths visible in notes and PR descriptions.
- Point directly at the copied upstream files that justify the workflow instead of relying on generic review boilerplate.
- Treat generated examples as scaffolding; adapt them to the concrete task before execution.
- Route to a stronger native skill when architecture, debugging, design, or security concerns become dominant.
Troubleshooting
Problem: The operator skipped the imported context and answered too generically
Symptoms: The result ignores the upstream workflow in
plugins/antigravity-awesome-skills-claude/skills/angular, fails to mention provenance, or does not use any copied source files at all.
Solution: Re-open metadata.json, ORIGIN.md, and the most relevant copied upstream files. Load only the files that materially change the answer, then restate the provenance before continuing.
Problem: The imported workflow feels incomplete during review
Symptoms: Reviewers can see the generated
SKILL.md, but they cannot quickly tell which references, examples, or scripts matter for the current task.
Solution: Point at the exact copied references, examples, scripts, or assets that justify the path you took. If the gap is still real, record it in the PR instead of hiding it.
Problem: The task drifted into a different specialization
Symptoms: The imported skill starts in the right place, but the work turns into debugging, architecture, design, security, or release orchestration that a native skill handles better. Solution: Use the related skills section to hand off deliberately. Keep the imported provenance visible so the next skill inherits the right context instead of starting blind.
Imported Troubleshooting Notes
Imported: Common Troubleshooting
| Issue | Solution |
|---|---|
| Signal not updating UI | Ensure + call signal as function |
| Hydration mismatch | Check server/client content consistency |
| Circular dependency | Use with |
| Zoneless not detecting changes | Trigger via signal updates, not mutations |
| SSR fetch fails | Use or |
Related Skills
- Use when the work is better handled by that native specialization after this imported skill establishes context.@00-andruia-consultant
- Use when the work is better handled by that native specialization after this imported skill establishes context.@10-andruia-skill-smith
- Use when the work is better handled by that native specialization after this imported skill establishes context.@20-andruia-niche-intelligence
- Use when the work is better handled by that native specialization after this imported skill establishes context.@3d-web-experience
Additional Resources
Use this support matrix and the linked files below as the operator packet for this imported skill. They should reflect real copied source material, not generic scaffolding.
| Resource family | What it gives the reviewer | Example path |
|---|---|---|
| copied reference notes, guides, or background material from upstream | |
| worked examples or reusable prompts copied from upstream | |
| upstream helper scripts that change execution or validation | |
| routing or delegation notes that are genuinely part of the imported package | |
| supporting assets or schemas copied from the source package | |
Imported Reference Notes
Imported: Resources
Imported: 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 |
Imported: 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 | ✅ | works |
| Side effects | ✅ | operator |
| HTTP requests | ❌ | ✅ HttpClient returns Observable |
| Event streams | ❌ | ✅ , operators |
| Complex async flows | ❌ | ✅ , |
Imported: 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), }, ];
Imported: 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
Imported: 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 |
|---|---|
| Low-priority, hydrate when browser idle |
| Hydrate when element enters viewport |
| Hydrate on first user interaction |
| Hydrate when user hovers |
| Hydrate after specified delay |
Imported: 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']))); }
Imported: 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}`); } }
Imported: 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 {}
Imported: 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); } }
Imported: 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() }); } } }
Imported: 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" /> ` })
Imported: 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"); }); });
Imported: 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 | and | Eager load everything |
| DI | function | Constructor injection (verbose) |
| Inputs | signal function | decorator (legacy) |
| Zoneless | Enable for new projects | Force on legacy without testing |
Imported: 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.