จัดการ array ของ FormGroup ด้วย FormArray ใน Reactive Form

เขียนเมื่อ 6 ปีก่อน โดย Ninenik Narkdee
formgroup reactive form formarray

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ formgroup reactive form formarray

ดูแล้ว 15,462 ครั้ง


เรามาถึงตอนสุดท้ายของ reactive form แล้ว เนื้อหานี้น่าจะเป็นตอนที่ 4 เป็นเนื้อหา
เกี่ยวกับการใช้งาน FormArray รวมถึง การจัดเตรียมข้อมูลใน form data สำหรับส่งค่า
หรือนำค่าไปใช้งาน เมื่อมีการ save หรือบันทึกข้อมูลจาก Reactive form
 
เนื้อหาต่อจากตอนที่แล้ว ทบทวนบทความได้ที่
การใช้งาน data model และ form model ร่วมกับ Reactive Form 
 
สามารถดูเนื้อหาทั้งหมดเกี่ยวกับ reactive form โดยคลิกที่ tags หัวข้อ "reactive form" ที่แสดง
ด้านบน อยู่ใต้ หัวข้อหลักของบทความ
 

รู้จักกับ FormArray คืออะไร

เราได้รู้จักกับ FormControl และ FormGroup มาพอสมควรแล้ว   โดย FormGroup ก็คือชื่อเรียก object
ที่มีค่าของ property เป็น FormControl หรือ เป็น FormGroup ย่อย ดูรูปประกอบส่วนของโค้ดด้านล่าง
 


 
 
ในการกำหนดฟอร์ม บางครั้งเราอาจจำเป็นจะต้องใช้จำนวนของ control หรือ group ตามที่เราต้องการ อย่างในกรณี
ตัวอย่างของเรา staff สามารถมีระดับการศึกษาได้หลายรูปแบบ เช่น ไม่มีการระบุระดับการศึกษ มีการศึกษาระดับเดียว
หรือ มีจำนวนระดับการศึกษาไม่จำกัด ยกตัวอย่าง staff บางคนอาจจะจบระดับปริญญาตรี และปริญญาโท เป็นต้น
 
staffForm.education คือรูปแบบ property ที่เป็น array ของ ตัวอย่างข้อมูล "Education" โดย "education"
เป็น FormGroup ย่อย ที่สามารถแสดงตัวอย่างข้อมูล "Education" ได้เพียงหนึ่งข้อมูล 
และเราสามารถใช้ FormArray ใน Angular ในการแสดง array ของ "education" FormGroup ที่มีมากกว่าหนึ่งข้อมูลได้
 
เพือใช้งาน FormArray ให้ทำการ import class มาใช้งาน ดังนี้
 

ไฟล์ staff-detail.component.ts (บางส่วน)

import { Component, Input, OnChanges } from '@angular/core';
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
 
เราจะใช้ FormArray ทำอะไรบ้าง
  • กำหนดรายการ FormControl หรือ FormGroup ในรูปแบบ array
  • กำหนดค่าเริ่มต้นให้กับ FormArray ด้วยการสร้างรายการด้วยข้อมูลจาก data model
  • เพิ่มหรือลบจำนวนรายการ FormArray ตามที่เรากำหนด
 
ในตัวอย่างต่อไปนี้ เราจะใช้ FormArray ในการเพิ่มหรือแก้ไขรายการเกี่ยวกับ ระดับการศึกษาของ staff
 
สิ่งที่เราต้องทำในส่วนของ form model ใน StaffDetailComponent คือใน constructor ตอนนี้เราแสดง
การศึกษาแรกของ staff ใน "education" FormGroup
 

ไฟล์ staff-detail.component.ts (บางส่วน)

this.staffForm = this.fb.group({// <-- the parent FormGroup
  name: ['',Validators.required], 
  education: this.fb.group(new Education()),
  age: '',
  gender: '',
  vacation: '' 
});
 
พิจารณาในส่วนนี้
 
education: this.fb.group(new Education()),
 
ในฟังก์ชั่น this.fb.group() เรากำหนด constructor โดยให้แสดงเฉพาะระดับการศึกษาแรกของ staff ด้วย
การสร้างตัวอย่างข้อมูลด้วย new Education()
 
เราจะเปลี่ยนสุดนี้ให้อยู่ในรูปแบบ array โดยใช้ FormArray  เป็นดังนี้
 

this.staffForm = this.fb.group({// <-- the parent FormGroup
  name: ['',Validators.required], 
  academics: this.fb.array([]), // <-- academics as an empty FormArray
  age: '',
  gender: '',
  vacation: '' 
});

 
 
