본문으로 건너뛰기
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(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

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 / 1,000자

이 양식은 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의 스킬 모두 보기

이 스킬의 문제 신고