| name | build-form |
| description | Build a reactive form with @fundamental-ngx/platform form components, FormGroup wiring, validation, and error states |
| argument-hint | <form-name> [field:type ...] |
| context | fork |
| agent | general-purpose |
| allowed-tools | Read, Grep, Glob, Bash(nx *), Bash(ng build*), Write, Edit |
Build Form: $ARGUMENTS
If $ARGUMENTS is empty, ask the user: (1) what the form is for, (2) which fields it needs, (3) whether validation is required.
Phase 1: Determine Scope
Parse from $ARGUMENTS or ask:
- Form name (PascalCase, e.g.,
UserProfile, ShippingAddress)
- Fields: name, type (
text | number | select | date | checkbox | textarea), required (default) or optional (suffix with ?, e.g., birthDate:date?)
- Layout:
1 column | 2 columns (default) | 3 columns
- Submit target: inline handler, emitted event to parent, or none
Phase 2: Gather Component Context
Call the @fundamental-ngx/mcp MCP server:
get_usage_guide('form') — composition order, fdp-form-group vs fd-form-item decision tree, pitfalls
get_component_api('fdp-form-group') — layout inputs, (onSubmit) output
get_component_api('fdp-form-field') — [id], [label], [required], [columns], hint/error slots
- For each non-text field:
get_component_api for its control (e.g., fdp-select, fdp-date-picker, fdp-checkbox)
If MCP is unavailable, read libs/mcp-server/src/data/usage-guides.ts.
Phase 3: Present Plan
Output this table before writing any code:
## Form Plan: [FormName]
**Layout:** 2 columns | **Validation trigger:** on submit + on touched
| Field | Control | Required | Validators |
|-------|---------|----------|------------|
| firstName | fdp-input | yes | minLength(2) |
| role | fdp-select | yes | required |
| birthDate | fdp-date-picker | no | — |
Stop here and wait for approval before generating code.
Phase 4: Generate Component
Create three files in the target path (ask the user if not already known).
Also generate the fields interface at the top of the .component.ts file:
export interface [Name]Fields {
firstName: string;
role: string;
}
TypeScript (.component.ts)
Import each platform form component directly — they are all standalone. There is no barrel PlatformFormModule to import.
import { ChangeDetectionStrategy, Component, inject, output } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { ButtonModule } from '@fundamental-ngx/core/button';
import {
FormFieldComponent,
FormFieldErrorDirective,
FormGroupComponent,
InputComponent
} from '@fundamental-ngx/platform/form';
@Component({
selector: 'app-[kebab-name]-form',
templateUrl: './[kebab-name]-form.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ReactiveFormsModule,
FormGroupComponent,
FormFieldComponent,
FormFieldErrorDirective,
InputComponent,
ButtonModule
]
})
export [] {
submitted = output<[]>();
_fb = ();
: = ..({
});
(): {
(..) {
..(..() []);
}
}
}
Standalone component imports reference:
| Template element | Import from @fundamental-ngx/platform/form |
|---|
fdp-form-group | FormGroupComponent |
fdp-form-field | FormFieldComponent |
fdp-input | InputComponent |
fdp-select | SelectComponent |
fdp-textarea | TextAreaComponent |
fdp-date-picker | PlatformDatePickerComponent |
fdp-checkbox | CheckboxComponent |
fdpFormFieldError (template directive) | FormFieldErrorDirective |
HTML Template (.component.html)
Wrap everything in a native <form> — FormGroupComponent does NOT render a <form> tag by default (useForm defaults to false) and has no footer slot. Submit/reset buttons must be placed outside <fdp-form-group> but inside the <form>.
Use columnLayout="XL2-L2-M2-S1" for a 2-column layout (not [layout]).
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<fdp-form-group [formGroup]="form" columnLayout="XL2-L2-M2-S1">
<ng-template fdpFormFieldError="required">This field is required</ng-template>
<ng-template fdpFormFieldError="minlength" let-error
>Minimum {{ error.requiredLength }} characters required</ng-template
>
<ng-template fdpFormFieldError="email">Please enter a valid email address</ng-template>
<fdp-form-field id="firstName" label="First Name" [required]="true" [column]="1">
<fdp-input name="firstName" [formControlName]="'firstName'">
Submit
Reset
fdp-select usage — use [list] with OptionItem[] from @fundamental-ngx/platform/shared. Each item's .value is what gets written to the FormControl:
<fdp-form-field id="role" label="Role" [required]="true" [column]="2">
<fdp-select name="role" [formControlName]="'role'" [list]="roleOptions"> </fdp-select>
</fdp-form-field>
import { OptionItem } from '@fundamental-ngx/platform/shared';
readonly roleOptions: OptionItem[] = [
{ label: 'Developer', value: 'developer' },
{ label: 'Designer', value: 'designer' },
];
SCSS (.component.scss)
Leave empty — form spacing and layout are handled by fundamental-styles. Only add custom rules if product requirements demand it.
Critical Rules
fdpFormFieldError templates are mandatory when any field has validators — if [required]="true" is set on any fdp-form-field, or its FormControl carries validators, the component throws at runtime: "Validation strings are required for the any provided validations." Fix: add <ng-template fdpFormFieldError="required">...</ng-template> (and one per additional error key) as direct children of fdp-form-group. They are shared automatically by all fields. Import FormFieldErrorDirective from @fundamental-ngx/platform/form.
- One
<ng-template fdpFormFieldError> per Angular error key — add templates for every key your validators can produce: required, minlength, maxlength, email, pattern, and any custom keys. The template context (let-error) exposes the error value (e.g. error.requiredLength for minlength).
- Import standalone components directly — there is no
PlatformFormModule. Import each component individually: FormGroupComponent, FormFieldComponent, FormFieldErrorDirective, InputComponent, SelectComponent, TextAreaComponent, etc.
fdp-form-field not fdp-form-item — fdp-form-item is the core fd- API; platform forms use fdp-form-field
- Wrap in
<form (ngSubmit)>, NOT (fdSubmit) on fdp-form-group — fdp-form-group does not emit fdSubmit. Use a standard <form [formGroup]="form" (ngSubmit)="onSubmit()"> wrapper. The component's own (onSubmit) output fires only when [useForm]="true" is set.
columnLayout (string), NOT [layout] (object) — FormGroupComponent accepts columnLayout="XL2-L2-M2-S1", not . The format is .
Phase 5: Validate
nx run <project>:build
Fix any TypeScript type errors or missing import errors before reporting done.
Output
## Build Form: [FormName]
**Files generated:**
- src/app/.../[kebab-name]-form.component.ts
- src/app/.../[kebab-name]-form.component.html
- src/app/.../[kebab-name]-form.component.scss
**Imports required in parent:**
- `import { [Name]FormComponent } from './[kebab-name]-form/[kebab-name]-form.component'`
**Next steps:**
- [ ] Add [Name]FormComponent to the parent's imports array
- [ ] Handle the (submitted) output event in the parent
- [ ] Customize per-field error messages with fdp-form-message if default messages are insufficient
- [ ] For multi-step forms, wrap multiple fdp-form-group instances in a wizard container