ตอนนี้เราเปลี่ยนชื่อ property ของ FormGroup  จาก education เป็น academics แล้วกำหนดให้เป็น
FormGroup แบบ array ในเบื้องต้นเป็นให้เป็น ค่าว่าง
 
ในการเปลี่ยนชื่อข้างต้น เราควรใช้ชื่อที่ให้ความหมายใกล้เคียงกับชื่อเดิม แต่ต้องไม่เป็นชื่อที่ตรงกับ
ชื่อใน data model ซึ่งในข้างต้นเราใช้ academocs มีความหมายถึง เกี่ยวกับการศึกษา มาแทนคำว่า education
 
 

กำหนดค่าเริ่มต้นให้กับ FormArray

สิ่งที่เราต้องทำต่อก็คือ สร้างคำสั่งสำหรับจัดการระดับการศึกษาของ staff ที่จะถูกส่งค่าเมื่อมีการเลือก staff ใดๆ
เกิดขึ้น โดยเราจะสรัางฟังก์ชั่น ชื่อ setEducation() เพื่อทำการแทนที่ ค่าของ "academics" FormArray ด้วย
ค่า FormArray ใหม่ โดยอาศัย array ค่าเริ่มต้นของ "education" FormGroup  ดูตัวอย่างฟังก์ชั่น และทำ
ความเข้าใจไปพร้อมกัน
 
  setEducation(educations:Education[]){
    const educationFGs = educations.map(education => this.fb.group(education));
    const educationFormArray = this.fb.array(educationFGs);
    this.staffForm.setControl('academics', educationFormArray);
  }
 
ฟังก์ชั่น setEducation() จะกำหนด parameter ชื่อ educations รับข้อมูลที่เป็น array ของ Education object 
จากนั้นส่งเข้ามาในฟังก์ชั่น สังเกตรูปแบบการใช้คำสั่ง .map()  คำสั่ง map() เป็นคำสั่งที่ใช้จัดการข้อมูล array
สมมติ
 
var a = [1,2,3]; // a คือตัวแปร array
var b = a.map(ฟังก์ชั่นทำคำสั่งวนลูปค่าตัวแปร a); // ตัวแปร b จะมีค่าเท่ากับ 
// ค่าของตัวแปร a ทีวนลุปเรียกใช้ฟังก์ชั่น และคืนค่าออกมา 
// ตัวอย่างเช่น

var a = [1,2,3]; 
var b = a.map(
	function(x){
		return x*2;	
	}
);
// b = [2,4,6];
// หรือถ้าเขียนในรูปแบบ array function ใน ES6 จะได้เป็น
var b = a.map( x => x*2 );
 
ดังนั้นรูปแบบ
 
const educationFGs = educations.map(education => this.fb.group(education));
 
ก็มาจาก
const educationFGs = educations.map(
	function(education){
		return this.fb.group(education);
	}
);
 
const เป็นการกำหนดค่าให้ตัวแปร แบบไม่สามารถแก้ไขค่าได้ คำสั่งข้างต้นก็คือ เมาเราส่ง array ของ Education object
เข้ามา array นั้น ก็จะไปวนลูป สร้างเป็น array ของ FormGroup ย่อย ด้วยคำสั่ง this.fb.group() จากนั้นนำ array
ของ FormGroup ย่อยที่ได้ ไปกำหนดค่าให้กับตัวแปร educationFGs ที่ไม่สามารถแก้ไขค่าได้
 
จากนั้น เอา array ของ FormGroup ที่ได้ ที่อยู่ในตัวแปร educationFGs ไปกำหนดเป็น FormArray ด้วยคำสั่ง
this.fb.array() แล้ว นำ FormArray ที่ได้ไปกำหนดไว้ในตัวแปร ชื่อ educationFormArray
 
const educationFormArray = this.fb.array(educationFGs);
 
เสร็จแล้ว ทำการสร้าง control ให้กับ staffForm จาก FormArray  โดยใช้คำสั่ง setControl() 
 
this.staffForm.setControl('academics', educationFormArray);
 
ลำดับการทำงานของฟังก์ชั่น ก็คือ เอา array ของ Education object มาสร้างเป็น array ของ FormGroup
จากนั้นเอา array ของ FormGroup มากำหนดเป็น FormArray และสุดท้าย เอา FormArray ไปกำหนด
เป็น control เข้าไปในฟอร์ม 
 
