Provides reference for Angular v19-v21 features: linkedSignal, resource/httpResource, signal forms, zoneless change detection, SSR hydration modes, Vitest migration, Angular Aria.
npx claudepluginhub nevaberry/nevaberry-plugins --plugin angular-knowledge-patchThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Implements structured self-debugging workflow for AI agent failures: capture errors, diagnose patterns like loops or context overflow, apply contained recoveries, and generate introspection reports.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Baseline: Angular through v18.x (signals basics, new control flow, standalone components, zoneless experimental, SSR hydration basics, esbuild builder).
This patch covers features from Angular v19 through v21 (2024-11 to 2025-11).
| Topic | File | Key APIs |
|---|---|---|
| Signals & Reactivity | references/signals-and-reactivity.md | linkedSignal, resource, httpResource |
| Signal Forms | references/signal-forms.md | form(), FormField, validators |
| SSR & Hydration | references/ssr-and-hydration.md | RenderMode, ServerRoute, incremental hydration |
| Zoneless & Testing | references/zoneless-and-testing.md | provideZonelessChangeDetection, Vitest |
| Components & Templates | references/components-and-templates.md | Angular Aria, regex in templates, @defer viewport |
| API | Status | Import | Purpose |
|---|---|---|---|
linkedSignal | Stable (v20) | @angular/core | Writable signal that resets when source changes |
resource | Experimental | @angular/core | Signal-based async data loading |
httpResource | Experimental | @angular/common/http | Signal-based HTTP fetching (reads only) |
form() | Experimental (v21+) | @angular/forms/signals | Signal-based forms with schema validation |
const selected = linkedSignal(() => options()[0]);
selected.set(options()[2]); // writable
// Resets to options()[0] when options() changes
import { resource, Signal } from '@angular/core';
const userId: Signal<string> = getUserId();
const userResource = resource({
params: () => ({ id: userId() }),
loader: ({ params, abortSignal }) =>
fetch(`/users/${params.id}`, { signal: abortSignal }),
});
// userResource.value(), .isLoading(), .error(), .hasValue()
// IMPORTANT: .value() throws in error state — guard with .hasValue()
import { httpResource } from '@angular/common/http';
// Reactive URL — re-fetches when userId() changes
const user = httpResource<User>(() => `/api/user/${userId()}`);
// user.value(), .isLoading(), .error(), .hasValue(), .headers()
// Only for reads — use HttpClient directly for mutations
bootstrapApplication(AppComponent, {
providers: [
provideZonelessChangeDetection(),
provideBrowserGlobalErrorListeners(),
],
});
// Remove zone.js polyfill from angular.json
# Migrate existing Jasmine tests
ng g @schematics/angular:refactor-jasmine-vitest
import { RenderMode, ServerRoute } from '@angular/ssr';
export const serverRouteConfig: ServerRoute[] = [
{ path: '/login', mode: RenderMode.Server },
{ path: '/dashboard', mode: RenderMode.Client },
{
path: '/product/:id',
mode: RenderMode.Prerender,
async getPrerenderPaths() {
return (await inject(ProductService).getIds()).map((id) => ({ id }));
},
},
{ path: '/**', mode: RenderMode.Prerender },
];
provideClientHydration(withIncrementalHydration())
// Template
@defer (hydrate on viewport) {
<shopping-cart/>
}
import { form, FormField, required, email, submit } from '@angular/forms/signals';
loginModel = signal({ email: '', password: '' });
loginForm = form(this.loginModel, (schema) => {
required(schema.email, { message: 'Email is required' });
email(schema.email, { message: 'Invalid email' });
required(schema.password, { message: 'Password is required' });
});
Template: <input [formField]="loginForm.email" />, access state via loginForm.email() → .value(), .touched(), .valid(), .errors(), .dirty(), .pending().
const selected = linkedSignal<ShippingMethod[], ShippingMethod>({
source: shippingOptions,
computation: (newOptions, previous) =>
newOptions.find(o => o.id === previous?.value.id) ?? newOptions[0],
});
const user = httpResource(() => ({
url: `/api/user/${userId()}`,
method: 'GET',
headers: { 'X-Special': 'true' },
params: { fast: 'yes' },
}));
// Response type variants
httpResource.text(() => url);
httpResource.blob(() => url);
// Zod validation
const res = httpResource(() => `/api/people/${id()}`, {
parse: starWarsPersonSchema.parse,
});
Headless accessible components: npm i @angular/aria. Patterns: Accordion, Combobox, Grid, Listbox, Menu, Tabs, Toolbar, Tree. Unstyled — you provide all CSS.