
Angular Signals
Implement Angular signal-based state, async resources, forms, and tests instead of legacy RxJS-only patterns in new UI code.
Overview
Angular Signal Patterns is an agent skill for the Build phase that documents Resource API, signal stores, forms, async flows, and testing patterns for Angular frontends.
Install
npx skills add https://github.com/analogjs/angular-skills --skill angular-signalsWhat is this skill?
- Documents resource() for async fetch with params, abortSignal, loading/error/status signals, reload, and local updates
- Includes Signal Store pattern for centralized reactive state
- Covers form state with signals and async operations wired to computed views
- Provides testing signals guidance for unit and component tests
- Table of contents spans Resource API, stores, forms, async ops, and testing
Adoption & trust: 6.4k installs on skills.sh; 592 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your Angular UI mixes imperative subscriptions and manual loading flags instead of consistent signal and resource() patterns.
Who is it for?
Indie developers building Angular SaaS or internal tools who want agent-assisted migration to signals and Resource API.
Skip if: Greenfield projects on React/Vue/Svelte or teams that do not use Angular—pick a stack-specific skill instead.
When should I use this skill?
User or task requires Angular signal(), resource(), signal stores, signal-based forms, or testing reactive Angular UI.
What do I get? / Deliverables
Components use resource(), computed state, and signal-friendly forms and tests that reload cleanly when params change.
- Resource-backed data loaders with status computeds
- Signal store and form-state snippets
- Signal testing examples
Recommended Skills
Journey fit
Signal patterns are applied while building the product UI—the canonical build phase shelf for frontend architecture work. Covers Resource API, signal stores, form state, and signal testing—directly frontend subphase concerns for Angular apps.
How it compares
Framework pattern reference for Angular signals, not a generic state-management essay or backend API skill.
Common Questions / FAQ
Who is angular-signals for?
Solo builders and small teams writing Angular TypeScript frontends who want reproducible signal, resource(), and store patterns for their coding agents.
When should I use angular-signals?
During build frontend work when implementing user profiles, dashboards, or forms that need reactive loading states and testable signal graphs.
Is angular-signals safe to install?
It is documentation-only pattern guidance with no mandated tool execution; check the Security Audits panel on this Prism page for the parent repo before install.
SKILL.md
READMESKILL.md - Angular Signals
# Angular Signal Patterns ## Table of Contents - [Resource API](#resource-api) - [Signal Store Pattern](#signal-store-pattern) - [Form State with Signals](#form-state-with-signals) - [Async Operations](#async-operations) - [Testing Signals](#testing-signals) ## Resource API The `resource()` API handles async data fetching with signals: ```typescript import { resource, signal, computed } from '@angular/core'; @Component({...}) export class UserProfile { userId = signal<string>(''); // Resource fetches data when params change userResource = resource({ params: () => ({ id: this.userId() }), loader: async ({ params, abortSignal }) => { const response = await fetch(`/api/users/${params.id}`, { signal: abortSignal, }); return response.json() as Promise<User>; }, }); // Access resource state user = computed(() => this.userResource.value()); isLoading = computed(() => this.userResource.isLoading()); error = computed(() => this.userResource.error()); } ``` ### Resource Status ```typescript const userResource = resource({...}); // Status signals userResource.value(); // Current value or undefined userResource.hasValue(); // Boolean - has resolved value userResource.error(); // Error or undefined userResource.isLoading(); // Boolean - currently loading userResource.status(); // 'idle' | 'loading' | 'reloading' | 'resolved' | 'error' | 'local' // Manual reload userResource.reload(); // Local updates userResource.set(newValue); userResource.update(current => ({ ...current, name: 'Updated' })); ``` ### Resource with Default Value ```typescript const todosResource = resource({ defaultValue: [] as Todo[], params: () => ({ filter: this.filter() }), loader: async ({ params }) => { const response = await fetch(`/api/todos?filter=${params.filter}`); return response.json(); }, }); // value() returns Todo[] (never undefined due to defaultValue) ``` ### Conditional Loading ```typescript const userId = signal<string | null>(null); const userResource = resource({ params: () => { const id = userId(); // Return undefined to skip loading return id ? { id } : undefined; }, loader: async ({ params }) => { return fetch(`/api/users/${params.id}`).then(r => r.json()); }, }); // Status is 'idle' when params returns undefined ``` ## Signal Store Pattern For complex state, create a dedicated store: ```typescript interface ProductState { products: Product[]; selectedId: string | null; filter: string; loading: boolean; error: string | null; } @Injectable({ providedIn: 'root' }) export class ProductSt { // Private state private state = signal<ProductState>({ products: [], selectedId: null, filter: '', loading: false, error: null, }); // Selectors (computed signals) readonly products = computed(() => this.state().products); readonly selectedId = computed(() => this.state().selectedId); readonly filter = computed(() => this.state().filter); readonly loading = computed(() => this.state().loading); readonly error = computed(() => this.state().error); readonly filteredProducts = computed(() => { const { products, filter } = this.state(); if (!filter) return products; return products.filter(p => p.name.toLowerCase().includes(filter.toLowerCase()) ); }); readonly selectedProduct = computed(() => { const { products, selectedId } = this.state(); return products.find(p => p.id === selectedId) ?? null; }); private http = inject(HttpClient); // Actions setFilter(filter: string): void { this.state.update(s => ({ ...s, filter })); } selectProduct(id: string | null): void { this.state.update(s => ({ ...s, selectedId: id })); } async loadProducts(): Promise<void> { this.state.update(s => ({ ...s, loading: true, error: null })); try { const products = await firstValueFrom( this.http.get<Produc