ฟังก์ชั่นคำสั่ง setEducation() ข้างต้น เราจะให้ทำงานเมื่อมีการเปลี่ยนแปลงค่า staff ตอนเลือกรายการ
ซึ่งก็คือในคำสั่ง ngOnChanges()  รอดูโค้ดในลำดับต่อไป
 

การอ่านข้อมูลจาก FormArray 

ทุกครั้งที่มีการเปลี่ยนแปลงข้อมูลของ staff ระดับการศึกษาของแต่ละคนก็จะเปลี่ยนไปด้วย
ดังนั้น เพื่อใช้ค่า ไปแสดงในฟอร์ม เราต้องทำการเรียกค่าข้อมูลมาใช้ ดังนี้
 
  get academics(): FormArray {
    return this.staffForm.get('academics') as FormArray;
  };
 
คำสั่งนี้เป็นการไปดึงข้อมูล form model ที่ชื่อ academics ออกมาในรูปแบบ FormArray
ราจะได้ค่า academics เป็น FormArray  เรามาดูส่วนของโค้ด ที่ได้กำหนดใน StaffDetailComponent class
 

ไฟล์ staff-detail.component.ts (บางส่วน)

export class StaffDetailComponent{
  @Input() staff:Staff;
  
  staffForm: FormGroup; 
  degrees = degrees;

  constructor(private fb: FormBuilder) { 
    this.createForm();
  }
   
  createForm() {
    this.staffForm = this.fb.group({// <-- the parent FormGroup
      name: ['',Validators.required], 
      academics: this.fb.array([]), // <-- academics as an empty FormArray
      age: '',
      gender: '',
      vacation: '' 
    });
  }    

  ngOnChanges(staff: Staff){
    this.staffForm.reset({
      name:    this.staff.name,
      age:this.staff.age,
      gender:this.staff.gender,
      vacation:this.staff.vacation
    });
    this.setEducation(this.staff.education);
  }

  setEducation(educations:Education[]){
    const educationFGs = educations.map(education => this.fb.group(education));
    const educationFormArray = this.fb.array(educationFGs);
    this.staffForm.setControl('academics', educationFormArray);
  }
  get academics(): FormArray {
    return this.staffForm.get('academics') as FormArray;
  };  

}
 
 
สังเกตว่า เมื่อมีการคลิกชื่อ staff คนใหม่ คำสั่ง ngOnChanges() ก็จะทำงาน พร้อมส่ง staff ที่เลือกเข้ามา
ค่าต่างๆ จะถูกกำหนดไปยัง form model ผ่าน คำสัง reset() ยกเว้น ค่าที่เป็น education object ที่เป็น array
ค่าจะถูกนำไปกำหนดใน fom model ด้วยคำสั่ง setEducation()  โดยคำสั่งนี้จะไปสร้าง array ของ FormGroup
ตามจำนวนของ education object ที่ส่งเข้ามา ซึ่งที่เรายังจำได้ "Manop" มี education object อยู่สองค่า หรือ
มีระดับการศึกษาอยู่สองรับ คำสั่ง setEducation() ก็จะไปวนลูปสร้าง FormGroup มาสองค่า เพื่อที่จะนำไปแสดง
ใน template หลังจากทำการสร้าง form model ทุกค้าเรียบร้อยแล้ว คำสั่ง academics() จะไปทำการดึงข้อมูล
ของ FormArray เฉพาะในส่วนของ education มา โดยค่าจะอยู่ในตัวแปร academics เพราะมีการเรียกใช้ฟังก์ชั่น
ด้วย get 
 
ตอนนี้เราจัดการในส่วนของ form model เรียบร้อยแล้ว ต่อไปก็ส่วนของการนำไปแสดงในฟอร์ม template 
ซึ่งจากเดิม เราจะแสดงระดับการศึกษาเพียงระดับเดียว แต่ในรูปแบบการใช้งาน FormArray เราจะแสดงระดับการศึกษา
ตามจำนวนที่เป็นจริงของข้อมูล
 

แสดงข้อมูลจาก FormArray ใน template

ให้เราปรับไฟล์ staff-detail.component.html ตามวิธีดังนี้
 
1. คลุมส่วนของ รายละเอียดเกี่ยวกับการศึกษา ด้วย <div> โดยกำหนด "formArrayName" directive ให้เท่ากับ
"academics" ค่า "academics" ตัวนี้คือชื่อ form model  
 
<div formArrayName="academics"  class="well well-lg">
<!-- education template -->
</div>
 
แล้วคลุมทับ <div> ที่วนลูปด้วย *ngFor 
 
<div formArrayName="academics"  class="well well-lg">
    <div *ngFor="let education of academics.controls; let i=index" [formGroupName]="i" >
    <!-- The repeated education template -->
    </div>
