Advanced
Murakkab forma vazifalari: custom UI component'larni formaga ulash, typed forms, dinamik forma generatsiya va performance optimizatsiya.
ControlValueAccessor — custom form control
Nima bu?
ControlValueAccessor (CVA) — custom component'ni Angular form tizimiga ulash interfeysi. Rating yulduzcha, color picker, rich text editor kabi component'lar [formControl] yoki formControlName bilan ishlashi uchun CVA implement qilinadi.
Metodlar: writeValue, registerOnChange, registerOnTouched, setDisabledState.
Kod misoli
import {
Component, forwardRef, Input, signal,
} from '@angular/core';
import {
ControlValueAccessor, NG_VALUE_ACCESSOR,
} from '@angular/forms';
@Component({
selector: 'app-star-rating',
standalone: true,
template: `
@for (star of [1,2,3,4,5]; track star) {
<button type="button" (click)="select(star)">
{{ star <= value() ? '★' : '☆' }}
</button>
}
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => StarRatingComponent),
multi: true,
},
],
})
export class StarRatingComponent implements ControlValueAccessor {
value = signal(0);
disabled = signal(false);
private onChange: (v: number) => void = () => {};
private onTouched: () => void = () => {};
writeValue(val: number): void {
this.value.set(val ?? 0);
}
registerOnChange(fn: (v: number) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled.set(isDisabled);
}
select(star: number): void {
if (this.disabled()) return;
this.value.set(star);
this.onChange(star);
this.onTouched();
}
}
// Ishlatish
@Component({
template: `
<form [formGroup]="form">
<app-star-rating formControlName="rating" />
<p>Reyting: {{ form.value.rating }}</p>
</form>
`,
})
export class ReviewComponent {
form = new FormGroup({
rating: new FormControl(0, Validators.min(1)),
});
}
Ikkita nozik joy tez-tez interview'da so'raladi. Birinchisi — forwardRef(() => StarRatingComponent): provider massivi klass tanasi (va shu bilan klass nomi) to'liq e'lon qilinmasdan oldin baholanadi, shuning uchun to'g'ridan-to'g'ri StarRatingComponentga havola qilib bo'lmaydi — forwardRef bu havolani lazy qiladi. Ikkinchisi — writeValue tashqaridan (form.setValue(), patchValue(), dastlabki forma yuklanishi) chaqiriladi, ya'ni bu component'ning o'z event handler'i emas. Agar component ChangeDetectionStrategy.OnPush bo'lsa va value signal emas, oddiy field bo'lsa, writeValue ichida view yangilanmasligi mumkin — shuning uchun signal ishlatish yoki writeValue ichida ChangeDetectorRef.markForCheck() chaqirish kerak.
Imtihonda
Savol: CVA'siz custom component formControlName bilan nima qiladi?
Javob: Ishlamaydi — Angular component'ni form control sifatida tan olmaydi. CVA yoki NgModel @Directive kerak.
Savol: forwardRef nega kerak va OnPush bilan writeValue qanday muammo keltirib chiqarishi mumkin?
Javob: forwardRef klass hali e'lon qilinmagan joyda (provider array'ida, klass tanasidan oldin) unga havola berish uchun kerak. OnPushda writeValue component ichidan emas, tashqaridan (setValue/patchValue) chaqirilgani uchun signal ishlatilmasa view yangilanmay qolishi mumkin — signal yoki markForCheck() bilan hal qilinadi.
Yodlash uchun
Typed Reactive Forms — FormGroup<T> (v14+)
Nima bu?
Angular 14+ typed reactive forms — FormGroup, FormControl, FormArray generic tip bilan. form.controls.email — to'g'ridan-to'g'ri FormControlstring``, cast kerak emas. FormBuilder.nonNullable null xavfsizligi beradi.
Kod misoli
interface LoginForm {
username: FormControl<string>;
password: FormControl<string>;
rememberMe: FormControl<boolean>;
}
@Component({
selector: 'app-typed-login',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form" (ngSubmit)="login()">
<input formControlName="username" />
<input formControlName="password" type="password" />
<label>
<input formControlName="rememberMe" type="checkbox" /> Eslab qol
</label>
</form>
`,
})
export class TypedLoginComponent {
private fb = inject(FormBuilder);
form = this.fb.nonNullable.group<LoginForm>({
username: this.fb.nonNullable.control('', Validators.required),
password: this.fb.nonNullable.control('', Validators.required),
rememberMe: this.fb.nonNullable.control(false),
});
login(): void {
// Tip xavfsiz — string, string, boolean
const { username, password, rememberMe } = this.form.getRawValue();
console.log(username.toUpperCase(), rememberMe);
}
patchPartial(): void {
this.form.patchValue({ username: 'admin' }); // faqat username
}
}
Imtihonda
Savol: nonNullable nima beradi?
Javob: Control qiymati hech qachon null bo'lmaydi — string | null o'rniga string. reset() default qiymatga qaytadi (null emas).
Yodlash uchun
Dynamic form generation
Nima bu?
Forma struktura JSON/config dan generatsiya qilinadi — admin panel, survey builder, CMS. Har bir field uchun tip, label, validator config beriladi va runtime'da FormGroup + template yaratiladi.
Kod misoli
interface FieldConfig {
key: string;
label: string;
type: 'text' | 'number' | 'select';
required?: boolean;
options?: { value: string; label: string }[];
}
const SURVEY_CONFIG: FieldConfig[] = [
{ key: 'fullName', label: 'Ism', type: 'text', required: true },
{ key: 'age', label: 'Yosh', type: 'number' },
{
key: 'city', label: 'Shahar', type: 'select',
options: [
{ value: 'tashkent', label: 'Toshkent' },
{ value: 'samarkand', label: 'Samarqand' },
],
},
];
@Component({
selector: 'app-dynamic-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form" (ngSubmit)="submit()">
@for (field of config; track field.key) {
<label>{{ field.label }}</label>
@switch (field.type) {
@case ('text') {
<input [formControlName]="field.key" />
}
@case ('number') {
<input [formControlName]="field.key" type="number" />
}
@case ('select') {
<select [formControlName]="field.key">
@for (opt of field.options; track opt.value) {
<option [value]="opt.value">{{ opt.label }}</option>
}
</select>
}
}
}
<button type="submit">Yuborish</button>
</form>
`,
})
export class DynamicFormComponent {
@Input({ required: true }) config!: FieldConfig[];
form!: FormGroup;
ngOnInit(): void {
const group: Record<string, FormControl> = {};
for (const field of this.config) {
const validators = field.required ? [Validators.required] : [];
group[field.key] = new FormControl('', validators);
}
this.form = new FormGroup(group);
}
submit(): void {
if (this.form.valid) {
console.log(this.form.value);
}
}
}
Typed reactive forms bilan muammo: FormGroup<T> field'lari compile vaqtida qat'iy interfeys talab qiladi, lekin dynamic form'da field'lar runtime konfiguratsiyadan keladi — oldindan interfeys yozib bo'lmaydi. Shu holat uchun Angular 14+ FormRecord<FormControl<string>> turini taqdim etadi — u Record<string, FormControl>ga o'xshaydi va key'lar runtime'da qo'shilishi/olib tashlanishiga ruxsat beradi, FormGroupdan farqli o'laroq (FormGroup key'lari sxema bo'yicha qat'iy).
Imtihonda
Savol: Dynamic form'da validator qanday qo'shiladi?
Javob: Config'da validator qoidalari saqlanadi (required, min, pattern) va FormControl yaratishda Validators ga map qilinadi. Murakkab validator — config'da funksiya nomi yoki validator registry.
Savol: Dynamic (runtime key'li) formalarda typed forms bilan FormGroup ishlatish mumkinmi?
Javob: Noqulay — FormGroup<T> fixed interfeys talab qiladi. Buning o'rniga FormRecord ishlatiladi — dinamik, oldindan noma'lum key'lar uchun typed control to'plami.
Yodlash uchun
Form performance — updateOn, debounce
Nima bu?
Katta formalarda har keystroke validatsiya va change detection sekinlashtiradi. Optimizatsiya: updateOn: 'blur', valueChanges da debounceTime, OnPush strategy, async validator debounce, emitEvent: false keraksiz event'larni o'chirish.
Kod misoli
@Component({
selector: 'app-optimized-search',
standalone: true,
imports: [ReactiveFormsModule],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<input [formControl]="search" />
<p>Natijalar: {{ results().length }}</p>
`,
})
export class OptimizedSearchComponent implements OnInit {
private api = inject(SearchApiService);
private destroyRef = inject(DestroyRef);
search = new FormControl('', { updateOn: 'blur' });
results = signal<SearchResult[]>([]);
ngOnInit(): void {
this.search.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
filter((q): q is string => !!q && q.length >= 2),
switchMap((q) => this.api.search(q)),
takeUntilDestroyed(this.destroyRef)
).subscribe((data) => this.results.set(data));
}
// Ko'p field patch — bitta CD cycle
bulkUpdate(values: Partial<UserForm>): void {
this.form.patchValue(values, { emitEvent: false });
this.form.updateValueAndValidity({ emitEvent: true }); // bir marta
}
form = new FormGroup({ /* ... */ });
}
Imtihonda
Savol: emitEvent: false qachon ishlatiladi?
Javob: Ko'p field bir vaqtda yangilanganda har biri uchun valueChanges trigger bo'lmasin. Oxirida bitta updateValueAndValidity({ emitEvent: true }) yetarli.
Yodlash uchun
