Angular Signal Forms Generator
SkillDev toolsGenerate an Angular Signal Forms component with schema validation and dynamic field management for a given entity.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Angular Signal Forms Generator skill
What this skill tells your AI
The instructions your AI receives, as published by danielsogl/copilot-workflow-demo in .apm/skills/angular-signal-forms/SKILL.md and read by ahel’s review.
Goal
Generate a complete Angular Signal Form with schema validation, error handling, and dynamic field management for a specified entity, following the project's architecture patterns and modern Angular Signal Forms API with built-in schema validation.
Important: Read the
angular-developerskill'sreferences/signal-forms.mdfor comprehensive Signal Forms patterns, schema validation approaches, and best practices before generating forms. Project-wide conventions live in the generated rules (.claude/rules/for Claude,.github/instructions/for Copilot).
Prerequisites
Before running this skill, ensure you have:
- Entity Definition: The target entity model must be defined and linked in the chat context
- Form Purpose: Specify the form's purpose (e.g., "User Registration", "Task Creation", "Profile Update")
- Form Type: Indicate if it's a create, edit, or multi-purpose form
- Field Requirements: Any specific field requirements or validation rules
Instructions
Step 1: Entity and Requirements Analysis
- Analyze the provided entity model to understand its structure, types, and relationships
- Identify which fields should be included in the form (some entity fields might be auto-generated or readonly)
- Determine appropriate input types for each field (text, email, number, date, select, checkbox, etc.)
- Check for any existing validation patterns or business rules in the codebase
- Identify nested objects and array fields that require dynamic management
Step 2: Form Component Generation
Generate a complete Angular Signal Form component with schema validation:
Core Imports and Dependencies
import { Component, signal, computed, ChangeDetectionStrategy } from '@angular/core';
import {
form,
schema,
FormField,
required,
email,
minLength,
maxLength,
min,
max,
pattern,
applyEach,
} from '@angular/forms/signals';
import { JsonPipe } from '@angular/common';
// Material 21+: import individual components, not *Module umbrellas
import { MatButton } from '@angular/material/button';
import { MatCard, MatCardHeader, MatCardTitle, MatCardContent } from '@angular/material/card';
import { MatFormField, MatLabel, MatError } from '@angular/material/form-field';
import { MatInput } from '@angular/material/input';
import { MatSelect, MatOption } from '@angular/material/select';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatDatepicker, MatDatepickerInput, MatDatepickerToggle } from '@angular/material/datepicker';
Form Definition with Inline Schema
Define the form by passing the model signal and a schema function directly to form(). The schema function receives a fieldPath for type-safe validator wiring:
interface [EntityType] {
name: string;
email: string;
description?: string;
address: {
street: string;
city: string;
};
tags: string[];
}
const EMPTY_[ENTITY_NAME]: [EntityType] = {
name: '',
email: '',
description: '',
address: { street: '', city: '' },
tags: [],
};
Component Structure
@Component({
selector: 'app-[entity-name]-form',
imports: [
MatCard, MatCardHeader, MatCardTitle, MatCardContent,
MatButton,
MatFormField, MatLabel, MatError,
MatInput,
MatSelect, MatOption,
MatCheckbox,
MatDatepicker, MatDatepickerInput, MatDatepickerToggle,
FormField,
JsonPipe,
],
templateUrl: './[entity-name]-form.html',
styleUrl: './[entity-name]-form.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class [EntityName]Form {
// Signal-backed form model
protected readonly [entityName]Model = signal<[EntityType]>(EMPTY_[ENTITY_NAME]);
// Validation schema (defined at module level for reuse and memoization)
private readonly [entityName]Schema = schema<[EntityType]>((f) => {
required(f.name, { message: 'Name is required' });
minLength(f.name, 2, { message: 'Name must be at least 2 characters' });
required(f.email, { message: 'Email is required' });
email(f.email, { message: 'Enter a valid email address' });
maxLength(f.description, 500, { message: 'Description cannot exceed 500 characters' });
// Nested objects
required(f.address.street, { message: 'Street is required' });
required(f.address.city, { message: 'City is required' });
// Arrays
applyEach(f.tags, (tag) => {
minLength(tag, 1, { message: 'Tag cannot be empty' });
});
});
// Form with schema
protected readonly [entityName]Form = form(this.[entityName]Model, this.[entityName]Schema);
// Computed form state — dirty check at field level, not form level
protected readonly canSubmit = computed(() =>
this.[entityName]Form().valid() &&
(this.[entityName]Form.name().dirty() || this.[entityName]Form.email().dirty()),
);
protected onSubmit(): void {
if (!this.canSubmit()) return;
const value = this.[entityName]Model();
// ... call store / API
}
}
Step 3: Template Generation
Create a comprehensive template with modern Angular patterns:
Form Structure
<mat-card>
<mat-card-header>
<mat-card-title>[Form Title]</mat-card-title>
</mat-card-header>
<mat-card-content>
<form (ngSubmit)="onSubmit()">
<!-- Form fields based on entity structure -->
</form>
</mat-card-content>
</mat-card>
Field Templates
For each entity field, generate appropriate input with validation:
Text/Email Fields:
<mat-form-field class="w-full">
<mat-label>[Field Label]</mat-label>
<input matInput [formField]="[entityName]Form.[fieldName]" placeholder="[Placeholder]">
@if([entityName]Form.[fieldName]().touched() || [entityName]Form.[fieldName]().dirty()) {
@for (error of [entityName]Form.[fieldName]().errors(); track error.kind) {
<mat-error>{{ error.message }}</mat-error>
}
}
</mat-form-field>
Select Fields:
<mat-form-field class="w-full">
<mat-label>[Field Label]</mat-label>
<mat-select [formField]="[entityName]Form.[fieldName]">
@for (option of [options]; track option.value) {
<mat-option [value]="option.value">{{ option.label }}</mat-option>
}
</mat-select>
@if([entityName]Form.[fieldName]().touched() || [entityName]Form.[fieldName]().dirty()) {
@for (error of [entityName]Form.[fieldName]().errors(); track error.kind) {
<mat-error>{{ error.message }}</mat-error>
}
}
</mat-form-field>
Dynamic Array Fields:
<div class="array-field-section">
<h3>[Array Field Label]</h3>
@for (item of [entityName]Form.[arrayField]; track item; let i = $index) {
<mat-card class="mb-4">
<!-- Array item fields -->
<button mat-icon-button color="warn" type="button" (click)="remove[ArrayItem](i)">
<mat-icon>delete</mat-icon>
</button>
</mat-card>
}
@empty {
<p class="text-muted">No [items] added yet.</p>
}
<button mat-stroked-button type="button" (click)="add[ArrayItem]()">
<mat-icon>add</mat-icon> Add [Item]
</button>
</div>
Step 4: Component Methods Implementation
Dynamic Field Management
Implement methods for adding/removing dynamic fields:
add[ArrayItem](): void {
this.[entityName].update(state => ({
...state,
[arrayField]: [...state.[arrayField], {
// Default values for new item
}]
}));
}
remove[ArrayItem](index: number): void {
this.[entityName].update(state => ({
...state,
[arrayField]: state.[arrayField].filter((_, i) => i !== index)
}));
}
Form Submission
onSubmit(): void {
if (this.[entityName]Form().valid()) {
const formData = this.[entityName]();
console.log('Form submitted with valid data:', formData);
// Handle form submission (emit event, call service, etc.)
this.handleFormSubmit(formData);
} else {
// Mark all fields as touched to show validation errors
this.markAllFieldsTouched();
}
}
private handleFormSubmit(data: [EntityType]): void {
// Emit to parent component or call service
this.formSubmit.emit(data);
}
private markAllFieldsTouched(): void {
// Implementation to mark all fields as touched for validation display
// This will trigger error display for all invalid fields
}
Step 5: Styling and Accessibility
Component Styles
Create responsive and accessible styles:
.form-container {
max-width: 600px;
margin: 0 auto;
padding: 1rem;
}
.form-section {
margin-bottom: 2rem;
}
.array-field-section {
.mat-card {
position: relative;
.remove-button {
position: absolute;
top: 8px;
right: 8px;
}
}
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 1rem;
margin-top: 2rem;
}
@media (max-width: 768px) {
.form-container {
padding: 0.5rem;
}
.form-actions {
flex-direction: column;
}
}
Accessibility Features
- Proper ARIA labels and descriptions
- Keyboard navigation support
- Focus management
- Screen reader compatibility
Step 6: Integration Guide
Component Usage
<app-[entity-name]-form
[initialData]="[entityName]"
(formSubmit)="onFormSubmit($event)"
(formCancel)="onFormCancel()">
</app-[entity-name]-form>
Input/Output Properties
// Add these to the component if needed
readonly initialData = input<Partial<[EntityType]>>();
readonly mode = input<'create' | 'edit'>('create');
readonly formSubmit = output<[EntityType]>();
readonly formCancel = output<void>();
Parent Component Integration
// Example parent component usage
@Component({
template: `
<app-[entity-name]-form
[initialData]="selectedEntity()"
(formSubmit)="handleFormSubmit($event)">
</app-[entity-name]-form>
`
})
export class Parent {
selectedEntity = signal<[EntityType] | null>(null);
handleFormSubmit(data: [EntityType]): void {
// Handle form submission
}
}
File Structure
Create the following files in the appropriate domain folder:
src/app/[domain]/
├── feature/
│ ├── [entity-name]-form/
│ │ ├── [entity-name]-form.ts
│ │ ├── [entity-name]-form.html
│ │ └── [entity-name]-form.scss
└── data/
└── models/
└── [entity-name].model.ts # Entity model reference
Best Practices
- Schema-Based Validation: Always use Angular's built-in
schema()function for declarative validation following theangular-developerskill (references/signal-forms.md). Define schemas at module level (not inside the class) for memoization and reusability. - Modern Angular Patterns: Use standalone components, function-based DI with
inject(), and modern control flow (@if,@for) - Signal-First Approach: Leverage signals for all reactive state management
- Material Design: Use Angular Material components for consistent UI and built-in accessibility
- Type Safety: Maintain strict TypeScript typing throughout the form with proper interfaces
- Dirty Check at Field Level: Use
field().dirty()on individual fields — the form-levelform()object only exposesvalid(), notdirty() - Responsive Design: Ensure forms work well on all device sizes
- Error Handling: Provide clear, user-friendly error messages with custom messages in validators
- Performance: Use OnPush change detection and efficient signal updates with computed signals
- Built-in Validators: Use Angular's built-in validators (
required,email,minLength, etc.) for common validation needs
Key References
angular-developerskill —references/signal-forms.mdforform(),schema(), validators andFormField- Styling and TypeScript conventions from the project instructions
Signals
- GitHub stars
- 39
- Forks
- 6
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
angular-signal-forms- Source
- github.com/danielsogl/copilot-workflow-demo