Angular Reactive Forms patterns with strict typing for FormControl bindings in templates. Trigger: When creating Angular forms, fixing FormControl type errors, or binding form controls in templates.
AbstractControl | null template binding errors.[formControl] and formControlName.app-input and app-toggle.Before using shared controls, check their READMEs under apps/frontend/src/app/shared/components/{input,toggle}/ and verify current inputs in the component source.
apps/frontend/src/app/private/modules/store/settings/general/components/general-settings-form/FormGroup examples: apps/frontend/src/app/private/modules/super-admin/subscriptions/pages/gateway/ and plans/plan-form.component.tsformControlName example: apps/frontend/src/app/private/modules/store/products/pages/product-create-page/apps/frontend/src/app/shared/components/input/input.component.ts and toggle/toggle.component.ts| Template Pattern | Use When | Rule |
|---|---|---|
formControlName="name" inside [formGroup] |
Standard form layout with static control names | Preferred for straightforward forms |
[formControl]="nameControl" |
Passing a specific control to a component or dynamic binding | Use a typed getter or typed property |
[control]="..." |
Component-specific extra input, not Angular Forms binding | Avoid form.get() directly unless the component explicitly needs AbstractControl |
Do not use $any(form.get(...)) in new code. It exists in legacy pages only.
Use typed getters when binding to [formControl].
readonly form = new FormGroup({
name: new FormControl('', { nonNullable: true }),
enabled: new FormControl(false, { nonNullable: true }),
logoUrl: new FormControl<string | null>(null),
});
get nameControl(): FormControl<string> {
return this.form.get('name') as FormControl<string>;
}
get enabledControl(): FormControl<boolean> {
return this.form.get('enabled') as FormControl<boolean>;
}
get logoUrlControl(): FormControl<string | null> {
return this.form.get('logoUrl') as FormControl<string | null>;
}
<app-input [formControl]="nameControl" label="Name" />
<app-toggle [formControl]="enabledControl" label="Enabled" />
Use formControlName when the control is inside the current [formGroup] and no explicit control reference is needed.
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<app-input formControlName="name" label="Name" />
<app-toggle formControlName="is_active" label="Active" />
</form>
This avoids repetitive getters for large static forms.
Prefer typed forms for new complex forms.
interface GatewayFormControls {
public_key: FormControl<string>;
private_key: FormControl<string>;
enabled: FormControl<boolean>;
}
readonly form = new FormGroup<GatewayFormControls>({
public_key: new FormControl('', { nonNullable: true }),
private_key: new FormControl('', { nonNullable: true }),
enabled: new FormControl(false, { nonNullable: true }),
});
InputComponent is a CVA and supports text-like types including text, email, password, number, tel, url, search, date, time, datetime-local, and color.
Important InputComponent features:
currency, currencyDecimals, and allowNegative for money inputs.prefixIcon, suffixIcon, suffixClickable, and suffixClick.tooltipText, tooltipPosition, and tooltipVisible.ToggleComponent is a CVA with checked, disabled, label, ariaLabel, and styleVariant. It emits both toggled and changed, and stores form-written state in signals.
In any custom CVA, every field written by writeValue or setDisabledState and read by the template must be a signal().
readonly value = signal(false);
readonly disabledFromForm = signal(false);
writeValue(value: boolean): void {
this.value.set(Boolean(value));
}
setDisabledState(disabled: boolean): void {
this.disabledFromForm.set(disabled);
}
Plain mutable fields in CVA callbacks can leave the template stale in Zoneless mode.
[formControl]="form.get('name')" in templates.[formControl]="form.get('name')!"; use a typed getter.$any(...) in new form templates.any.value, disabled, checked, or selected state.vendix-zoneless-signals - Signals, CVA, and change detection rulesvendix-currency-formatting - Money input/display patternsvendix-date-timezone - Date input and date-only handlingvendix-frontend-sticky-header - Form page headers and save/cancel actions