Run a structured bug-focused code review for selected files, folders, or the whole workspace.
日本語の概要は準備中です。原文の説明を表示しています。
Generate an Angular Signal Forms component with schema validation and dynamic field management for a given entity.
日本語の概要は準備中です。原文の説明を表示しています。
インストール方法を見るインストールする前に、エージェントに与えられる指示の中身を確認できます。
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).
Before running this skill, ensure you have:
Generate a complete Angular Signal Form component with schema validation:
import { Component, signal, computed } from '@angular/core';
import {
form,
schema,
FormField,
FormRoot,
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';
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({
selector: 'app-[entity-name]-form',
imports: [
MatCard, MatCardHeader, MatCardTitle, MatCardContent,
MatButton,
MatFormField, MatLabel, MatError,
MatInput,
MatSelect, MatOption,
MatCheckbox,
MatDatepicker, MatDatepickerInput, MatDatepickerToggle,
FormField,
FormRoot,
JsonPipe,
],
templateUrl: './[entity-name]-form.html',
styleUrl: './[entity-name]-form.scss',
})
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; `submission.action` runs on submit when the form is valid
protected readonly [entityName]Form = form(this.[entityName]Model, this.[entityName]Schema, {
submission: { action: async () => this.onSubmit() },
});
// 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()),
);
private onSubmit(): void {
const value = this.[entityName]Model();
// ... call store / API
}
}
Create a comprehensive template with modern Angular patterns:
<mat-card>
<mat-card-header>
<mat-card-title>[Form Title]</mat-card-title>
</mat-card-header>
<mat-card-content>
<!-- [formRoot] submits via Enter and type="submit" buttons; no (ngSubmit), no FormsModule -->
<form [formRoot]="[entityName]Form">
<!-- Form fields based on entity structure -->
<button mat-flat-button type="submit" [disabled]="!canSubmit()">Save</button>
</form>
</mat-card-content>
</mat-card>
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 class="remove-button" type="button" aria-label="Remove [item]" (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>
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)
}));
}
[formRoot] calls submit() on the form: it marks every field as touched (so errors show), then runs
submission.action only if the form is valid. No manual valid check or "mark all touched" helper.
protected readonly [entityName]Form = form(this.[entityName]Model, this.[entityName]Schema, {
submission: {
action: async () => this.formSubmit.emit(this.[entityName]Model()),
onInvalid: () => { /* optional: focus the first invalid field */ },
},
});
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 {
color: var(--mat-sys-error); // `color="warn"` has no effect in Material 22
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;
}
}
<app-[entity-name]-form
[initialData]="[entityName]"
(formSubmit)="onFormSubmit($event)"
(formCancel)="onFormCancel()">
</app-[entity-name]-form>
// 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>();
// 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
}
}
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
schema() function for declarative validation following the angular-developer skill (references/signal-forms.md). Define schemas at module level (not inside the class) for memoization and reusability.inject(), and modern control flow (@if, @for)field().dirty() on individual fields — the form-level form() object only exposes valid(), not dirty()changeDetection; derive state with computedrequired, email, minLength, etc.) for common validation needsangular-developer skill — references/signal-forms.md for form(), schema(), validators and FormFieldまだレビューはありません。使ってみた感想をお寄せください。
ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。
まだコメントはありません。最初のコメントを書いてみませんか。
Run a structured bug-focused code review for selected files, folders, or the whole workspace.
日本語の概要は準備中です。原文の説明を表示しています。
Generates Angular code and provides architectural guidance. Trigger when creating projects, components, services, or HTTP communication, or for best practices on reactivity (signals, linkedSignal, resource, httpResource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, naming conventions, or CLI tooling.
日本語の概要は準備中です。原文の説明を表示しています。
Write disciplined Behavior-Driven Development specs and tests with Gherkin / Cucumber. Use whenever the user mentions BDD, Gherkin, Cucumber, .feature files, Given/When/Then, scenarios, Scenario Outline, Background, step definitions, acceptance criteria, executable specifications, living documentation, the Three Amigos, or Example Mapping. Also trigger when the user asks to "write tests in plain English", convert acceptance criteria into tests, design step definitions, refactor an imperative or UI-coupled feature file, or wire up BDD with Vitest (unit/component) or Playwright (end-to-end). Do NOT use for pure unit tests with no business-readable contract, pure performance/load tests, or generic test-framework questions unrelated to behavior specs.
日本語の概要は準備中です。原文の説明を表示しています。
Build, refactor, and architect Angular state with the @ngrx/signals Signal Store. Use whenever the user mentions signalStore, withState, withMethods, withComputed, withHooks, withLinkedState, withFeature, signalStoreFeature, withEntities, rxMethod, signalMethod, patchState, the SignalStore events plugin (withReducer, withEventHandlers), or wants to refactor BehaviorSubject/service-based state to Signal Store. Also use for designing custom store features, request status, optimistic updates, entity collections, and the store/service split in Angular. Do NOT use for @ngrx/store (classic Redux), createReducer/createEffect, or generic RxJS questions unrelated to Signal Store.
日本語の概要は準備中です。原文の説明を表示しています。
Generate an NgRx Signal Store with complete CRUD operations (API service, store, tests) for a given entity.
日本語の概要は準備中です。原文の説明を表示しています。
Review the current branch against its base branch: lint, tests, architecture and Angular 22 conventions, reported by severity.
日本語の概要は準備中です。原文の説明を表示しています。