Angular LogoAngular Tutorials

Angular Forms

Một vài thứ mà forms có thể làm:
  • Khởi tạo các form fields và hiển thị chúng cho user
  • Thu thập dữ liệu từ user
  • Theo dõi các thay đổi được thực hiện đối với các field
  • Validate the inputs
  • Hiện thị các lỗi hữu ích cho user
Angular có 2 cách để xây dựng forms:
So sánhReactive formsTemplate-driven forms
Giống nhau Cả hai forms đều:
  • Capture user input
  • Validate user input
  • Tạo một form model và data model để update
  • Cung cấp cách để theo dõi các thay đổi (track changes)
LogicLogic của form được đặt trong component như 1 objectLogic của form được đặt trong template
Set up form model Rõ ràng (Explicit), được tạo trong component class => Có thể truy cập trực tiếp vào FormControl instance (form API). Ngụ ý (implicit), được tạo bởi các directive => Chỉ có thể truy cập FormControl instance thông qua NgModel directive
Data modelCó cấu trúc và bất biến (Structured and immutable)Không có cấu trúc và có thể thay đổi (Unstructured and mutable)
Data flow between the view and the data modelSynchronous => Giúp tạo các form lớn và phức tạp dễ dàng hơnAsynchronous
Form validationFunctionsDirectives
Khả năng mở rộng (Scalability) Form model được tạo trong component class nên có thể reuse và dễ mở rộng hơn => Phù hợp cho các form quan trọng hoặc ứng dụng sử dụng reactive pattern. Phù hợp cho các form đơn giản và không thể reuse
Testing Dễ hơn do không yêu thiết lập quá nhiều thứ và cũng không cần hiểu rõ về change detection. Yêu cầu thiết lập nhiều hơn và phải hiểu rõ về change detection để run manual (thủ công).
  1. Template-driven :
    • Logic của form được đặt trong template.
    • Phù hợp khi muốn tạo các form đơn giản mà không cần phải reuse.
    • Quá trình cập nhật thay đổi diễn ra giữa view và component là Asynchronous.
    • Sử dụng các directive để validate input fields.
  2. Reactive forms (còn gọi là model-driven forms):
    • Logic của form được đặt trong component như 1 object. Trong component class:
      • Tạo form model với Form Groups, Form Controls và Form Arrays.
      • Define validation rules: define các custom validator như các function nhận vào control để validate.
      • Bind form model và validation rules vào HTML form trong template.
    • Quá trình cập nhật thay đổi diễn ra giữa view và component là Synchronous.
    • Cách tiếp cận theo hướng model có nhiều lợi ích hơn vì nó được tạo trong comonent class với các Form field như các property của component class.Do đó, nó làm cho việc kiểm tra component dễ dàng hơn.
Angular Forms (cả Template-driven và Reactive forms) có 3 khối Building (Building Blocks):
  1. FormControl
    • Đại diện cho 1 input field duy nhất như input,...
    • FormControl là một object chứa tất cả thông tin liên quan đến một input element, cho phép bạn điều khiển input element đó như value, validation status,...
  2. FormGroup: là một tập hợp các FormControl, mỗi FormControl được khai báo như một property của object.
  3. FormArray: là một mảng (array) các FormControl, mỗi FormControl là một phần tử trong mảng.
Ưu điểm
  • Chứa ít code trong component class
  • Dễ set up
Nhược điểm
  • Khó add dynamic control
  • Khó Unit test

Các bước xây dựng Template-driven forms:

  1. Import FormsModule vào root module (hoặc shared module).
    FormsModule chứa tất cả các directive form và các cấu trúc để làm việc với forms. FormsModule phải được import nếu bạn muốn sử dụng ngModel.
    Khi chúng ta import FormsModule, ngForm directive của Angular sẽ tự động tìm tất các các <form> trong HTML template và tự động binding với chính nó.
  2. Tạo HTML Form
  3. Export ngForm instance thành một template reference variable (do ngForm directive đã sử dụng exportAs trong Directive Decorator).
    <form #contactForm="ngForm">
    Điều này cho phép chúng ta truy cập các property và method của ngForm qua template reference variable contactForm
    Nhiệm vụ của ngForm:
    1. Binding chính nó với <Form> directive
    2. Tạo một top-level FormGroup instance
    3. Tạo FormControl instance cho mỗi field chứa ngModel.
    4. Tạo FormGroup instance cho mỗi ngModelGroup directive
    Ngoài ra, chúng ta cũng có thể export FormControl/FormGroup instance thành template reference variable tương ứng với exportAs: ngModel/ngModelGroup.
  4. Gắn ngModel cho các form field. Bạn cũng có thể cung cấp biến cho ngModel để two-way binding với component.
    <input type="text" name="firstname" ngModel>
    ngModel sẽ sử dụng name attribute để tạo FormControl instance cho mỗi form field được gắn nó.
    Trong Template-driven forms, chúng ta sử dụng ngModelngModelGroup directive trên các HTML element. FormsModule sau đó sẽ tạo các FormControl & FormGroup instance từ template.
  5. Validate form
  6. Submit form data cho component sử dụng ngSubmit event.
    Chúng ta sử dụng event binding để bind ngSubmit với onSubmit method in component class
    <form #contactForm="ngForm" (ngSubmit)="onSubmit(contactForm)">
    Khi user click submit button (type="submit"), ngSubmit event sẽ kích hoạt.
    Chúng ta truyền local template variable contactForm vào onSubmit. contactForm giữ tham chiếu đến ngForm directive, bằng cách này chúng ta có thể truy xuất data form field từ component class.

