FormGroup, FormControl, FormBuilder, Validators

Reactive Forms ใช้ FormControl เพื่อควบคุม input เดี่ยว ใช้ FormGroup เพื่อรวมหลาย control ใช้ FormArray เพื่อรองรับรายการแบบ dynamic และใช้ Validators เพื่อตรวจสอบความถูกต้องของข้อมูล

Timeline/ประวัติศาสตร์

%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#fabd2f", "primaryTextColor": "#282828", "primaryBorderColor": "#b57614", "lineColor": "#7c6f64", "secondaryColor": "#83a598", "tertiaryColor": "#b8bb26", "background": "#fbf1c7", "mainBkg": "#ebdbb2", "fontFamily": "Tahoma, sans-serif"}}}%%
flowchart LR
  subgraph Era1["ยุค Manual Validation / Manual Checks"]
    A["if input === ''
ตรวจเองทีละ field"] end subgraph Era2["ยุค Form Model / Reactive Forms"] B["FormControl
ควบคุม input"] C["FormGroup
รวมหลาย field"] end subgraph Era3["ยุค Dynamic Validation / Advanced Forms"] D["FormArray
รายการ dynamic"] E["Custom/Async Validator
กติกาเฉพาะระบบ"] end A --> B --> C --> D --> E

แนวคิดสำคัญ

ตารางเปรียบเทียบส่วนประกอบ Reactive Forms

ส่วนประกอบ หน้าที่ ตัวอย่าง
FormControl ควบคุม field เดี่ยว name
FormGroup รวมหลาย field profile form
FormArray รายการ dynamic เบอร์โทรหลายเบอร์
FormBuilder สร้างฟอร์มแบบสั้น fb.group()
Validators ตรวจความถูกต้อง required, email

Mermaid Diagram: Reactive Form Model

%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#fabd2f", "primaryTextColor": "#282828", "primaryBorderColor": "#b57614", "lineColor": "#7c6f64", "secondaryColor": "#83a598", "tertiaryColor": "#b8bb26", "background": "#fbf1c7", "mainBkg": "#ebdbb2", "fontFamily": "Tahoma, sans-serif"}}}%%
flowchart TD
  A["FormGroup
ฟอร์มหลัก"] --> B["FormControl: name
ชื่อ"] A --> C["FormControl: email
อีเมล"] A --> D["FormArray: skills
ทักษะหลายรายการ"] B --> E["Validators.required
ต้องกรอก"] C --> F["Validators.email
รูปแบบอีเมล"]

Code Example

// profile-form.component.ts
// Reactive Form พร้อม built-in validator และ custom validator
import { Component } from '@angular/core';
import { AbstractControl, FormArray, FormBuilder, ValidationErrors, Validators } from '@angular/forms';

function noAdminName(control: AbstractControl): ValidationErrors | null {
  return control.value?.toLowerCase() === 'admin'
    ? { reservedName: true }
    : null;
}

@Component({
  selector: 'app-profile-form',
  templateUrl: './profile-form.component.html'
})
export class ProfileFormComponent {
  constructor(private fb: FormBuilder) {}

  profileForm = this.fb.group({
    name: ['', [Validators.required, Validators.minLength(3), noAdminName]],
    email: ['', [Validators.required, Validators.email]],
    skills: this.fb.array([
      this.fb.control('HTML')
    ])
  });

  get skills(): FormArray {
    return this.profileForm.get('skills') as FormArray;
  }

  addSkill(): void {
    this.skills.push(this.fb.control('', Validators.required));
  }

  save(): void {
    if (this.profileForm.valid) {
      console.log(this.profileForm.value);
    }
  }
}

// ตัวอย่างการใช้งาน:
// กด Add Skill เพื่อเพิ่ม FormControl ใหม่ใน FormArray
<!-- profile-form.component.html -->
<form [formGroup]="profileForm" (ngSubmit)="save()">
  <input formControlName="name" placeholder="ชื่อ">
  <p *ngIf="profileForm.controls.name.hasError('reservedName')">
    ห้ามใช้ชื่อ admin
  </p>

  <input formControlName="email" placeholder="อีเมล">

  <div formArrayName="skills">
    <input *ngFor="let skill of skills.controls; let i = index" [formControlName]="i">
  </div>

  <button type="button" (click)="addSkill()">Add Skill</button>
  <button type="submit" [disabled]="profileForm.invalid">Save</button>
</form>

กิจกรรมท้ายบท

กลับสัปดาห์ที่ 9