</div>
 
2. ข้อมูลรายการที่วนลูป คือ FormArray.controls ในตัวอย่างคือ academics.controls ไม่ใช่ค่า academics
ที่เป็น FormArray โดยตรง 
 
3. แต่ละ FormGroup ที่วนลูป จำเป็นต้องกำหนด "formGroupName" ไม่ให้ซ้ำกัน ในตัวอย่างเราใช้ index ของ
array ใน FormArray มาใช้ 
 
เราจะได้ไฟล์ staff-detail.component.html ในส่วนของการกำหนด Education เป็นดังนี้
 

ไฟล์ ไฟล์ staff-detail.component.html (บางส่วน)

<div formArrayName="academics"  class="well well-lg">
    <div *ngFor="let education of academics.controls; let i=index" [formGroupName]="i" >   
      <!-- The repeated education template -->
      <h4>Education #{{i + 1}}</h4>
      <div class="form-group">
        <label class="center-block">Degree:
          <select class="form-control" formControlName="degree">
              <option *ngFor="let degree of degrees" [value]="degree">{{degree}}</option>
          </select>
        </label>
      </div>
      <div class="form-group">
        <label class="center-block">Discipline:
          <input class="form-control" formControlName="discipline">
        </label>
      </div>
      <div class="form-group">
        <label class="center-block">Year:
          <input class="form-control" formControlName="year">
        </label>
      </div>      
    </div>
</div>
 
ทดลองรัน App ดูความแตกต่างเมื่อเรากดเลือก "Manop" 
 


 
 
ส่วนของระดับการศึกษา ของ "Manop" จะวนลูปแสดงรายการ ระดับการศึกษา สองครั้ง
ตามข้อมูลรายการจาก data model  เทียบกับเมื่อกดเลือก "Jubjang"
 


 
 
จะแสดงข้อมูลรายการระดับการศึกษาเพียงรายการเดียว
 
เรามาดูโครงสร้างข้อมูล form data ของ "Manop" จะเป็นดังนี้
 
Form value: {
  "name": "Manop",
  "age": 27,
  "gender": "male",
  "vacation": false,
  "academics": [
    {
      "degree": "Master",
      "discipline": "MBA",
      "year": "2017"
    },
    {
      "degree": "Bachelor",
      "discipline": "Engeering",
      "year": "2014"
    }
  ]
}
 
ต่อไปมาดูส่วนของ "Jubjang" 
 
Form value: {
  "name": "Jubjang",
  "age": 30,
  "gender": "female",
  "vacation": true,
  "academics": [
    {
      "degree": "Bachelor",
      "discipline": "Accounting",
      "year": "2008"
    }
  ]
}
 
 

การเพิ่ม FormArray ใหม่เข้าไปใน Form model

ตอนนี้เราสามารถแสดงรายการของระดับการศึกษของ staff แต่ละคนได้แล้ว และถ้าเราต้องการที่จะเพิ่มระกับการ
ศึกษา เช่น Jubjang เรียนต่อปริญญาโท ต้องการเพิ่มระดับการศึกษาเข้าจะทได้อย่างไร
 
วิธีการสามารถทำได้อย่างง่าย โดยเพิ่มฟังก์ชั่นดังนั้น
 
  addAcademic() {
    this.academics.push(this.fb.group(new Education()));
  }
 
เราอาจะจำปุ่ม แทกเข้าไป แล้วเรียกใช้คำสั่งเพิ่ม ระดับการศึกษาในรูปแบบดังนี้
 
<button (click)="addAcademic()" type="button">Add an Education</button>
 
ทดสอบดู สมมติเราเปิดดูข้อมูลของ "Jubjang" ดูในส่วนของระดับการศึกษา จะได้เป็น
 


 
 
จะเห็นว่ามีระดับการศึกษาเดียว แล้วเราต้องการเพิ่มระดับการศึกษา เรากดที่ปุ่มที่เราสร้างขึ้น
และมีการเรียกใช้ฟังก์ชั่น addAcademic() จะได้ผลลัพธ์ดังรูป
 
 

 
 
จะเห็นว่าเราสามารถเพิ่มรายการในส่วนของระดับการศึกษาจำนวนเท่าไหร่ก็ได้
 

การลบ FormArray ออกจาก Form mdel

กรณีที่เราต้องการลบรายการ FormArray ออกจาก form model นั้นสามารถทำได้โดยการส่งค่า index ของ
รายการที่เราต้องการลบมาใช้ในคำสั่ง removeAt() รูปแบบคำสั่งที่เรากำหนดมาใช้งานจะเป็นดังนี้
 
  removeAcademic(i:number) {
    this.academics.removeAt(i);
  }
 
