skills/vendix-angular-forms/SKILL.md
Angular Reactive Forms patterns with strict typing, shared CVA components, and Zoneless-safe form state. Trigger: When creating Angular forms, fixing FormControl type errors, binding form controls in templates, or implementing ControlValueAccessor components.
npx skillsauth add rzyfront/vendix vendix-angular-formsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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 actionsdevelopment
Mobile app development rules for Vendix Expo/React Native project. Trigger: When editing, creating, or modifying any file under apps/mobile, or when developing mobile-specific features.
development
Feature gating by store subscription state: global store write guard, AI feature gate, Redis feature resolution, quota consumption, frontend paywall interceptor, banner, and subscription UI states. Trigger: When adding feature gates, paywalls, subscription-based access control, protecting store write operations, AI feature gates, or rollout flags.
testing
SaaS subscription billing for Vendix stores: plan pricing, invoices, Wompi platform payments, manual payments, partner commissions, payouts, proration, and dunning. Trigger: When creating SaaS invoices, working with partner rev-share, margin/surcharge pricing, invoice sequence allocation, partner payout batches, subscription payments, manual payments, or dunning flows.
development
Periodic quota counters with Redis, UTC period keys, Lua-based idempotent AI quota consumption, request-id deduplication, and post-success consumption. Trigger: When building quota counters, enforcing monthly/daily feature caps, or reusing AI quota patterns for uploads, emails, exports, or rate-limited features.