Ng22 signal forms
Skill PavanAnguluri/angular22-agent-skills/skills/ng22-signal-forms
Angular 22 Agent Skills
npx -y skills add PavanAnguluri/angular22-agent-skills --skill ng22-signal-formsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Enforces Angular 22 signal-centric form patterns, computed validation mapping, and controlled state mutation.
SKILL.md
3.1 KB, as published. Nobody here has run it
Angular 22 Signal-Centric Forms
Build forms with explicit reactive state and avoid fallback to legacy patterns. Treat each form as a signal-driven data model with deterministic validation and update flow.
Core Rules
- Do not default to
FormBuilderor template-driven forms for new complex workflows. - Keep primary form state in signals and derive view state with
computed(). - Model validation errors as computed maps so error rendering is deterministic and centralized.
- Use signal inputs to seed initial control values and keep initialization explicit.
- Keep form arrays trackable with signal-backed collections and immutable updates.
Signal Input Initialization Pattern
import { Component, computed, input, signal } from '@angular/core';
type ProfileForm = {
name: string;
aliases: string[];
};
@Component({
standalone: true,
selector: 'app-profile-form',
template: `
<input [value]="form().name" (input)="setName($any($event.target).value)" />
@for (alias of form().aliases; track $index; let i = $index) {
<input [value]="alias" (input)="setAlias(i, $any($event.target).value)" />
}
@if (errors().name) {
<p>{{ errors().name }}</p>
}
`,
})
export class ProfileFormComponent {
initialName = input<string>('');
initialAliases = input<string[]>([]);
form = signal<ProfileForm>({
name: this.initialName(),
aliases: [...this.initialAliases()],
});
errors = computed(() => {
const value = this.form();
return {
name: value.name.trim().length < 2 ? 'Name must be at least 2 characters.' : '',
aliases: value.aliases.map((alias) =>
alias.trim().length === 0 ? 'Alias cannot be empty.' : ''
),
};
});
setName(name: string): void {
this.form.update((value) => ({ ...value, name }));
}
setAlias(index: number, alias: string): void {
this.form.update((value) => ({
...value,
aliases: value.aliases.map((current, i) => (i === index ? alias : current)),
}));
}
}
Form Array Mutation Discipline
- Add rows with immutable operations such as
aliases: [...aliases, '']. - Remove rows with index-filtered copies, never in-place mutation.
- Keep the rendered list keyed with stable tracking (
track item.id) when available.
Validation Mapping Pattern
- Derive all validation state in one
computed()tree. - Keep field-level and cross-field rules together so templates remain simple.
- Keep error text generation centralized to avoid duplicated string logic in templates.
Anti-Patterns
- Mixing template-driven directives with signal-first form state.
- Triggering hidden side effects from validators.
- Mutating arrays or nested objects in place and expecting deterministic updates.
- Spreading validation logic across template fragments.
Review Checklist
- Form state is signal-backed.
- Validation is computed and centralized.
- Arrays are updated immutably and tracked correctly.
- Initialization uses explicit signal inputs.