ตัว i คือ ค่า index ของ formGroup ใน FormArray ที่เราต้องการลบ เราสามารถกำหนดปุ่ม ไว้ในลูป
ของ template ในลักษณะดังนี้
 
<button (click)="removeAcademic(i)" type="button">Remove Education</button>
 
โดยปุ่มดังกล่าวข้างต้น จะต้องอยู่ใน *ngFor เพื่ออ้างอิงค่า index จากตัวแปร i ในการส่งค่ามาทำการลบ FormArray
 
 

สังเกตเปลี่ยนแปลงค่าของ Form Control

Angular จะเรียกคำสั่ง ngOnChanges ให้ทำงานเมื่อ มีการเปลี่ยนแปลงค่าของ input property ใน 
StaffDetailComponent ที่เป็น component ย่อย จากการที่ผู้ใช้ ทำการเลือก staff ใน StaffListComponent ที่เป็น 
component หลัก
 
Angular จะไม่มีการเรียกใช้คำสั่ง ngOnChanges เมื่อผู้ใช้แก้ไขข้อมูลในฟอร์ม อย่างเช่น แก้ไข "name" หรือ 
"academics" แต่เราสามารถสังเกตการเปลี่ยนแปลงของค่าดังกล่าวข้างต้น โดยอาศัย form control property 
เมื่อมีการเปลี่ยนแปลงค่าเกิดขึ้น
 
property ที่กล่าวข้างต้น ก็อย่างเช่น "valueChanges" จะคืนค่า RxJS Observable ตรงจุดนี้เราไม่ต้องสนใจว่า
RxJS Observable ติดตามดูค่าของ form control อย่างไร
 
ลองทดสอบโดยเพิ่มคำสั่งด้านล่างนี้ ซึ่งทำหน้าที่เก็บข้อมูลการเปลี่ยนแปลงของ form control ที่ชื่อว่า "name" 
โดยเรียกใช้งานคำสั่งผ่าน ส่วนของ constructor ดูส่วนของโค้ดด้านล่างประกอบ
 

ไฟล์ staff-detail.component.ts (บางส่วน)

  nameChangeLog: string[] = [];
  logNameChange() {
    const nameControl = this.staffForm.get('name');
    nameControl.valueChanges.forEach(
      (value: string) => this.nameChangeLog.push(value)
    );
  }  
  
  constructor(private fb: FormBuilder) { 
    this.createForm();
    this.logNameChange();
  }
 
การทำงานของคำสั่งข้างต้นคือ เมื่อ คำสั่ง logNameChange() ถูกเรียกใช้งาน
ก็จะทำการเรียกใช้งาน form control ที่ชื่อ "name" ผ่านคำสั่ง this.staffForm.get('name') แลัวนำมา
กำหนดในตัวแปร nameControl form control ดังกล่าวมี property ที่ชื่อ valueChanges
โดยเมื่อมีการแก้ไขค่าของ "name" ในฟอร์ม ข้อมูลที่เปลี่ยนแปลง ก็จะถูกวนลูปด้วยคำสั่ง forEach() ดึง
แต่ละค่าแล้วใส่เข้าไปใน array ที่ชื่อ nameChangeLog 
 
ซึ่งจริงๆ แล้วการที่เราจะเช็คหรือตรวจสอบการเปลี่ยนแปลงของ formControl เราสามารถใช้วิธีการ
การเรียกใช้ด้วยวิธีการแทรกค่าตัวแปร เช่น 
 
<div>{{ this.staffForm.get("name").value }} </div>
 
แต่ที่มาแนะนำการใช้งานผ่าน property ของ form control ก็เพื่อให้เห็นว่า เราสามรถจัดการส่วน
เหล่านี้ได้ภายใน component class 

 
 

การบันทึกข้อมูลจากฟอร์ม และการยกเลิกการเปลี่ยนแปลง

มาถึงห้วข้อสุดท้ายของการใช้งานเกี่ยวกับ reactive form ตอนนี้เรามีข้อมูลในฟอร์มเรียบร้อยแล้ว 
ถ้าเราต้องการจะนำข้อมูลไปใช้งานต้องทำอย่างไร ในที่นี้คงไม่ได้ลงลึกไปถึงการส่งค่า
แต่ต้องการให้เห็นโครงสร้างของข้อมูลที่เราจะนำไปใช้งาน และอีกส่วนคือ การยกเลิกการเปลี่ยนแปลง
เช่น สมมติเราแก้ไขไปสักพัก แต่เกิดเปลี่ยนใจ อยากให้กลับเป็นค่าเก่า เริ่มต้นก่อนที่จะเปลี่ยนแปลง ต้องทำอย่างไร
 
