Validators
Angular built-in va custom validatorlar orqali forma ma'lumotlarini tekshiradi. Sync — darhol, async — server tekshiruvi (username bandligi).
Sync custom validator — ValidatorFn
Nima bu?
ValidatorFn — (control: AbstractControl) => ValidationErrors | null signature'li funksiya. null — valid, ob'ekt — xato ({ myError: true }). Bir nechta validator array sifatida beriladi.
Kod misoli
import { AbstractControl, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
// O'zbekiston telefon raqami
export function uzbekPhoneValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value as string;
if (!value) return null; // required alohida
const valid = /^\+998[0-9]{9}$/.test(value);
return valid ? null : { uzbekPhone: { value } };
};
}
// Parol kuchliligi
export function passwordStrengthValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const v = control.value as string;
if (!v) return null;
const hasUpper = /[A-Z]/.test(v);
const hasDigit = /[0-9]/.test(v);
const hasSpecial = /[!@#$%^&*]/.test(v);
if (hasUpper && hasDigit && hasSpecial) return null;
return { passwordStrength: { hasUpper, hasDigit, hasSpecial } };
};
}
@Component({
selector: 'app-signup-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form">
<input formControlName="phone" />
@if (form.controls.phone.errors?.['uzbekPhone']) {
<span>Format: +998XXXXXXXXX</span>
}
<input formControlName="password" type="password" />
</form>
`,
})
export class SignupFormComponent {
form = new FormGroup({
phone: new FormControl('', [Validators.required, uzbekPhoneValidator()]),
password: new FormControl('', [Validators.required, passwordStrengthValidator()]),
});
}
Imtihonda
Savol: Custom validator required o'rniga bo'sh qiymatni invalid qilsa?
Javob: Noto'g'ri pattern. Bo'sh qiymat uchun return null — Validators.required alohida ishlasin. Aks holda required va custom validator ziddiyat qiladi.
Yodlash uchun
Async custom validator — AsyncValidatorFn
Nima bu?
AsyncValidatorFn — (control) => ObservableValidationErrors | null | Promise...``. Server tekshiruvi: username bandligi, email mavjudligi. Control pending: true holatiga o'tadi. Sync validatorlardan alohida asyncValidators array'ida beriladi.
Kod misoli
import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms';
import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map, catchError, of, debounceTime, switchMap, first } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class UsernameCheckService {
private http = inject(HttpClient);
isAvailable(username: string): Observable<boolean> {
return this.http.get<{ available: boolean }>(`/api/check-username/${username}`).pipe(
map((res) => res.available),
catchError(() => of(false))
);
}
}
export function uniqueUsernameValidator(): AsyncValidatorFn {
const service = inject(UsernameCheckService);
return (control: AbstractControl): Observable<ValidationErrors | null> => {
if (!control.value) return of(null);
return of(control.value).pipe(
debounceTime(400),
switchMap((username) => service.isAvailable(username)),
map((available) => (available ? null : { usernameTaken: true })),
first()
);
};
}
@Component({
selector: 'app-username-field',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<input [formControl]="username" />
@if (username.pending) { <span>Tekshirilmoqda...</span> }
@if (username.errors?.['usernameTaken']) { <span>Bu login band</span> }
`,
})
export class UsernameFieldComponent {
username = new FormControl('', {
validators: Validators.required,
asyncValidators: uniqueUsernameValidator(),
updateOn: 'blur',
});
}
Imtihonda
Savol: Async validator nega first() bilan tugatiladi?
Javob: Observable complete bo'lishi kerak — aks holda control doim pending holatda qoladi. first() birinchi qiymatdan keyin complete qiladi.
Savol: Sync va async validatorlar bir vaqtda ishga tushadimi?
Javob: Yo'q. Angular avval barcha sync validatorlarni tekshiradi; ular hech biri xato qaytarmasa, keyin async validatorlar ishga tushadi. Shuning uchun required xato bo'lsa, uniqueUsernameValidator HTTP so'rovi umuman jo'natilmaydi — bu tarmoq trafigini behuda sarflashdan saqlaydi, lekin submit paytida form.pending === true bo'lishi mumkinligini va tugmani shunga qarab disable qilish kerakligini unutmaslik kerak.
Yodlash uchun
Cross-field validator — FormGroup darajasida
Nima bu?
Ba'zi validatsiya bir nechta field'ga bog'liq: parol tasdiqlash, sana oralig'i, "kamida bittasi to'ldirilsin". Bunday validator FormGroup darajasida beriladi — control o'rniga butun group tekshiriladi.
Kod misoli
function passwordMatchValidator(): ValidatorFn {
return (group: AbstractControl): ValidationErrors | null => {
const password = group.get('password')?.value;
const confirm = group.get('confirmPassword')?.value;
if (password !== confirm) {
group.get('confirmPassword')?.setErrors({ passwordMismatch: true });
return { passwordMismatch: true };
}
// confirmPassword'dagi eski xatoni tozalash
const confirmCtrl = group.get('confirmPassword');
if (confirmCtrl?.hasError('passwordMismatch')) {
confirmCtrl.setErrors(null);
}
return null;
};
}
function dateRangeValidator(): ValidatorFn {
return (group: AbstractControl): ValidationErrors | null => {
const start = group.get('startDate')?.value;
const end = group.get('endDate')?.value;
if (start && end && new Date(start) > new Date(end)) {
return { dateRange: { start, end } };
}
return null;
};
}
@Component({
selector: 'app-password-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form">
<input formControlName="password" type="password" />
<input formControlName="confirmPassword" type="password" />
@if (form.errors?.['passwordMismatch']) {
<span>Parollar mos kelmadi</span>
}
</form>
`,
})
export class PasswordFormComponent {
form = new FormGroup(
{
password: new FormControl('', Validators.required),
confirmPassword: new FormControl('', Validators.required),
},
{ validators: passwordMatchValidator() }
);
}
Imtihonda
Savol: Cross-field xato qayerda ko'rsatiladi — field yoki group?
Javob: form.errors — group darajasidagi xato. Field-specific xato uchun control.setErrors() ham ishlatiladi. UI: ikkala joyda ham ko'rsatish mumkin.
Yodlash uchun
Validator composition — Validators.compose
Nima bu?
Bir nechta sync validatorni bitta ValidatorFn ga birlashtirish. Validators.compose([v1, v2]) — birinchi xato topilganda to'xtaydi. Validators.composeAsync — async uchun. Array [v1, v2] bilan deyarli bir xil (Angular ichida compose ishlatadi).
Kod misoli
const emailOrPhoneValidator = Validators.compose([
Validators.required,
(control: AbstractControl): ValidationErrors | null => {
const v = control.value as string;
const isEmail = Validators.email({ value: v } as AbstractControl) === null;
const isPhone = /^\+998[0-9]{9}$/.test(v);
return isEmail || isPhone ? null : { emailOrPhone: true };
},
]);
@Component({
selector: 'app-contact-field',
standalone: true,
imports: [ReactiveFormsModule],
template: `<input [formControl]="contact" />`,
})
export class ContactFieldComponent {
contact = new FormControl('', {
validators: emailOrPhoneValidator,
nonNullable: true,
});
// Qayta ishlatiladigan validator to'plami
static readonly userValidators = Validators.compose([
Validators.required,
Validators.minLength(3),
Validators.maxLength(20),
Validators.pattern(/^[a-zA-Z0-9_]+$/),
]);
}
Imtihonda
Savol: [Validators.required, myValidator] va Validators.compose([...]) farqi?
Javob: Amaliy jihatdan bir xil — Angular array'ni ichida compose qiladi. compose qayta ishlatiladigan validator export qilishda qulay.
Yodlash uchun