Get form controls từ forms

@ViewChild('contactForm') contactForm: NgForm;
this.contactForm.controls['country'].setValue('vn');
this.contactForm.control.get('country').setValue('vn');

Demo

Ưu điểm
  • Có thể truy cập và tương tác trực tiếp với các form API (FormControl,...)
  • Dễ dàng tạo các custom validators bằng cách tạo ra các function nhận vào control.
  • Hỗ trợ tạo các dynamic form
  • Dễ dàng reuse và mở rộng
  • Testing đơn giản hơn so với Template-driven forms.
Nhược điểm
  • Cần học nhiều thứ hơn để có thể làm việc với các Reactive forms có logic xử lý phức tạp.

Các bước xây dựng và sử dụng Reactive Forms

  1. Import ReactiveFormsModule vào root module hoặc shared module - e.g: AppModule;
    import { ReactiveFormsModule } from '@angular/forms';
  2. Tạo Form Model trong component class sử dụng Form Group, Form Control & Form Arrays;
    Import FormGroup, FormControl & Validator từ @angular/forms.
    import { FormGroup, FormControl, Validators } from '@angular/forms'
    Tạo FormGroup
    contactForm = new FormGroup({})

    The FormGroup takes 3 arguments. a collection of a child FormControl, a validator - Sync Validator, and an asynchronous validator - Async Validator. The validators are optional.

    Tạo FormControl
            contactForm = new FormGroup({
              firstname: new FormControl(),
              lastname: new FormControl(),
              email: new FormControl(),
              gender: new FormControl(),
              isMarried: new FormControl(),
              country: new FormControl()
            })

    The FormControl takes 3 arguments. a default value, a validator - Sync Validator, and an asynchronous validator - Async Validator. The validators are optional.

  3. Tạo HTML Form tương ứng với Form Model trong template.
  4. Bind HTML Form với Form Model sử dụng [FormGroup]formControlName.
    1. Binding the template to the model - set FormGroup directive bằng model (contactFrom defined in component class)
      <form [formGroup]="contactForm">
    2. Binding từng form field element trong form của chúng ta với FormControl models sử dụng FormControlName (formControlName) directive. Note: value được gán cho formControlName tương ứng với FormControl instance đã khai báo trong component class.
      <input type="text" id="firstname" name="firstname" formControlName="firstname">
      <input type="text" id="lastname" name="lastname" formControlName="lastname">
  5. Submit form sử dụng (ngSubmit)
    Chúng ta sử dụng event binding để bind ngSubmit với onSubmit method in component class:
    <form [formGroup]="contactForm" (ngSubmit)="onSubmit()">

Demo

Default Value

Bạn có thể truyền giá trị mặc định dưới dạng primitive values (string,...) hoặc object (key-value) cho FormControl khi khởi tạo.
// Setting Default value as string
firstname= new FormControl('Kenji');
Khi bạn truyền object, bạn có thiết lập cả valuedisabled (Nếu gán true sẽ disable element).
// Setting Default value & disabled state as object
firstname: new FormControl({value: 'Kenji', disabled: true}),

