Template-driven Forms Trong Angular (Phần 1)
- 15-01-2024
- Toanngo92
- 0 Comments
Biểu mẫu, hay còn được gọi là form, đó là một phần quan trọng trong hệ thống và doanh nghiệp ngày nay. Hình dung như khi bạn muốn mở một tài khoản ngân hàng, hoặc đơn giản như khi bạn đăng ký vào một trường Đại học. Thậm chí, trong môi trường công việc, có nhiều biểu mẫu mà bạn cần điền khi bắt đầu công việc tại một công ty mới.
Đối với ứng dụng, việc sử dụng chức năng Forms là hết sức quan trọng để có thể thu thập thông tin cần thiết từ người dùng. Dưới đây là một ví dụ về biểu mẫu Đăng Nhập (Sign In):
Mục lục
Giới thiệu
Angular là một framework toàn diện, và nó cung cấp hai phương pháp chính để xử lý đầu vào từ người dùng thông qua các biểu mẫu: Template-driven Forms và Reactive Forms, còn được gọi là Model-driven Forms.
Template-driven Forms: Trong cơ chế này, chúng ta tận dụng các directive như NgForm, NgModel, required, vv. trên template để thực hiện các chức năng của form. Đặc điểm quan trọng của form này là sử dụng Two-way binding, giúp cập nhật mô hình dữ liệu giữa template và component một cách linh hoạt thông qua sự thay đổi.
<form (ngSubmit)="onSubmit()">
<label for="username">Username:</label>
<input type="text" id="username" name="username" [(ngModel)]="user.username" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" [(ngModel)]="user.password" required>
<button type="submit">Submit</button>
</form>
Reactive Forms: Ở phương pháp này, chúng ta xây dựng biểu mẫu từ các model, là các đối tượng có các chức năng đặc biệt để quản lý thông tin từ các input của form. Reactive Forms sử dụng các directive, mặc dù rất ít so với Template-driven Forms.
Reactive Forms mang lại cách tiếp cận dựa trên mô hình để xử lý đầu vào form có giá trị thay đổi theo thời gian.
import { FormGroup, FormControl } from '@angular/forms';
// ...
this.myForm = new FormGroup({
username: new FormControl(''),
password: new FormControl('')
});
Sự khác biệt quan trọng là Reactive Forms tập trung vào việc quản lý các giá trị của form theo thời gian, trong khi Template-driven Forms tận dụng sự linh hoạt của Two-way binding giữa template và component.
Khởi tạo dự án
Để thực hiện demo trong ngày hôm nay, chúng ta có thể sử dụng Angular và Angular Material để tạo ra một dự án động.
Để khởi tạo một dự án Angular mới, bạn có thể sử dụng lệnh sau:
ng new acme
Sau đó, bạn sẽ được yêu cầu chọn cấu hình cho routing, style. Ví dụ:
- Bạn có muốn thêm Angular routing không? Yes
- Bạn muốn sử dụng định dạng stylesheet nào? SCSS
Sau khi tạo thành công, bạn có thể thêm Angular CDK và Angular Material vào dự án bằng cách sử dụng các lệnh sau:
ng add @angular/cdk
ng add @angular/material
Trong quá trình thực hiện, bạn có thể được hỏi một số câu hỏi, hoặc bạn có thể chọn giá trị mặc định. Ví dụ:
- Chọn tên của một chủ đề được xây sẵn hoặc chọn “custom” cho một chủ đề tùy chỉnh: Indigo/Pink [ Xem trước: https://material.angular.io?theme=indigo-pink ]
- Thiết lập các kiểu chữ toàn cầu của Angular Material? Yes
- Thiết lập animations trình duyệt cho Angular Material? Yes
Sau bước này, bạn đã hoàn tất quá trình khởi tạo dự án. Bạn có thể chạy lệnh sau để bắt đầu chương trình:
ng serve
Form đăng nhập
Để bắt đầu phát triển trang đăng nhập, chúng ta sẽ tạo một component mới có tên là “sign-in” và thiết lập routing cho nó như sau:
ng g c sign-in
Sau đó, cấu hình routing trong file routing module:
const routes: Routes = [
{
path: "sign-in",
component: SignInComponent,
},
];
Bây giờ, bạn có thể truy cập trang đăng nhập bằng cách nhập địa chỉ http://localhost:4200/sign-in và bắt đầu viết mã.
Giả sử trang đăng nhập sẽ có một ô nhập cho Username, một ô nhập cho Password, và một ô checkbox để chọn “Remember me”. Cuối cùng, chúng ta có một nút để submit form.
Dưới đây là đoạn mã HTML template cho trang đăng nhập:
<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>
Lưu ý: Để sử dụng các thành phần/directive từ Angular Material, bạn cần import các module tương ứng vào NgModule quản lý component hiện tại. Ví dụ, nếu SignInComponent được quản lý bởi AppModule, bạn cần thêm các imports sau:
import { MatInputModule } from "@angular/material/input";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatCheckboxModule } from "@angular/material/checkbox";
import { MatButtonModule } from "@angular/material/button";
@NgModule({
declarations: [AppComponent, SignInComponent],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule,
MatCheckboxModule,
MatButtonModule,
],
})
export class AppModule {}
Khi render chúng ta sẽ có giao diện như sau:
Tích hợp Angular Forms
Để kích hoạt sử dụng các API của Angular dành cho việc làm việc với Template-driven Forms, chúng ta cần nhập NgModule là FormsModule từ package @angular/forms và thêm nó vào NgModule của ứng dụng. Bạn có thể thực hiện điều này như sau:
import { FormsModule } from "@angular/forms";
@NgModule({
declarations: [AppComponent, SignInComponent],
imports: [
// các NgModules khác
FormsModule,
],
})
export class AppModule {}
Như vậy, chúng ta đã tích hợp thành công FormsModule vào ứng dụng Angular của mình, giúp chúng ta sử dụng đầy đủ các tính năng và API liên quan đến Template-driven Forms.
ngForm và ngModel directives
Để lấy được NgForm instance, bạn chỉ cần tạo một biến template cho thẻ form và đặt thuộc tính exportAs là “ngForm” như sau:
<form novalidate #signInForm="ngForm" ...></form>
Từ đây, bạn có thể sử dụng biến signInForm trong template và truyền nó vào class component. Ví dụ, bạn có thể truyền instance này vào hàm lắng nghe sự kiện submit của form như sau:
export class SignInComponent implements OnInit {
constructor() {}
ngOnInit(): void {}
onSubmit(form: NgForm): void {
console.log(form);
}
}
<form
novalidate
#signInForm="ngForm"
(submit)="onSubmit(signInForm)"
...
></form>
Tuy nhiên, tại thời điểm hiện tại, mặc dù đã khai báo một số input/control, nhưng form không thể xác định được những control đó. Do đó, nếu bạn in giá trị của form, bạn sẽ nhận được một đối tượng rỗng:
<pre>
{{ signInForm.value | json }}
</pre>
Để giải quyết vấn đề này, bạn cần đăng ký các control vào form sử dụng directive ngModel. Ví dụ, để đăng ký một control input cho việc nhập Username, bạn thêm directive ngModel như sau:
<input matInput placeholder="Username" ngModel name="username" />
Tuy nhiên, để tránh lỗi runtime, bạn cần gắn thêm thuộc tính name cho thẻ input hoặc sử dụng directive ngModelOptions để đặt control là ‘standalone’. Dưới đây là một ví dụ:
<input matInput placeholder="Username" ngModel name="username" />
Thực hiện tương tự cho các control khác, bạn sẽ có một form hoàn chỉnh.
Event Submit vs NgSubmit
Trong phần trước, chúng ta đã lắng nghe sự kiện submit của form, tuy nhiên, ngoài sự kiện đó, còn có một sự kiện khác được kích hoạt khi form được submit, đó chính là ngSubmit. Vậy ngSubmit và submit có gì khác biệt?
Giống như sự kiện submit, sự kiện ngSubmit cũng được kích hoạt khi form được submit, ví dụ như khi người dùng nhấn nút submit. Tuy nhiên, ngSubmit thêm vào một số nhiệm vụ để đảm bảo rằng form không thực hiện submit theo cách thông thường, tức là không làm tải lại trang web sau khi submit.
Giả sử chúng ta thực hiện một số thao tác trong hàm lắng nghe sự kiện submit của form và có một lỗi xuất hiện. Trong trường hợp này, nếu chúng ta sử dụng sự kiện submit, trang web sẽ bị tải lại; ngược lại, nếu sử dụng ngSubmit, trang web sẽ không bị tải lại – điều này áp dụng trong phiên bản hiện tại mà tôi đang sử dụng.
Dưới đây là một đoạn mã minh họa:
onSubmit(form: NgForm) {
// Thực hiện một số thao tác tuyệt vời
console.log(form);
throw new Error('Có lỗi xảy ra');
}
Lời khuyên chung là nên sử dụng sự kiện ngSubmit để lắng nghe sự kiện submit của form, đặc biệt là khi bạn làm việc với Reactive Forms.
ngModel, ngModel and (ngModel)
Chúng ta đã biết rằng ngModel là một directive giúp chúng ta đăng ký control với form. Vậy hai biểu thức cú pháp khác [(ngModel)] và [ngModel] có tác dụng gì?
- [ngModel]: Đây là một cơ chế gọi là property binding, cho phép chúng ta gắn một giá trị hoặc một thuộc tính cho ngModel. Nói một cách đơn giản, giá trị từ model sẽ được binding vào control, nhưng nếu control thay đổi, giá trị trong model không thay đổi theo.
<input matInput placeholder="Username" [ngModel]="userInfo.userName" name="username" />
- [(ngModel)]: Đây là một cơ chế gọi là two-way data binding, kết hợp cả property binding và event binding để đồng bộ dữ liệu giữa template và model. Nếu giá trị trong control thay đổi, giá trị trong model cũng thay đổi tương ứng và ngược lại.
<input matInput placeholder="Username" [(ngModel)]="userInfo.userName" name="username" />
Ví dụ, nếu chúng ta có một đối tượng (model) chứa dữ liệu có sẵn để binding vào form, chẳng hạn form chỉnh sửa:
export class SignInComponent implements OnInit {
userInfo = {
userName: "tiepphan",
password: "",
rememberMe: true,
};
constructor() {}
ngOnInit(): void {}
onSubmit(form: NgForm): void {
console.log(form);
}
}
Chúng ta có thể sử dụng cú pháp [ngModel] để binding dữ liệu từ model vào control. Tuy nhiên, nếu muốn đồng bộ cả khi control thay đổi, chúng ta có thể chuyển sang cú pháp [(ngModel)].