Reactive Forms Trong Angular (Phần 1)
- 15-01-2024
- Toanngo92
- 0 Comments
Mục lục
Reactive Forms
Reactive Forms, hay còn gọi là Model-driven Forms, là một phương pháp tạo form trong Angular mà không sử dụng các directive như ngModel hay required như ở Template-driven Forms. Thay vào đó, chúng ta khởi tạo các Object Model trong các Component và xây dựng form dựa trên chúng. Điều quan trọng là Reactive Forms thực hiện đồng bộ (sync), trong khi Template-driven Forms thực hiện không đồng bộ (async).
Trong Reactive Forms, chúng ta tạo toàn bộ cấu trúc form control ngay trong mã nguồn (ở trong constructor, ngOnInit, hoặc khởi tạo ngay lập tức). Điều này giúp chúng ta dễ dàng truy cập các thành phần của form ngay lập tức. Trạng thái của form trong Reactive Forms là bất biến (immutable), mọi thay đổi trạng thái của form sẽ tạo ra một trạng thái mới.
Reactive Forms sử dụng nhiều Observable streams, như valueChanges, statusChanges và bạn có thể kết hợp, thay đổi stream này giống như làm với các Observable thông thường. Quá trình validation trong Reactive Forms cũng đơn giản, chỉ là một hàm, và bạn có thể thay đổi nó trong khi ứng dụng đang chạy.
Tổng quan về Reactive Forms từ trang chính thức Angular (https://angular.io/guide/reactive-forms#overview-of-reactive-forms) mô tả rằng Reactive Forms sử dụng một cách tiếp cận rõ ràng và không thay đổi để quản lý trạng thái của form tại một thời điểm cụ thể. Mỗi thay đổi của trạng thái form trả về một trạng thái mới, duy trì tính toàn vẹn của mô hình giữa các thay đổi. Reactive Forms được xây dựng xung quanh các luồng Observable, nơi các giá trị và đầu vào của form được cung cấp dưới dạng các luồng giá trị đầu vào, có thể truy cập một cách đồng bộ.
Reactive Forms cũng mang lại một con đường trực tiếp để kiểm thử vì bạn có đảm bảo rằng dữ liệu của bạn là nhất quán và dự đoán được khi được yêu cầu. Mọi đối tượng sử dụng các luồng này có quyền truy cập để thao tác dữ liệu một cách an toàn.
So với Template-driven Forms, Reactive Forms khác biệt ở một số điểm quan trọng. Reactive Forms mang lại tính dự đoán cao với việc truy cập đồng bộ vào mô hình dữ liệu, tính không thay đổi với các toán tử Observable, và theo dõi thay đổi thông qua các luồng Observable. Template-driven Forms, ngược lại, cho phép truy cập trực tiếp để sửa đổi dữ liệu trong template, nhưng ít rõ ràng hơn Reactive Forms vì nó phụ thuộc vào các directive được nhúng trong template cùng với dữ liệu có thể thay đổi để theo dõi thay đổi một cách không đồng bộ.
Sign In Reactive Forms Component
Ví dụ như form Sign in chúng ta đã quen thuộc trong những ngày qua:
Chúng ta sẽ tạo một component mới để thực hiện bài học về đăng nhập (Sign In):
ng g c sign-in-rf
Sau đó, chúng ta cần cấu hình routing cho component này trong file routes:
const routes: Routes = [
{
path: "sign-in",
component: SignInComponent,
},
{
path: "sign-in-rf",
component: SignInRfComponent,
},
];
Tiếp theo, chúng ta sẽ xây dựng template cho component của mình. Đây sẽ là một form đơn giản với các trường như các phiên bản trước đây:
<div class="container">
<form class="sign-in-form">
<h2>Sign in</h2>
<div class="row-control">
<mat-form-field appearance="outline">
<mat-label>Username</mat-label>
<input matInput placeholder="Username" />
</mat-form-field>
</div>
<div class="row-control">
<mat-form-field appearance="outline">
<mat-label>Password</mat-label>
<input type="password" matInput placeholder="Password" />
</mat-form-field>
</div>
<div class="row-control">
<mat-checkbox>Remember me</mat-checkbox>
</div>
<div class="row-control row-actions">
<button mat-raised-button color="primary" type="submit">Sign in</button>
</div>
</form>
</div>
Sau khi ứng dụng được khởi chạy với lệnh ng serve
, bạn có thể truy cập trang web tại địa chỉ http://localhost:4200/sign-in-rf để xem giao diện của form đăng nhập Reactive Forms.
Integrate Angular Forms
Để tích hợp Reactive Forms vào ứng dụng Angular của bạn, bạn cần thêm một NgModule được gọi là ReactiveFormsModule
vào NgModule chịu trách nhiệm quản lý các component trong ứng dụng. Trong trường hợp này, chúng ta sẽ thêm vào AppModule
.
import { ReactiveFormsModule } from "@angular/forms";
@NgModule({
declarations: [
// các components, pipes, directives khác
],
imports: [
// các imports khác
ReactiveFormsModule,
],
// ...
})
export class AppModule {}
Bằng cách này, Angular sẽ hiểu rằng ứng dụng của bạn sử dụng Reactive Forms và có thể sử dụng các tính năng liên quan đến Reactive Forms trong các component của nó.
FormGroup, FormControl, FormArray
FormGroup, FormControl, và FormArray đều là những phần cơ bản của form trong Angular, và chúng đều được kế thừa từ lớp cơ bản là AbstractControl.
- FormControl: Đây là một đơn vị nhỏ nhất trong form, được sử dụng để theo dõi thông tin về giá trị và validation của một control trong form. Điển hình là nó có thể đại diện cho một ô nhập liệu, một ô checkbox, và các loại control khác.
- FormGroup: Là một tập hợp các control, group, hoặc array khác (AbstractControl). Nó hoạt động như một đối tượng, có thể chứa các giá trị đơn lẻ hoặc các đối tượng khác. Thường thì mỗi form bắt đầu với một FormGroup, và nó đăng ký các AbstractControl khác bên trong nó.
- FormArray: Là một cấu trúc dạng mảng, được sử dụng để quản lý các AbstractControl theo dạng mảng. Thường được sử dụng khi bạn có một cấu trúc form mà bạn muốn có khả năng linh hoạt thêm, bớt các phần tử.
Ví dụ, với form Sign In, chúng ta có thể khởi tạo nó như sau:
export class SignInRfComponent implements OnInit {
signInForm = new FormGroup({
username: new FormControl(""), // Giá trị mặc định
password: new FormControl(""), // Giá trị mặc định
rememberMe: new FormControl(false), // Giá trị mặc định
});
constructor() {}
ngOnInit(): void {}
}
Trong đoạn mã trên, signInForm
là một instance của FormGroup
, chứa ba FormControl
là username
, password
, và rememberMe
. Điều này giúp quản lý và theo dõi trạng thái của form một cách hiệu quả.
Binding Form
Để kết nối giữa mô hình form và template, chúng ta sử dụng directive [formGroup] như sau:
<form class="sign-in-form" [formGroup]="signInForm"></form>
Để liên kết các FormControl với các điều khiển form như textbox, checkbox, chúng ta sử dụng directive formControlName. Đối số của nó là khóa mà chúng ta sử dụng để đăng ký control trong FormGroup gần nhất:
<form class="sign-in-form" [formGroup]="signInForm">
<h2>Sign in</h2>
<div class="row-control">
<mat-form-field appearance="outline">
<mat-label>Username</mat-label>
<input matInput placeholder="Username" formControlName="username" />
</mat-form-field>
</div>
<div class="row-control">
<mat-form-field appearance="outline">
<mat-label>Password</mat-label>
<input
type="password"
matInput
placeholder="Password"
formControlName="password"
/>
</mat-form-field>
</div>
<div class="row-control">
<mat-checkbox formControlName="rememberMe">Remember me</mat-checkbox>
</div>
<div class="row-control row-actions">
<button mat-raised-button color="primary" type="submit">Sign in</button>
</div>
<pre>{{ signInForm.value | json }}</pre>
</form>
Với vài bước đơn giản như vậy, bạn đã có một form hoạt động như mong muốn.
FormBuilder Service
Để tạo form một cách thuận tiện khi có nhiều control, Angular Reactive Forms cung cấp một dịch vụ là FormBuilder. Sử dụng FormBuilder giúp việc khởi tạo form trở nên đơn giản hơn.
Để sử dụng dịch vụ này, bạn chỉ cần tiến hành inject nó vào constructor như sau:
export class SignInRfComponent implements OnInit {
signInForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.signInForm = this.fb.group({
username: "",
password: "",
rememberMe: false,
});
}
}
Qua đó, bạn có thể thấy việc tạo form trở nên dễ dàng hơn và giảm bớt sự phức tạp khi có nhiều control.
Cập nhật giá trị cho Reactive Forms qua patchValue hoặc setValue
Để cập nhật giá trị cho form control trong Angular Reactive Forms, chúng ta có hai phương thức là setValue
và patchValue
. Cả hai đều là các phương thức trừu tượng, và từ đó, các lớp con cần triển khai chúng theo cách riêng của mình.
Trong trường hợp của FormControl
, không có sự khác biệt đáng kể giữa setValue
và patchValue
. Thực tế, patchValue
đơn giản chỉ là gọi lại setValue
.
Đối với các lớp FormGroup
và FormArray
, patchValue
sẽ cập nhật các giá trị chỉ định trong đối tượng giá trị được truyền vào. Ngược lại, setValue
sẽ thông báo lỗi nếu một control nào đó bị thiếu hoặc thừa. Điều này có nghĩa là bạn cần truyền một đối tượng có cấu trúc chính xác giống như cấu trúc của form hiện tại.
Do đó, nếu bạn muốn chỉ cập nhật một phần của form, hãy sử dụng patchValue
. Ngược lại, nếu bạn muốn đặt lại tất cả và đảm bảo không có thông tin nào bị thiếu, hãy sử dụng setValue
để tận dụng khả năng báo lỗi.
Ngoài ra, Angular Reactive Forms còn cung cấp phương thức reset
để đặt lại form về trạng thái ban đầu, giống như lúc khởi tạo.
Dưới đây là một ví dụ minh họa sử dụng patchValue
trong một tình huống giả định:
ngOnInit(): void {
this.signInForm = this.fb.group({
username: '',
password: '',
rememberMe: false,
});
setTimeout(() => {
// Giả sử có một cuộc gọi API giả mạo sau đó cập nhật giá trị form
this.signInForm.patchValue({
username: 'Someone'
});
}, 1000);
}
Event Submit vs NgSubmit
Angular Reactive Forms cung cấp sự kiện ngSubmit
, tương tự như trong Template-driven Forms. Để sử dụng, bạn chỉ cần lắng nghe sự kiện này và xử lý nó theo ý muốn của mình.
Dưới đây là một ví dụ sử dụng sự kiện ngSubmit
trong một form Reactive:
<form
class="sign-in-form"
[formGroup]="signInForm"
autocomplete="off"
(ngSubmit)="onSubmit()"
></form>
Ở đây, chúng ta đã đặt thuộc tính formGroup
để liên kết form với Reactive Form của chúng ta và sử dụng (ngSubmit)
để lắng nghe sự kiện submit.
Còn đối với component, chúng ta triển khai hàm onSubmit
như sau:
onSubmit(): void {
console.log(this.signInForm);
}
Khi form được submit, hàm onSubmit
này sẽ được gọi và chúng ta có thể xử lý dữ liệu form theo cách mong muốn.