Có 2 cách để set default value cho forms:

  1. Set giá trị mặc định cho từng FormControl khi tạo form bằng cách truyền default value vào tham số đầu tiên của FormControl.
    firstname: new FormControl('Nguyen'),
    firstname: [''],
  2. Dùng setValue trong ngOnInit method.
    ngOnInit() {
      this.setDefault();
    }
      
    setDefault() {
      let contact = {
        firstname: "Sachin",
        lastname: "Tendulkar",
        email: "sachin@gmail.com",
        gender: "male",
        isMarried: true,
        country: "2",
        address: {
          city: "Mumbai",
          street: "Perry Cross Rd",
          pincode: "400050"
        }
      };
    
      this.reactiveForm.setValue(contact);
    }
    Note: contact object phải bao gồm tất cả các controls của form và phải chính xác số lượng control (không ít hơn, cũng không được nhiều hơn).

Sync Validator

  • The second parameter is an array of sync Validators.
  • Sync validators chạy validation và trả về kết quả ngay lập tức.
  • Trả về một danh sách errors hoặc null nếu validate thành công.
Note: Chúng cần disable browser validator cần cách thêm novalidate attribute vào form element. Bằng cách này, chúng ta sẽ ngăn chặn built-in HTML5 validate form khi submit.
<form [formGroup]="contactForm" (ngSubmit)="onSubmit()" novalidate>

Cách thêm validators cho FormControl

Angular has some built-in Validators such as required, pattern and minLength etc.
Required Validator
firstname: new FormControl('', [Validators.required]),
Dùng FormBuilder: firstname: ['', [Validators.required]]
Minlength Validator
firstname: new FormControl('', [Validators.required,Validators.minLength(10)]),
Maxlength Validator
lastname: new FormControl('',[Validators.maxLength(15)]),
Pattern Validator
lastname: new FormControl('',[Validators.maxLength(15), Validators.pattern("^[a-zA-Z]+$")]),
Chỉ cho phép ký tự chữ và không chứa khoảng trắng.
Email Validator
email:new FormControl('',[Validators.email,Validators.required]),

Disable Submit button

<button type="submit" [disabled]="!contactForm.valid">Submit</button>

Displaying the Validation/Error messages

Component Class
get firstname() { return this.contactForm.get('firstname'); }
Template
<div
    *ngIf=
"!firstname?.valid && (firstname?.dirty ||firstname?.touched)"> <div [hidden]="!firstname.errors.required"> First Name is required </div> <div [hidden]="!firstname.errors.minlength"> Min Length is 10 </div> </div>
Cách khác:
<div
    *ngIf=
"!contactForm.controls.firstname?.valid && (contactForm.controls.firstname?.dirty ||contactForm.controls.firstname?.touched)"> First Name is not valid </div>

Demo

Bảng ký tự đặc biệt dùng với biểu thức chính quy (regular expression)

RegexLink

Asynchronous validator

  • The third argument is the Async Validator. The syntax of Async Validators is similar to Sync Validators.
  • Async validators trả về một Promise hoặc Observable. Đây là điểm khác biệt so với sync validator.

