d2-05-form-page について
エンティティレコードの作成・編集フォームを実装するAngular開発スキルです。ルートに応じたモード切り替え、既存データの読み込み、Reactive Formsによる検証と送信を説明します。
主な機能
- 01ルートから作成・編集モードを判定
- 02Reactive Formsによるフォーム構築
- 03フィールドごとの検証ルール設定
- 04編集時のレコード読み込みと入力
- 05検証後の保存または更新
活用シーン
- 01エンティティ項目の検証付きフォーム作成
- 02作成と編集で同じページを使用
- 03無効なフォームの送信防止
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ softinter-chiangrai/ up-ai-skill-training --skill d2-05-form-pageインストール後、Claude Code で/d2-05-form-pageを入力して読み込まれたか確認します。
詳細
2 日目・ステップ 5 — 機能に応じた作成/更新を行うフォームページ
注記: このスキルは、d2-03-main-menu で選択した機能に応じて動作します。
createを選択した場合:新しいレコードを作成できますsaveを選択した場合:既存のレコードを更新できます- どちらも選択すると、作成/更新の両方を行えます
ルート: すでに d2-03-main-menu(完全な app.routes.ts)で定義されています。このスキルでは、次のルートを使用します。
- 新しいレコードを作成する場合は
/<entity>/newルート - 既存のレコードを編集する場合は
/<entity>/:id/editルート
前提条件: app.config.ts に ReactiveFormsModule を追加します。
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(FormGroup を持つスタンドアロンコンポーネント):
ReactiveFormsModule、FormBuilder、FormGroup、Validatorsをインポートします。- エンティティの各フィールドに
FormControlを持つform: FormGroupを作成し、Validators.requiredやその他のルール(例:minLength、min)を追加します。 - 初期化時に、ルートから
idを取得します(ActivatedRoute)。idがない(またはid === 'new')場合は作成モード(空のフォーム)です。idがある場合は編集モードです(getById(id)を呼び出し、form.patchValue(data)でフォームに値を事前入力します)。 - キャンセルボタン → 保存せずに
router.navigate(['/<entity>'])を実行します。 - 保存ボタン → 送信前に
form.validを確認します。作成モードではsave(form.value)を呼び出し、編集モードではupdate(id, form.value)を呼び出します。成功したらrouter.navigate(['/<entity>'])を実行します。 - フォームが無効な場合は、送信ボタンを無効にします(
[disabled]="form.invalid")。
コンポーネントの 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>']);
}
}
テンプレート(バリデーション、フォーカスリング、標準の 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>
フォームのカスタマイズ:
empCode、empName、address、phoneNoを、エンティティの実際のプロパティ名とバリデーターに置き換えます- エンティティの構造に合わせて、フォームフィールドを追加または削除します
- フィールドごとにバリデーターのルール(required、minLength、pattern など)を調整します
バリデーターのリファレンス:
Validators.required:フィールドを空にできませんValidators.minLength(n):最小文字数の要件Validators.pattern(regex):カスタム正規表現によるパターンマッチングValidators.min(n)/Validators.max(n):数値の範囲を検証します
エラーの表示:
- フィールドにエラーがあり、かつ操作済みの場合にエラーメッセージを表示します
- 特定のエラーを確認するには
form.get('fieldName')?.hasError('errorType')を使用します
コメント
Markdown 対応