Note: This skill adapts based on capabilities selected in d2-03-main-menu:
- If
createis selected: allows creating new records - If
saveis 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>/newroute for creating new records/<entity>/:id/editroute 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:
```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,phoneNowith 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 emptyValidators.minLength(n): Minimum length requirementValidators.pattern(regex): Custom regex pattern matchingValidators.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