Cách tạo custom Async Validator

  1. Tạo function implement AsyncValidatorFn Interface.
    interface AsyncValidatorFn { (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> }
  2. Function phải trả về ValidationErrors | null dưới dạng Promise hoặc Observable.
    import { AbstractControl, ValidationErrors } from '@angular/forms' import { Observable, of } from 'rxjs'; export function gte(control: AbstractControl): Observable<ValidationErrors> | null { const v:number=+control.value; if (...) { // Add validation logic return of({ 'gte': true, 'requiredValue': 10 }); } return of(null); }

Example

import { AbstractControl, ValidationErrors } from '@angular/forms' import { Observable, pipe } from 'rxjs'; import { map, debounceTime } from 'rxjs/operators'; export function validate(control: AbstractControl): Observable<ValidationErrors> | null { const value: string = control.value; return this.http.get(this.baseURL + 'checkIfValid/?value=' + value) .pipe( debounceTime(500), map( (data:any) => { if (!data.isValid) return ({ 'InValid': true }) }) ) }
Chúng ta có thể sử dụng combineLatest để merge data từ nhiều Observable và validate input.

Grouping the controls using FormGroup

Cách nhóm các form controls cũng giống như cách mà chúng tạo FormGroup - contactForm ban đầu.
Chúng ta sẽ tạo ra một FromGroup mới đặt tên là address và tạo thêm 3 form controls cho address group:
      contactForm = new FormGroup({
        ...,
        country: new FormControl(),
        address:new FormGroup({
          city: new FormControl(),
          street: new FormControl(),
          pincode:new FormControl()
        })
      })
    
Trong template, chúng ta sử dụng formGroupName trên một element bao các form control elements để đánh dấu một group các controls trong Form. Và đồng thời cũng sử dụng formControlName cho mỗi form control của address group.
<div formGroupName="address">

Sử dụng FormBuilder để build Forms

FormBuilder là API hỗ trợ xây dựng các reactive form trong Angular một cách đơn giản hơn. Nó cung cấp một syntax ngắn gọn hơn để tạo instance của FormControl, FormGroup, các FormGroup lồng nhau, hoặc FormArray => Giúp chúng ta giảm code required để viết các forms phức tạp.

Cách sử dụng FormBuilder

  1. Import & inject FormBuilder API vào component
    import { FormBuilder } from '@angular/forms'
    constructor(private formBuilder: FormBuilder)
  2. Use the group, array & control methods to build the FormModel:
    FormGroup
                  this.contactForm = this.formBuilder.group({
                    firstname: [''],
                    lastname: [''],
                    email: [''],
                    gender: [''],
                    isMarried: [''],
                    country: [''],
                  });
    Nested FormGroup
                this.contactForm = this.formBuilder.group({
                  ...,
                  country: [''],
                  address: this.formBuilder.group({
                    city: [''],
                    street: [''],
                    pincode: ['']
                  })
                })
    Validations
    The second argument to the FormControl is the list of sync validators.
                  this.contactForm = this.formBuilder.group({
                    firstname: ['', [Validators.required, Validators.minLength(10)]],
                    lastname: ['', [Validators.required, Validators.maxLength(15), Validators.pattern("^[a-zA-Z]+$")]],
                    email: ['', [Validators.required, Validators.email]],
                    gender: ['', [Validators.required]],
                    isMarried: ['', [Validators.required]],
                    country: ['', [Validators.required]],
                    address: this.formBuilder.group({
                      city: ['', [Validators.required]],
                      street: ['', [Validators.required]],
                      pincode: ['', [Validators.required]],
                    })
                  });

Demo

setValue

setValue(value: { [key: string]: any; }, options: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
Chúng ta sử dụng setValue để update FormControl, FormGroup, FormArray.
Note:
  • Khi sử dụng setValue để update, object truyền vào phải match chính xác với cấu trúc FormGroup hoặc FormArray đã tạo - object phải bao gồm tất cả form control của form (chính xác số lượng controls của form - không hơn, không kém), nếu không, chúng ta sẽ gặp lỗi.
  • Chúng ta thường dùng setValue để set default values và giá trị của một form control cụ thể.

patchValue

patchValue(value: { [key: string]: any; }, options: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
Chúng ta sử dụng patchValue khi chỉ muốn update một vài form controls mà chúng ta chỉ định và giữ nguyên giá trị của các form controls khác.

onlySelf

  • Giá trị mặc định của onlySelffalse ➞ Khi setValue của một form control nào đó, Angular sẽ check validation của form control đó. Sau đó, nó sẽ gửi kết quả validation của control này lên top-level FormGroup. Do đó, validation status của FormGroup trước và sau setValue có thể bị thay đổi.
  • Nếu set giá trị onlySelf: true ➞ Angular sẽ chỉ check validation của form control thực hiện setValue, không check lại validation của form control khác, và cũng không gửi kết quả validation của form control đó đến parent form group. Do đó, validation status của FormGroup trước và sau setValue không thay đổi.
    ➤ Do đó, khi onlySelf: true - StatusChanges và ValueChanges chỉ kích hoạt với controls được chỉ định, không kích hoạt với form.

emitEvent

Angular forms emit 2 event: ValueChanges (valueChanges Observable) và StatusChanges (statusChanges Observable). ValueChanges event được emit bất cứ khi nào value của form bị thay đổi. StatusChanges event được emit bất của khi nào Angular validate form.
  • Mặc định, emitEvent: true ➞ Kích hoạt ValueChangesStatusChanges.
  • Nếu emitEvent: false ➞ sẽ stop hành vi mặc định của ValueChanges và StatusChanges event. Không kích hoạt ValueChangesStatusChanges.

Cách get form controls từ FormGroup

contactForm: FormGroup;
this.reactiveForm.get("firstname");
this.reactiveForm.get("firstname").setValue("");

StatusChanges

  • StatusChanges được kích hoạt mỗi khi Angular validate FormControl/FormGroup/FormArray.
  • StatusChanges trả về một Observable emit latest validation status của control, do đó, chúng ta có thể subscribe nó để lấy validation status mới nhất của control hoặc form.
  • Validation status của control hoặc form là một trong 2 giá trị: VALID hoặc INVALID.

Quá trình kích hoạt StatusChanges của controls và form

  1. Thay đổi giá trị của input FormControl ⇒ Angular chạy validation để check input FormControl.
  2. Nếu validation tạo một list các validation error, status => INVALID. Nếu không có error, status => VALID.
  3. Kích hoạt StatusChanges của FormControl:
    this.reactiveForm.get("firstname").statusChanges.subscribe(newStatus=> { console.log(newStatus) //latest status console.log(this.reactiveForm.get("firstname").status) //latest status console.log(this.reactiveForm.status) //Previous status // waiting for the next tick setTimeout(() => { console.log(this.reactiveForm.status) //latest status }) })
    Note: Lúc này, status của control là mới nhất, NHƯNG status của parent form vẫn giữ status trước đó.
  4. Truyền validation status của control lên parent form => Angular update lại validation status của parent form và return VALID hoặc INVALID.
  5. Kích hoạt StatusChanges của cả FormGroup
    this.reactiveForm.statusChanges.subscribe(newStaus => { console.log('form Status changed event') console.log(newStaus) })
  6. Sau khi chạy xong StatusChanges của parent FormGroup, nếu trước đó chúng ta có sử dụng setTimeout bên trong StatusChanges của control thì nó sẽ run callback của setTimeout.

emitEvent & StatusChanges

Nếu chúng ta set option emitEvent: false thì StatusChange của control và parent form sẽ không được kích hoạt, dù chúng ta có thay đổi input hay không.
this.reactiveForm.get("firstname").setValue("", { emitEvent: false });
Chúng ta có thể sử dụng emitEvent: false với setValue, patchValue, markAsPending, disable, enable, updateValueAndValidity & setErrors methods.

onlySelf & StatusChanges

Khi chúng ta sử dụng onlySelf: true, các thay đổi chỉ ảnh hưởng đến FormControl và thay đổi không truyền lên parent của nó. Do đó, chỉ có StatusChanges của control được kích hoạt
this.reactiveForm.get("firstname").setValue("", { onlySelf: true });
Chúng ta có thể sử dụng onlySelf: true với setValue, patchValue, markAsUntouched, markAsDirty, markAsPristine, markAsPending, disable, enable, and updateValueAndValidity methods.

Demo

ValueChanges

  • ValueChanges được kích hoạt bất cứ khi nào value của FormControl/FormGroup/FormArray thay đổi.
  • ValueChanges return về một Observable, do đó, chúng ta có thể subscribe nó để lấy value mới nhất của control.
  • Nó cho phép chúng track changes value in real-time và phản hồi lại nó. Do đó, chúng ta có thể sử dụng nó để validate value, tính toán, so sánh các field/control.

Subscribe ValueChanges

  • Với FormControl:
    this.contactForm .get('firstname') .valueChanges.subscribe((selectedValue) => { console.log('firstname value changed'); console.log(selectedValue); //latest value of firstname console.log(this.contactForm.get('firstname').value); //latest value of firstname console.log(this.contactForm.value); //still shows the old first name });
    Note 1: valueChangesevent cho firstname được kích hoạt ngay lập tức sau khi new value được update NHƯNG trước khi thay đổi này được chuyển lên cho parent của nó. Do đó, this.contactForm.value vẫn giữ giá trị trước đó.
    Note 2: Ta có thể lấy giá trị mới nhất của this.contactForm.value bằng cách lấy nó trong callback của setTimeout. Callback của setTimeout lúc này sẽ nằm trong Callback Queue cho đến khi, Angular truyền thay đổi lên parent (FormGroup), update value của Form. Sau đó, Call Stack trống, callback của setTimeout trong callback queue sẽ được đưa vào Call Stack và thực thi.
        
    this.contactForm .get('firstname') .valueChanges.subscribe((selectedValue) => { console.log('firstname value changed'); console.log(selectedValue); //latest value of firstname console.log(this.contactForm.get('firstname').value); //latest value of firstname console.log(this.contactForm.value); //still shows the old first name setTimeout(() => { console.log(this.contactForm.value); //shows the latest first name }); });
  • Với FormGroup:
    this.contactForm.valueChanges.subscribe((objectValue) => { console.log('form value changed'); console.log(objectValue); });

EmitEvent & ValueChanges

Nếu chúng ta set value sử dụng option emitEvent: false thì ValueChanges sẽ không được kích hoạt.

this.contactForm.get("firstname").setValue("", { emitEvent: false });

Chúng ta có thể sử dụng emitEvent: false với setValue, patchValue, markAsPending, disable, enable, updateValueAndValidity & setErrors methods.

OnlySelf & ValueChanges

Khi onlySelf: true các thay đổi chỉ ảnh hưởng đến FormControl đang thực hiện thay đổi và các thay đổi không được truyền cho parents của nó. Do đó, ValueChanges của parent FormGroup không được kích hoạt ⇒ Chỉ kích hoạt ValueChanges của FormControl đang thực hiện thay đổi.

this.contactForm.get("firstname").setValue("", { onlySelf: true });

Chúng ta có thể sử dụng onlySelf: true với setValue, patchValue, markAsUntouched, markAsDirty, markAsPristine, markAsPending, disable, enable, and updateValueAndValidity methods.

Demo

Tài liệu tham khảo:

FormControl

FormControl là một trong 3 building block của angular forms, được dùng để thiết lập và theo dõi (value và validation status) một input element riêng lẻ.

<input type="text" name="firstname" />

Các cách sử dụng FormControl

  1. Setting the value
    setValue()
    abstract setValue(value: any, options?: Object): voidthis.reactiveForm.get("email").setValue("sachin.tendulakar@gmail.com");
    patchValue()
    abstract patchValue(value: any, options?: Object): voidthis.reactiveForm.get("email").setValue("sachin.tendulakar@gmail.com");
  2. Getting the current value - Readonly
    Sử dung get() method
    get()this.reactiveForm.get("firstname").value
    valueChanges
    valueChanges: Observable<any>
    this.fNameChange = this.reactiveForm.get("firstname").valueChanges.subscribe(x => { console.log(x); })
  3. Control validation status
    status
    status: VALID | INVALID | PENDING | DISABLED
    this.reactiveForm.get("firstname").status
    valid
    valid: boolean
    this.reactiveForm.get("firstname").valid
    invalid
    invalid: boolean
    this.reactiveForm.get("firstname").invalid
    pending - đang validation check
    pending: boolean
    this.reactiveForm.get("firstname").pending
    disabled
    disabled: boolean
    this.reactiveForm.get("firstname").disabled
    enabled
    enabled: boolean
    this.reactiveForm.get("firstname").enabled
    pristine - TRUE - nếu người dùng chưa thay đổi giá trị trong UI
    pristine: boolean
    this.reactiveForm.get("firstname").pristine
    dirty - TRUE - nếu người dùng đã thay đổi giá trị trong UI
    dirty: boolean
    this.reactiveForm.get("firstname").dirty
    touched - TRUE - Nếu đã trigger một blur event
    touched: boolean
    this.reactiveForm.get("firstname").touched
    untouched - TRUE - Nếu chưa trigger một blur event
    untouched: boolean
    this.reactiveForm.get("firstname").untouched
  4. Changing the Status
    markAsTouched
    markAsTouched(opts: { onlySelf?: boolean; } = {}): void
    this.reactiveForm.get("firstname").markAsTouched()
    this.reactiveForm.get("firstname").markAsTouched({ onlySelf:true; })
    markAllAsTouched
    markAllAsTouched(): void
    Đánh dấu control và tất cả các children control của nó là touched.
    markAsUntouched
    markAsUntouched(opts: { onlySelf?: boolean; } = {}): void
    onlySelf: true - Chỉ đánh dấu control, không đánh dấu children của nó.
    markAsDirty - Đánh dấu đã thay đổi value
    markAsDirty(opts: { onlySelf?: boolean; } = {}): void
    onlySelf: true - Chỉ đánh dấu control, không đánh dấu children của nó.
    markAsPristine - Đánh dấu chưa thay đổi value
    markAsPristine(opts: { onlySelf?: boolean; } = {}): void
    onlySelf: true - Chỉ đánh dấu control, không đánh dấu children của nó.
    markAsPending - Dùng trong lúc chúng ta custom validation check để đánh dấu PENDING trong lúc đang validate và chưa xong.
    markAsPending(opts: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
    onlySelf: true - Chỉ đánh dấu control, không đánh dấu children của nó.
    emitEvent: true hoặc không truyền - Sẽ kích hoạt statusChanges.
    disable - Disables the control. Dùng khi custom validators hoặc valueChanges.
    disable(opts: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
    onlySelf: true - Chỉ đánh dấu control, không đánh dấu children của nó.
    emitEvent: true hoặc không truyền - Sẽ kích hoạt cả statusChangesvalueChanges.
    enable - Enables control. Dùng khi custom validators hoặc valueChanges.
    disable(opts: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
    onlySelf: true - Chỉ đánh dấu control, không đánh dấu children của nó.
    emitEvent: true hoặc không truyền - Sẽ kích hoạt cả statusChangesvalueChanges.
  5. StautsChanges Event
    statusChanges: Observable<any>
  6. updateValueAndValidity() - Bắt buộc form thực thi validation. Điều này hữu ích khi bạn thêm / xóa validators dynamically bằng cách sử dụng setValidators, RemoveValidators, v.v.
    updateValueAndValidity(opts: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
    this.reactiveForm.get("firstname").updateValueAndValidity();
    onlySelf: true - Chỉ update control, không update children của nó.
    emitEvent: true hoặc không truyền - Sẽ kích hoạt cả statusChangesvalueChanges.
  7. setValidators() / setAsyncValidators() adds the sync or async validators. Phương pháp này sẽ remove tất cả các sync hoặc async validators đã thêm trước đó.
    setValidators(newValidator: ValidatorFn | ValidatorFn[]): void
    setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[]): void
    setValidator() { this.reactiveForm.get("firstname").setValidators([Validators.required, Validators.minLength(5)]); this.reactiveForm.get("firstname").updateValueAndValidity(); }
  8. clearValidators() / clearAsyncValidators() Clear tất cả các sync/async validators.
    clearValidators(): void
    clearAsyncValidators(): void
    clearValidation() { this.reactiveForm.get("firstname").clearValidators(); this.reactiveForm.get("firstname").updateValueAndValidity(); }
  9. errors() Một object chứa bất kỳ errors nào được tạo ra do không validate được hoặc null nếu không có lỗi.
    errors: ValidationErrors | null
    getErrors() { const controlErrors: ValidationErrors = this.reactiveForm.get("firstname").errors; if (controlErrors) { Object.keys(controlErrors).forEach(keyError => { console.log("firtname "+ ' '+keyError); }); } }
  10. setErrors()
    setErrors(errors: ValidationErrors, opts: { emitEvent?: boolean; } = {}): void
    setErrors() { this.reactiveForm.get("firstname").setErrors( {customerror:'custom error'}); }
  11. getError() - Reports error data for the control with the given path.
    getError(errorCode: string, path?: string | (string | number)[]): any
    this.reactiveForm.getError("firstname")
    this.reactiveForm.getError("address.pincode");
    this.reactiveForm.getError(["address","pincode"]);
  12. hasError() - Reports whether the control with the given path has the error specified.
    hasError(errorCode: string, path?: string | (string | number)[]): boolean
    this.reactiveForm.hasError("firstname");
    this.reactiveForm.hasError("address.pincode");
    this.reactiveForm.hasError(["address","pincode"]);
  13. reset() - Resets the control. We can also pass the default value.
    abstract reset(value?: any, options?: Object): void
    this.reactiveForm.get("firstname").reset('');
    this.reactiveForm.get("firstname").reset('test');

Tài liệu tham khảo:

FormGroup

  • FormGroup là một tập hợp nhiều FormControl.
  • FormGroup cũng là một trong 3 building blocks của Angular forms.
  • FormGroup theo dõi value và validation status của một nhóm các FormControl.
<form>
  
First Name : <input type="text" name="firstname" /> Last Name : <input type="text" name="lastname" /> Email : <input type="text" name="email" /> </form>
  • FormGroup hữu ích khi chúng ta muốn check validation, set value hoặc lắng change event,... cho nhiều FormControl.
  • FormGroup tổng hợp value của từng FormControl thành một object với control name như một key của object.
  • Validation status của FormGroup được thực hiện bằng cách reduce validation status value của tất cả các FormControl của nó. Do đó, nếu một control trong group invalid thì validation status của group sẽ là invalid.
FormGroup có tất cả các methods giống FormControl và có thêm một số methods của riêng ForGroup. Trong phần này, chúng ta chỉ tập trung vào các methods của riêng FormGroup, những method khác chúng ta xem tại phần FormControl.

Adding Controls Dynamically to Form Group

addControl() - Thêm một control vào FormGroup và cũng update validity & validation status. Nếu control đã tồn tại, nó sẽ bị bỏ qua (ignore).
addControl(name: string, control: AbstractControl): void
addControl() { this.middleName = new FormControl('', [Validators.required]); this.reactiveForm.addControl("middleName",this.middleName); }
registerControl() - Thêm một control vào FormGroup và nhưng không update validity & validation status. Nếu control đã tồn tại, nó sẽ bị bỏ qua (ignore).
registerControl(name: string, control: AbstractControl): AbstractControl
registerControl() { this.middleName = new FormControl('', [Validators.required]); this.reactiveForm.addControl("middleName",this.middleName); }
removeControl() - Xóa một control khỏi FormGroup.
removeControl(name: string): void
remodeControl() { this.reactiveForm.removeControl("middleName"); }
setControl() - Thay thế một control có name được cung cấp bằng một FormControl mới.
setControl(name: string, control: AbstractControl): void
setControl() { this.middleName = new FormControl('test', [Validators.required]); this.reactiveForm.setControl("middleName",this.middleName); }
contains() - Kiểm tra control với tên được cung cấp có tồn tại trong FormGroup hay không.
contains(controlName: string): boolean
containsControl() { console.log(this.reactiveForm.contains("middleName")); }

Tài liệu tham khảo

FormArray

FormArray cũng là một cách để nhóm các FormControl nhưng theo dạng mảng. Thường được dùng để tạo form dạng table.

FormArray cho phép chúng ta add controls dynamically.

Cách tạo và submit một FormArray

  1. Import FormArray từ @angular/forms
    import { FormGroup, FormControl,FormArray, FormBuilder } from '@angular/forms'
  2. Tạo Form Model
    skillsForm: FormGroup; constructor(private fb:FormBuilder) { this.skillsForm = this.fb.group({ name: '', skills: this.fb.array([]) , }); }
  3. Dùng getter method để get skills FormArray
    get skills() : FormArray { return this.skillsForm.get("skills") as FormArray }
  4. Viết 1 function return về 1 phần tử của skills FormArray
    newSkill(): FormGroup { return this.fb.group({ skill: '', exp: '', }) }
  5. Viết 1 function để dynamic add 1 phần tử vào skills FormArray bằng push method
    addSkills() { this.skills.push(this.newSkill()); }
  6. Viết 1 function để dynamic remove 1 phần tử ra khỏi skills FormArray bằng removeAt method
    removeSkill(i:number) { this.skills.removeAt(i); }
  7. Viết 1 function để submit form
    onSubmit() { console.log(this.skillsForm.value); }
  8. Tạo Form template
    Binding skills FormArray (name của FormArray) cho formArrayName
    <div formArrayName="skills"></div>
    Bây giờ, div và mọi thứ bên trong div element được liên kết với skills FormArray.
    Bên trong div sử dụng ngFor để lặp qua từng phần tử của skills FormArray
    <div formArrayName=
    "skills"> <div *ngFor="let skill of skills.controls; let i=index"> </div> </div>
    Final template
    Skills: <div formArrayName="skills"> <div *ngFor="let skill of skills.controls; let i=index"> <div [formGroupName]="i"> {{i}} skill name : <input type="text" formControlName="skill"> exp: <input type="text" formControlName="exp"> <button (click)="removeSkill(i)">Remove</button> </div> </div> </div>

Demo

Tài liệu tham khảo

Custom Validator

Built-in validators không thể cover hết tất cả các case trong quá trình chúng ta làm việc với form. Do đó, chúng ta có thể tạo ra một custom validator để sử dụng logic riêng của mình.

Cách tạo một custom validator

Validator là một function implement ValidatorFn interface. Nó nhận vào AbstractControl và trả về ValidationErrors object hoặc null.

ValidatorFn
interface ValidatorFn { (control: AbstractControl): ValidationErrors | null }
AbstractControl là base class cho FormControl, FormGroup và FormArray.
Example
export function gte(control: AbstractControl): ValidationErrors | null { const v:number=+control.value; // Get value from control if (...) { // Add logic here return { 'gte': true, 'requiredValue': 10 } } return null }
ValidationErrors là một object key-value kiểu [key: string]: bất kỳ và nó xác định quy tắc bị hỏng. keystring và phải chứa tên của quy tắc bị hỏng (nên lấy tên validator function). Giá trị có thể là bất kỳ giá trị nào, nhưng thường được đặt thành true.
return { 'gte': true, 'requiredValue': 10 }

Tạo Custom Validator nhận thêm tham số đầu vào

Bởi vì validator phải implement ValidatorFn Interface và ValidatorFn chỉ nhận một tham số i.e control: AbstractControl. Do đó, để truyền tham số cho custom validator, chúng ta cần tạo function nhận vào các tham số chúng ta muốn và phải trả về ValidatorFn function (function này nhận vào control: AbstractControl và trả về ValidationErrors | null) - A factory function.

export function gte(val: number): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { let v: number = +control.value; // Get value from control if (...) { // Add validation logic return { 'gte': true, 'requiredValue': val } } return null; } }

Read more