mcpField
D
d2-05-form-page

d2-05-form-page

github.com/softinter-chiangraiDevelopment040 installsUpdated 9w ago
Repository

About d2-05-form-page

An Angular development skill for form pages that create or edit entity records. It describes route-based modes, loading existing values, and validation-gated submission with Reactive Forms.

Key features

  1. 01Detect create or edit mode from routes
  2. 02Build forms with Reactive Forms
  3. 03Configure validation rules per field
  4. 04Load and populate records for editing
  5. 05Save or update after validation

Use cases

  1. 01Create validated forms for entity fields
  2. 02Use one page for both create and edit
  3. 03Prevent submission of invalid forms

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/softinter-chiangrai/up-ai-skill-training --skill d2-05-form-page

After installing, type/d2-05-form-pagein Claude Code to confirm it loaded.

Details

From the project README

Day 2 · Step 5 — Form page with capability-based create/update

Note: This skill adapts based on capabilities selected in d2-03-main-menu:

  • If create is selected: allows creating new records
  • If save is selected: allows updating existing records
  • Both can be selected for full create/update functionality

Routes: Already defined in d2-03-main-menu (complete app.routes.ts). This skill uses:

  • /<entity>/new route for creating new records
  • /<entity>/:id/edit route for editing existing records

Prerequisites: Add ReactiveFormsModule to app.config.ts:

import { ReactiveFormsModule } from '@angular/forms';
import { provideClientHydration } from '@angular/platform-browser';
import { ApplicationConfig } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    provideClientHydration(),
    ReactiveFormsModule,
    // ... other providers
  ],
};

up-app/src/app/pages/<entity>-form/<entity>-form.component.ts (standalone component with FormGroup):

  • Import ReactiveFormsModule, FormBuilder, FormGroup, Validators.
  • Create form: FormGroup with FormControl for each entity field, add Validators.required and other rules (e.g., minLength, min).
  • On init, read id from route (ActivatedRoute). No id (or id === 'new') → create mode (empty form). id present → edit mode (call getById(id) and form.patchValue(data) to prefill).
  • Cancel button → router.navigate(['/<entity>']) without saving.
  • Save button → check form.valid before submit; create mode calls save(form.value), edit mode calls update(id, form.value); on success, router.navigate(['/<entity>']).
  • Disable Submit button when form is invalid ([disabled]="form.invalid").

Component TypeScript:

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { <Entity>Service } from '../../services/<entity>.service';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-<entity>-form',
  standalone: true,
  imports: [ReactiveFormsModule, CommonModule],
  templateUrl: './<entity>-form.component.html',
  styleUrls: ['./<entity>-form.component.css']
})
export class <Entity>FormComponent implements OnInit {
  form: FormGroup;
  isEdit = false;

  constructor(
    private fb: FormBuilder,
    private <entity>Service: <Entity>Service,
    private route: ActivatedRoute,
    private router: Router
  ) {
    this.form = this.fb.group({
      empCode: ['', [Validators.required, Validators.minLength(2)]],
      empName: ['', [Validators.required, Validators.minLength(3)]],
      address: [''],
      phoneNo: ['']
    });
  }

  ngOnInit() {
    const id = this.route.snapshot.paramMap.get('id');
    if (id && id !== 'new') {
      this.isEdit = true;
      this.<entity>Service.getById(+id).subscribe(data => {
        this.form.patchValue(data);
      });
    }
  }

  onSave() {
    if (this.form.invalid) return;
    const id = this.route.snapshot.paramMap.get('id');
    if (this.isEdit && id) {
      this.<entity>Service.update(+id, this.form.value).subscribe(() => {
        this.router.navigate(['/<entity>']);
      });
    } else {
      this.<entity>Service.save(this.form.value).subscribe(() => {
        this.router.navigate(['/<entity>']);
      });
    }
  }

  onCancel() {
    this.router.navigate(['/<entity>']);
  }
}

Template (with validation, focus ring, and standard Tailwind):

<div class="min-h-screen bg-gray-50 flex items-center justify-center p-4">
  <div class="bg-white rounded-lg shadow-lg p-8 max-w-md w-full">
    <h2 class="text-3xl font-bold mb-6 text-gray-900">{{ isEdit ? "Edit" : "Add New" }} <Entity></h2>

    <form [formGroup]="form" (ngSubmit)="onSave()">
      <div class="mb-6">
        <label class="block font-semibold mb-2 text-gray-700">Employee Code</label>
        <input
          type="text"
          formControlName="empCode"
          class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
          placeholder="e.g., EMP001"
        />
        @if (form.get("empCode")?.hasError("required") && form.get("empCode")?.touched) {
          <p class="text-red-500 text-sm mt-1 font-semibold">Employee Code is required</p>
        }
        @if (form.get("empCode")?.hasError("minlength") && form.get("empCode")?.touched) {
          <p class="text-red-500 text-sm mt-1 font-semibold">Code must be at least 2 characters</p>
        }
      </div>

      <div class="mb-6">
        <label class="block font-semibold mb-2 text-gray-700">Employee Name</label>
        <input
          type="text"
          formControlName="empName"
          class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
          placeholder="e.g., John Doe"
        />
        @if (form.get("empName")?.hasError("required") && form.get("empName")?.touched) {
          <p class="text-red-500 text-sm mt-1 font-semibold">Employee Name is required</p>
        }
        @if (form.get("empName")?.hasError("minlength") && form.get("empName")?.touched) {
          <p class="text-red-500 text-sm mt-1 font-semibold">Name must be at least 3 characters</p>
        }
      </div>

      <div class="mb-6">
        <label class="block font-semibold mb-2 text-gray-700">Address</label>
        <input
          type="text"
          formControlName="address"
          class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
          placeholder="Optional"
        />
      </div>

      <div class="mb-6">
        <label class="block font-semibold mb-2 text-gray-700">Phone Number</label>
        <input
          type="text"
          formControlName="phoneNo"
          class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
          placeholder="Optional"
        />
      </div>

      <div class="flex gap-4 mt-8">
        <button
          type="submit"
          [disabled]="form.invalid"
          class="flex-1 bg-green-500 hover:bg-green-600 disabled:bg-gray-300 text-white font-semibold px-6 py-3 rounded-lg transition disabled:cursor-not-allowed"
        >
          Save
        </button>
        <button
          type="button"
          (click)="onCancel()"
          class="flex-1 bg-gray-400 hover:bg-gray-500 text-white font-semibold px-6 py-3 rounded-lg transition"
        >
          Cancel
        </button>
      </div>
    </form>
  </div>
</div>

Form Customization:

  • Replace empCode, empName, address, phoneNo with your entity's actual property names and validators
  • Add/remove form fields to match your entity structure
  • Adjust validator rules (required, minLength, pattern, etc.) per field

Validators Reference:

  • Validators.required: Field cannot be empty
  • Validators.minLength(n): Minimum length requirement
  • Validators.pattern(regex): Custom regex pattern matching
  • Validators.min(n) / Validators.max(n): Number range validation

Error Display:

  • Show error messages when field has error AND has been touched
  • Use form.get('fieldName')?.hasError('errorType') for specific error checking

Comments

Markdown supported