Async Validator trong Angular Form
- 15-01-2024
- Toanngo92
- 0 Comments
Mục lục
Điều kiện bắt buộc
Hôm nay, chúng ta sẽ xây dựng một form đăng ký người dùng – registerForm, bao gồm:
- Username textbox:
- Không được bỏ trống.
- Phải có độ dài từ 6 đến 32 ký tự.
- Chỉ chứa các ký tự chữ cái.
- Password textbox:
- Không được bỏ trống.
- Phải có độ dài từ 6 đến 32 ký tự.
- Chứa các ký tự chữ cái, số, và ít nhất một ký tự đặc biệt trong danh sách: !@#$%^&*.
- Retype password:
- Giống như password ở trên.
- Để đảm bảo chắc chắn người dùng nhập đúng password, giá trị của textbox này phải giống hệt giá trị textbox password.
Đầu tiên, chúng ta sẽ thiết lập form như sau:
const PASSWORD_PATTERN = /^(?=.*[!@#$%^&*]+)[a-z0-9!@#$%^&*]{6,32}$/;
this.registerForm = this._fb.group({
username: [
"",
Validators.compose([
Validators.required,
Validators.minLength(6),
Validators.pattern(/^[a-z]{6,32}$/i),
]),
],
password: [
"",
Validators.compose([
Validators.required,
Validators.minLength(6),
Validators.pattern(PASSWORD_PATTERN),
]),
],
confirmPassword: [
"",
Validators.compose([
Validators.required,
Validators.minLength(6),
Validators.pattern(PASSWORD_PATTERN),
]),
],
});
Và phần HTML của form:
<div class="container">
<form
class="register-form"
[formGroup]="registerForm"
autocomplete="off"
(ngSubmit)="submitForm()"
>
<h2>Register</h2>
<div class="row-control">
<mat-form-field appearance="outline">
<mat-label>Username</mat-label>
<input matInput placeholder="Username" formControlName="username" />
</mat-form-field>
<pre>{{ registerForm.get("username")?.errors | json }}</pre>
</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>
<pre>{{ registerForm.get("password")?.errors | json }}</pre>
</div>
<div class="row-control">
<mat-form-field appearance="outline">
<mat-label>Confirm Password</mat-label>
<input
type="password"
matInput
placeholder="Confirm Password"
formControlName="confirmPassword"
/>
</mat-form-field>
<pre>{{ registerForm.get("confirmPassword")?.errors | json }}</pre>
</div>
<div class="row-control row-actions">
<button
mat-raised-button
color="primary"
type="submit"
[disabled]="registerForm.invalid"
>
Register
</button>
</div>
<pre>{{ registerForm.value | json }}</pre>
</form>
</div>
Trong đoạn mã này, chúng ta sử dụng Angular Reactive Forms để tạo form đăng ký người dùng với các yêu cầu nêu trên. Việc kiểm soát validations và hiển thị trạng thái của form giúp người dùng dễ dàng nhập thông tin đúng đắn.
Custom Validators
Dựa trên yêu cầu, chúng ta cần tạo ra hai custom validator:
- Async validator để kiểm tra username đã tồn tại trong hệ thống:
- Để kiểm tra này, chúng ta sẽ sử dụng một hàm mock để check xem username đã tồn tại hay chưa và trả về một Promise hoặc Observable.
- Mỗi lần hàm validateUsername được gọi, một dòng console sẽ được in ra để dễ theo dõi.
validateUsername(username: string): Observable<boolean> {
console.log("Trigger API call");
let existedUsers = ["trungvo", "tieppt", "chautran"];
let isValid = existedUsers.every(x => x !== username);
return of(isValid).pipe(delay(1000));
}
Tiếp theo, chúng ta sẽ viết một async validator để kiểm tra tính hợp lệ của username.
const validateUserNameFromApi = (api: ApiService) => {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
return api.validateUsername(control.value).pipe(
map((isValid: boolean) => {
return isValid ? null : { usernameDuplicated: true };
})
);
};
};
Sau đó, cần thiết lập control trong form để sử dụng async validator:
this.registerForm = this._fb.group({
username: [
"",
Validators.compose([
Validators.required,
Validators.minLength(6),
Validators.pattern(/^[a-z]{6,32}$/i),
]),
validateUserNameFromApi(this._api),
],
});
2. Sync validator để kiểm tra xem password nhập lại có khớp với password đầu tiên hay không:
const passwordMatchValidator = (control: AbstractControl): ValidationErrors | null => {
const password = control.get("password")?.value;
const confirmPassword = control.get("confirmPassword")?.value;
return password === confirmPassword ? null : { passwordMismatch: true };
};
Sau đó, cần thiết lập control trong form để sử dụng sync validator:
this.registerForm = this._fb.group({
// ... (các control khác)
confirmPassword: [
"",
Validators.compose([
Validators.required,
Validators.minLength(6),
Validators.pattern(PASSWORD_PATTERN),
]),
],
}, { validators: passwordMatchValidator });
Như vậy, chúng ta đã tạo thành công các custom validators để đảm bảo tính hợp lệ của thông tin người dùng trong form đăng ký.
Bonus: Validate confirm password
Trong trường hợp cần kiểm tra xem confirm password có khớp với password hay không, chúng ta có thể viết một hàm validator tùy chỉnh đơn giản hơn. Do hàm này cần giá trị của 2 controls, nên chúng ta sẽ áp dụng validator này cho FormGroup thay vì từng FormControl. Mã của hàm validateMatchedControlsValue
sẽ như sau:
const validateMatchedControlsValue = (
firstControlName: string,
secondControlName: string
) => {
return function (formGroup: FormGroup): ValidationErrors | null {
const { value: firstControlValue } = formGroup.get(
firstControlName
) as AbstractControl;
const { value: secondControlValue } = formGroup.get(
secondControlName
) as AbstractControl;
return firstControlValue === secondControlValue
? null
: {
valueNotMatch: {
firstControlValue,
secondControlValue,
},
};
};
};
Chúng ta tạo hàm validateMatchedControlsValue
và truyền vào tên của 2 controls. Hàm này sẽ trả về một hàm thực hiện việc kiểm tra.
Hàm kiểm tra sẽ nhận một FormGroup và lấy giá trị từ hai control. Nếu giá trị của hai control này giống nhau, hàm sẽ trả về null, tức là không có lỗi. Ngược lại, nó sẽ trả về một đối tượng thông báo lỗi để chúng ta có thể hiển thị trên giao diện người dùng. Sau đó, chúng ta áp dụng validator này vào FormGroup:
this.registerForm = this._fb.group(
{
password: [
"",
Validators.compose([
Validators.required,
Validators.minLength(6),
Validators.pattern(PASSWORD_PATTERN)
])
],
confirmPassword: [
"",
Validators.compose([
Validators.required,
Validators.minLength(6),
Validators.pattern(PASSWORD_PATTERN)
])
]
},
{
validators: validateMatchedControlsValue("password", "confirmPassword")
}
);
Kết quả là nếu hai password không giống nhau, form sẽ có lỗi. Cách hiển thị thông báo trên giao diện tùy thuộc vào sự sáng tạo của bạn! 😍