เรามาถึงตอนสุดท้ายของ reactive form แล้ว เนื้อหานี้น่าจะเป็นตอนที่ 4 เป็นเนื้อหา
เกี่ยวกับการใช้งาน FormArray รวมถึง การจัดเตรียมข้อมูลใน form data สำหรับส่งค่า
หรือนำค่าไปใช้งาน เมื่อมีการ save หรือบันทึกข้อมูลจาก Reactive form
เนื้อหาต่อจากตอนที่แล้ว ทบทวนบทความได้ที่
การใช้งาน data model และ form model ร่วมกับ Reactive Form
https://www.ninenik.com/content.php?arti_id=795 via @ninenik
สามารถดูเนื้อหาทั้งหมดเกี่ยวกับ 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> <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 จะเห็นว่าเนื้อหาค่อนข้างเริ่มจะเข้มข้นขึ้นเรื่อยๆ
สำหรับตอนต่อไป จะเกี่ยวกับอะไร ให้รอติดตาม และอย่าลืมทบทวน บทความซ้ำๆ เพื่อทำความเข้าใจให้มากยิ่งขึ้น