ให้เราแก้ไขไฟล์ staff-detail.component.html โดยทำการเพิ่มปุ่มเข้าไป สองปุ่ม เป็นปุ่ม submit ข้อมูลฟอร์ม
และปุ่ม revert หรือยกเลิกการเปลี่ยนแปลงค่า และใน <form> element เราทำการกำหนด event (ngSubmit) 
ให้ทำคำสั่ง onSubmit() เมื่อมีการทำการบันทึกข้อมูล
 

ไฟล์ staff-detail.component.html  (ทั้งหมด)

<h2>Staff Detail</h2>
<h3><i>A FormGroup with multiple FormControls</i></h3>
<form [formGroup]="staffForm" (ngSubmit)="onSubmit()" novalidate>
  <div style="margin-bottom: 1em">
    <button type="submit"
            [disabled]="staffForm.pristine" class="btn btn-success">Save</button> &nbsp;
    <button type="reset" (click)="revert()"
            [disabled]="staffForm.pristine" class="btn btn-danger">Revert</button>
  </div>  
  <div class="form-group">
    <label class="center-block">Name:
      <input class="form-control" formControlName="name">
    </label>
  </div>
<div formArrayName="academics"  class="well well-lg">
    <div *ngFor="let education of academics.controls; let i=index" [formGroupName]="i" >   
      <!-- The repeated education template -->
      <h4>Education #{{i + 1}}</h4>
      <div class="form-group">
        <label class="center-block">Degree:
          <select class="form-control" formControlName="degree">
              <option *ngFor="let degree of degrees" [value]="degree">{{degree}}</option>
          </select>
        </label>
      </div>
      <div class="form-group">
        <label class="center-block">Discipline:
          <input class="form-control" formControlName="discipline">
        </label>
      </div>
      <div class="form-group">
        <label class="center-block">Year:
          <input class="form-control" formControlName="year">
        </label>
      </div>    
      <button (click)="removeAcademic(i)" type="button">Remove Education</button>  
    </div>
    <button (click)="addAcademic()" type="button">Add an Education</button>
</div>  
  <div class="form-group">
    <label class="center-block">Age:
      <input class="form-control" formControlName="age">
    </label>
  </div>   
  <div class="form-group radio">
    <h4>Gender:</h4>
    <label class="center-block"><input type="radio" formControlName="gender" value="male">Male</label>
    <label class="center-block"><input type="radio" formControlName="gender" value="female">Female</label>
  </div>  
  <div class="checkbox">
    <label class="center-block">
      <input type="checkbox" formControlName="vacation">I have a vacation.
    </label>
  </div>       
</form>
<p>Form value: {{ staffForm.value | json }}</p>
<p>Form status: {{ staffForm.status | json }}</p>
<div>{{ this.staffForm.get("name").value }} </div>
<h4>Name change log</h4>
<div *ngFor="let name of nameChangeLog">{{name}}</div>
 
 

ผลลัพธ์เบื้องต้นเราจะได้เป็นดังนี้ แสดงรูปบางส่วน

 



 
 
เราเลือก "Jubjang" สังเกตว่าปุ่มทั้งสองที่เราเพิ่มเข้ามาจะยังเป็น diabled คือไม่สามารถกดได้
ทั้งนี้ก็เพราะเราทำการเชื่อมโยงด้วย "disabled" property กับค่าการตรวจสอบฟอร์ม 
 
[disabled]="staffForm.pristine" 
 
ถ้ายังไม่ได้อะไรเกี่ยวกับฟอร์ม เช่น ยังไม่มีการแก้ไขใดๆ เกิดขึ้น ฟอร์มยังบริสุทธ์อยู่ ให้ disabled ปุ่มก่อน
แต่ถ้าสมมติเราลองลบชื่อ Jubjang ออก ปุ่มก็จะขึ้นให้สามารถกดได้ทันที
 
ในส่วนของ <form> element เรามีการกำหนด event (ngSubmit) โดยเมื่อเรากดปุ่ม save ที่มี type เป็นปุ่ม
Submit ฟอร์มก็จะเกิด event submit ขึ้น แล้วเราก็ให้ไปทำงานคำสั่ง onSubmit() ที่เราจะกำหนดใน 
component class อีกที 
 
 

