AngularのControlValueAccessor Part 2 — Validator

Suzuki Aki
odds.team
Published in
3 min readJul 15, 2018

จากคราวที่แล้วที่แนะนำการใช้ ControlValueAccessor ไป วันนี้จะมาต่อ เรื่องการทำ Validation สำหรับ Component ที่เป็น ControlValueAccessor

เรามี Component DateRangeComponent หน้าตาแบบนี้

<app-date-range 
[(ngModel)]="range"
name="range">
</app-date-range>

ก่อนอื่นมากำหนดกันก่อนว่าเราจะ Validate อะไรบ้าง

  1. ต้อง input ทั้ง startDate และ endDate
  2. endDate จะต้องไม่น้อยว่า startDate

ไปที่ไฟล์ date-range.component.ts เพิ่ม implements Validator

export class DateRangeComponent implements ControlValueAccessor, Validator {

กำหนด provider NG_VALIDATORS เพิ่มอีกอันใน @Component Decoratorให้คนอื่นมองเห็น DateRangeComponent เป็น Validator ด้วย

@Component({
selector: 'app-date-range',
templateUrl: './date-range.component.html',
styleUrls: ['./date-range.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: DateRangeComponent
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: DateRangeComponent
}
]
})

เพิ่ม method validate ใน class DateRangeComponent method นี้จะทำงานทุกครั้งเมื่อมีการ change ค่าของ form control ใน component

...validate(c: AbstractControl): { [key: string]: any; } {

}
...

เคสแรกก็เช็คว่าต้องมีทั้ง startDate และ endDate โดยเช็คจาก property startDate และ endDate ในกรณีที่อยากให้เป็น invalid return Object ที่มี key และ value ซึ่งเป็นรูปแบบของการ return ของ Validator ใน Angular ส่วนในกรณีที่ valid ให้ return null

...validate(c: AbstractControl): { [key: string]: any; } {
if (!this.startDate || !this.endDate) {
return { required: true };
}
return null;
}
...

ลองไป print ดูที่ app.component นักศึกษาจะเห็นได้ว่า form.valid จะมีค่าเป็น false

<form #f="ngForm" style="text-align:center">
<div>
<app-date-range
[(ngModel)]="range"
name="range">
</app-date-range>
</div>
<div>
<div>
Value: {{ f.value | json }}
</div>
<div>
Valid: {{ f.valid }}
</div>
</div>
</form>

เมื่อ fill ค่าลงทั้งสองตัว form มีค่า valid เป็น true

เคสที่สองคือ validate ว่า endDate จะต้องไม่น้อยกว่า startDate ก็ไปใส่เงื่อนไขเพิ่ม

...validate(c: AbstractControl): { [key: string]: any; } {
if (!this.startDate || !this.endDate) {
return { required: true };
}
if (this.startDate > this.endDate) {
return { wrongPeriod: true };
}
return null;
}
...

ลองเล่นดูที่หน้าจอ ถ้าเลือก startDate เป็นวันที่มากกว่า endDate form ก็จะ invalid

ทีนี้ลองทำให้ขึ้น error message โดย message ที่ขึ้นจะต้องบอกว่าผิดอย่างไร ก่อนอื่นไปสร้าง template variable ของ date-range ก่อน โดยใส่ #r=”ngModel” ที่ input ที่เรา bind ngModel ไว้ แล้วเราจะได้ตัวแปรซึ่งเป็น type FormControl ไว้สำหรับ get ค่า error

<form #f="ngForm" style="text-align:center">
<div>
<app-date-range
[(ngModel)]="range"
name="range"
#r="ngModel">
</app-date-range>
</div>
</form>

จาก method validate ที่เรา return object ออกมาก่อนหน้านี้ key ของ object จะเป็นตัวบ่งบอกได้ว่า เป็น error เคสอะไร เริ่มที่เคส required ก่อน เราสามารถ access type ของ error ได้จาก ตัวแปร r

<form #f="ngForm" style="text-align:center">
<div>
<app-date-range
[(ngModel)]="range"
name="range"
#r="ngModel">
</app-date-range>
</div>
<div *ngIf="r.invalid" style="color:red">
<div *ngIf="r.errors.required">
Please input both dates
</div>
</div>
</form>

ก็จะแสดง error message แบบนี้

เมื่อใส่ date ให้ครบทั้งสองตัว error message จะหายไป

อีกเคสนึงก็ไปเพิ่มอีกชุดนึง

<form #f="ngForm" style="text-align:center">
<div>
<app-date-range
[(ngModel)]="range"
name="range"
#r="ngModel">
</app-date-range>
</div>
<div *ngIf="r.invalid" style="color:red">
<div *ngIf="r.errors.required">
Please input both dates
</div>
<div *ngIf="r.errors.wrongPeriod">
Start date must not more than End date
</div>
</div>
</form>

เมื่อ startDate มากกว่า endDate ก็จะขึ้น error message แบบนี้

สรุป

การทำ Validator ให้กับ ControlValueAccessor Component มีประโยชน์มากในงานร่วมกับ form ทำให้ใช้ Feature form ของ Angular ได้อย่างเต็มที่
และ Logic ของการ validate จะอยู่ภายใน Component เท่านั้น ทำให้โค้ดที่ Component แม่ที่เป็น Form สะอาดขึ้น แบ่ง Concern อย่างชัดเจน

นอกจากนี้ยังสามารถใช้กับ Reactive Forms ได้ด้วย ดังนั้นเราสามารถใช้ Template-Drive Form และ Reactive Forms ผสมกันได้แล้วแต่ความเหมาะสม

--

--