跳到正文
cccskills
免费在 GitHub 公开

angular-signal-forms

Generate an Angular Signal Forms component with schema validation and dynamic field management for a given entity.

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(1)

  • SKILL.md12.1 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Angular Signal Forms Generator

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-developer skill's references/signal-forms.md for 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:

  1. Entity Definition: The target entity model must be defined and linked in the chat context
  2. Form Purpose: Specify the form's purpose (e.g., "User Registration", "Task Creation", "Profile Update")
  3. Form Type: Indicate if it's a create, edit, or multi-purpose form
  4. 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 } 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';

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,
    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
  }
}

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>
    <!-- [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>

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 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>

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

[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 */ },
  },
});

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 {
      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;
  }
}

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

  1. Schema-Based Validation: Always use Angular's built-in 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.
  2. Modern Angular Patterns: Use standalone components, function-based DI with inject(), and modern control flow (@if, @for)
  3. Signal-First Approach: Leverage signals for all reactive state management
  4. Material Design: Use Angular Material components for consistent UI and built-in accessibility
  5. Type Safety: Maintain strict TypeScript typing throughout the form with proper interfaces
  6. Dirty Check at Field Level: Use field().dirty() on individual fields — the form-level form() object only exposes valid(), not dirty()
  7. Responsive Design: Ensure forms work well on all device sizes
  8. Error Handling: Provide clear, user-friendly error messages with custom messages in validators
  9. Performance: OnPush and zoneless are Angular 22 defaults — don't set changeDetection; derive state with computed
  10. Built-in Validators: Use Angular's built-in validators (required, email, minLength, etc.) for common validation needs

Key References

  • angular-developer skill — references/signal-forms.md for form(), schema(), validators and FormField
  • Styling and TypeScript conventions from the project instructions

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Run a structured bug-focused code review for selected files, folders, or the whole workspace.

中文简介正在准备中。当前显示原文说明。

danielsogl/copilot-workflow-demo392026年10月22日 更新

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.

中文简介正在准备中。当前显示原文说明。

danielsogl/copilot-workflow-demo392026年10月22日 更新

bdd

免费

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.

中文简介正在准备中。当前显示原文说明。

danielsogl/copilot-workflow-demo392026年10月22日 更新

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.

中文简介正在准备中。当前显示原文说明。

danielsogl/copilot-workflow-demo392026年10月22日 更新

Generate an NgRx Signal Store with complete CRUD operations (API service, store, tests) for a given entity.

中文简介正在准备中。当前显示原文说明。

danielsogl/copilot-workflow-demo392026年10月22日 更新

Review the current branch against its base branch: lint, tests, architecture and Angular 22 conventions, reported by severity.

中文简介正在准备中。当前显示原文说明。

danielsogl/copilot-workflow-demo392026年10月22日 更新

查看 danielsogl 的全部技能

报告此技能的问题