คำสั่งเมื่อกดปุ่ม Save

สิ่งที่เราต้องทำคือ ทำการจัดรูปแบบข้อมูลให้เหมือนกับ class ของ Staff Object 
อย่างในฟอร์มเราไม่มี id ของ staff ที่เราแก้ไข และกำลังจะนำค่าไปอัพเดทหรือบันทึก 
ดังนั้น เราต้องทำการจัดเตรียมข้อมูลก่อน คำสั่ง onSubmit() เราจะมีลักษณะเป็นดังนี้
 
  onSubmit() {
    this.staff = this.prepareSaveStaff();
    console.log(this.staff);
    // คำสั่งที่จะเอาค่า this.staff ไปใช้งาน
    this.ngOnChanges(this.staff);
  }
 
คำสั่ง onSubmit() จะทำการเรียกใช้คำสั่ง prepareSaveStaff() ซึ่งจะคืนค่าเป็นข้อมูลในรูป
Staff object นำค่ามาไว้ในตัวแปร staff ทางฝั่งซ้าย จากนั้นเราก็สามารถนำค่า staff นี้ไปใช้งาน
ต่อได้ ในตัวอย่างเราแสดงค่าของ staff ออกทาง console เท่านั้น
หลังจากนำค่าไปบันทึกหรือใช้งานแล้ว ก็ทำการเรียกใช้คำสั่ง ngOnChanges() ส่งค่าข้อมูล
staff ที่แก้ไขแล้ว แสดงในฟอร์มอีกครั้ง หรือไม่ก็ให้คำสั่งอื่นๆ เช่น เปลี่ยนไปหน้าอื่น แบบนี้เป็นต้น
 
เรามาดูส่วนของคำสั่ง prepareSaveStaff() โดยคำสั่งนี้ จะทำการจัดเตรียมข้อมูลให้อยู่ในรูปแบบที่พร้อมสำหรับ
นำไปใช้งาน
 
  prepareSaveStaff(): Staff {
    const formModel = this.staffForm.value;

    const academicsDeepCopy: Education[] = formModel.academics.map(
      (education: Education) => Object.assign({}, education)
    );

    const saveStaff: Staff = {
      id: this.staff.id as number,
      name: formModel.name as string,
      age: formModel.age as number,
      gender:formModel.gender as string,
      vacation:formModel.vacation as boolean,
      education: academicsDeepCopy
    };
    return saveStaff;
  }
 
 
มาดูส่วนแรก 
 
const formModel = this.staffForm.value;
 
ทำการอ้างอิง ค่าข้อมูลจาก form model ที่ชื่อ staffForm ผ่านตัวแปร formModel
 
ต่อด้วยบรรทัด
 
    const academicsDeepCopy: Education[] = formModel.academics.map(
      (education: Education) => Object.assign({}, education)
    );
 
วนลูปในส่วนของ Form control ที่เป็น FormArray นำค่ามากำหนดให้อยู่รูปแบบ array ของ object
เก็บไว้ในตัวแปร academicsDeepCopy
 
ส่วนต่อมา กำหนดค่า property ของ Staff object ที่ชื่อ saveStaff ให้มีโครงสร้างรูปแบบเหมือนกับ
โครงสร้างของ Staff object ที่เราจะนำไปใช้งาน จากนั้น return ค่ารูปแบบที่จัดเรียบร้อยแล้วออกมา
 
    const saveStaff: Staff = {
      id: this.staff.id as number,
      name: formModel.name as string,
      age: formModel.age as number,
      gender:formModel.gender as string,
      vacation:formModel.vacation as boolean,
      education: academicsDeepCopy
    };
    return saveStaff;
 
สังเกตตรง this.staff.id อันนี้ไม่ต้องงงว่า staff.id มาจากไหน staff.id ก็มาจาก input property ที่ชื่อ staff
ที่เมื่อเราคลิกเลือก staff ใดๆ  staff object ของคนๆนั้น ก็จะถูกส่งค่าด้วย
 

คำสั่งเมื่อกดปุ่ม Revert

เมื่อเราต้องการยกเลิกการเปลี่ยนแปลง เราสามารถกำหนดคำสั่ง สำหรับทำการ revert ข้อมูล
ให้กลับเป็นข้อมูลเดิมก่อนการเปลี่ยนแปลงได้ดังนี้
 
revert() { this.ngOnChanges(null); }
 
เราจะได้ไฟล์ staff-detail.component.ts สุดท้ายเป็นดังนี้
 

ไฟล์ staff-detail.component.ts ทั้งหมด

import { Component, Input, OnChanges } from '@angular/core';
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';

import { Education, Staff, degrees } from './data-model';

@Component({
  selector: 'staff-detail',
  templateUrl:'/app/components/staff-detail.component.html'
})
export class StaffDetailComponent{
  @Input() staff:Staff;
  
  staffForm: FormGroup; 
  degrees = degrees;

  nameChangeLog: string[] = [];
  logNameChange() {
    const nameControl = this.staffForm.get('name');
    nameControl.valueChanges.forEach(
      (value: string) => this.nameChangeLog.push(value)
    );
  }  

  constructor(private fb: FormBuilder) { 
    this.createForm();
    this.logNameChange();
  }
   
  createForm() {
    this.staffForm = this.fb.group({// <-- the parent FormGroup
      name: ['',Validators.required], 
      academics: this.fb.array([]), // <-- academics as an empty FormArray
      age: '',
      gender: '',
      vacation: '' 
    });
  }    

  ngOnChanges(staff: Staff){
    this.staffForm.reset({
      name:    this.staff.name,
      age:this.staff.age,
      gender:this.staff.gender,
      vacation:this.staff.vacation
    });
    this.setEducation(this.staff.education);
  }

  setEducation(educations:Education[]){
    const educationFGs = educations.map(education => this.fb.group(education));
    const educationFormArray = this.fb.array(educationFGs);
    this.staffForm.setControl('academics', educationFormArray);
  }
  get academics(): FormArray {
    return this.staffForm.get('academics') as FormArray;
  };  

  addAcademic() {
    this.academics.push(this.fb.group(new Education()));
  }

  removeAcademic(i:number) {
    this.academics.removeAt(i);
  }

  prepareSaveStaff(): Staff {
    const formModel = this.staffForm.value;

    const academicsDeepCopy: Education[] = formModel.academics.map(
      (education: Education) => Object.assign({}, education)
    );

    const saveStaff: Staff = {
      id: this.staff.id as number,
      name: formModel.name as string,
      age: formModel.age as number,
      gender:formModel.gender as string,
      vacation:formModel.vacation as boolean,
      education: academicsDeepCopy
    };
    return saveStaff;
  }

  onSubmit() {
    this.staff = this.prepareSaveStaff();
    console.log(this.staff);
    // คำสั่งที่จะเอาค่า this.staff ไปใช้งาน
    this.ngOnChanges(this.staff);
  }

  revert() { this.ngOnChanges(null); }

}
 
 

ทดสอบรัน App 

เมื่อเราเลือก "Jubjang" แล้วลองเปลี่ยนชื่อเป็น "Jubjang2" จะเป็นดังรูป
 


 
 
จากนั้นลองกดปุ่ม Revert ค่าทั้งหมดก็จะกลับมาเป็นเหมือนเดิม ก่อนการเปลี่ยนแปลงค่า ที่ยังไม่ได้บันทึก
 


 
 
ทีนี้เราลองเปลี่ยนแปลงค่าเป็น Ebiwayo แล้วก็เปลี่ยน Education degree เป็น Master 
 


 
 
จากนั้นกดปุ่ม Save จะได้เป็น
 


 
 
สังเกต Staff object ที่แสดงผ่าน console ทางฝั่งขวา ในรูป จะเห็นว่าชื่อ มีการเปลี่ยนเป็น Ebiwayo
และ education ใน array แรก มี degree เปลี่ยนเป็น Master ค่าตรงนี้ เป็นค่าที่เราจะนำไปใช้งานนั้นเอง
 
 
ถึงตอนนี้ก็เป็นอันจบส่วนของเนื้อหาเกี่ยวกับ Reactive Form จะเห็นว่าเนื้อหาค่อนข้างเริ่มจะเข้มข้นขึ้นเรื่อยๆ
สำหรับตอนต่อไป จะเกี่ยวกับอะไร ให้รอติดตาม และอย่าลืมทบทวน บทความซ้ำๆ เพื่อทำความเข้าใจให้มากยิ่งขึ้น

 


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ







เนื้อหาที่เกี่ยวข้อง









URL สำหรับอ้างอิง





คำแนะนำ และการใช้งาน

สมาชิก กรุณา ล็อกอินเข้าระบบ เพื่อตั้งคำถามใหม่ หรือ ตอบคำถาม สมาชิกใหม่ สมัครสมาชิกได้ที่ สมัครสมาชิก


  • ถาม-ตอบ กรุณา ล็อกอินเข้าระบบ
  • เปลี่ยน


    ( หรือ เข้าใช้งานผ่าน Social Login )







เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