Coming from ngx-formly? The migration guide maps
defaultProps,extends, and the formlyextensionsAPI to their ng-forge equivalents.
Configure global defaults for all forms at provider level, or per-form via defaultProps.
The Cascade
ng-forge applies props in priority order: more specific always wins.
withXxxFields({...})All formsdefaultPropsOne formpropsOne fieldConfig Options
Pass these to withBootstrapFields({ ... }) for library-level defaults, or to defaultProps for form-level defaults.
| Option | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'lg' | undefined | Default size for form controls |
floatingLabel | boolean | false | Use Bootstrap floating label style on text inputs and select |
variant | 'primary' | 'secondary' | 'success' | 'danger' | ... | 'primary' | Default Bootstrap color variant for buttons |
outline | boolean | false | Render buttons as outlined by default |
block | boolean | false | Render buttons as block-level (full width) by default |
Library-level (provider)
provideDynamicForm(
...withBootstrapFields({
size: 'sm',
floatingLabel: true,
variant: 'primary',
outline: false,
block: false,
})
)Form-level (defaultProps)
Use BsFormConfig from @ng-forge/dynamic-forms-bootstrap for type-safe defaultProps with autocomplete.
import { BsFormConfig } from '@ng-forge/dynamic-forms-bootstrap';
const config = {
defaultProps: {
size: 'lg', // overrides the library-level setting
floatingLabel: false,
},
fields: [
{ type: 'input', key: 'name', label: 'Name' },
{ type: 'input', key: 'email', label: 'Email', props: { size: 'sm' } },
],
} as const satisfies BsFormConfig;Field-level Props
Each field type also accepts its own adapter-specific props. See Field Types for the full per-field reference.
Nullable values
Value fields accept an optional nullable?: boolean flag. When true:
valueacceptsnullin addition to the field's normal type (e.g.string | null).- An omitted
valueresolves tonullinstead of the type-specific empty default ('',NaN,[], …). nullablestays orthogonal torequired: they describe different layers.nullabledeclares that the model acceptsnull(data shape).requiredis a validation constraint. ng-forge maps{ type: 'required' }to the Signal Formsrequired()validator, which treatsnullas invalid, so a field that is bothnullableandrequiredwill fail required-validation when the value isnull. The flags are independent OpenAPI concepts; combine them if that matches your schema, but understand the runtime interaction.
{
key: 'middleName',
type: 'input',
label: 'Middle Name',
nullable: true,
value: null, // allowed; also the resolved default when omitted
}Read-side caveat. A user clearing a text input reads back as "", not null. This is a DOM/Web IDL contract, identical to classic Reactive Forms. nullable is a contract for accepted values, not a guarantee of emitted ones. If your backend distinguishes null from empty string, handle the coercion at submission.
Multiple forms on one page
Each field renders a DOM id derived from its key (id="email", id="email-input"), and that id is reused for the <label for>, aria-describedby, and error/hint targets. Render two forms built from the same config on one page and those ids collide: clicking the second form's label focuses the first form's input, and duplicate ids are invalid HTML that also break getByLabelText-style queries.
ng-forge scopes ids to a form instance to prevent this:
- One form on the page: ids stay clean and unprefixed (
email-input). Nothing changes, so existing selectors anddata-testids are untouched. - Two or more forms visible at once: each form automatically gets a generated prefix (
df-1,df-2, …), so its ids becomedf-2_email-input. Detection tracks the forms currently rendered, so a form reverts to clean, unprefixed ids once it's the only one left (and hidden/cached pages, such as a previous route still in the DOM, don't count).
Set options.idPrefix when you want stable, human-readable ids regardless of how many forms are on the page (recommended when you control both forms). An explicit prefix always wins over auto-detection:
const billing = {
options: { idPrefix: 'billing' },
fields: [{ key: 'email', type: 'input', label: 'Email' }],
} as const satisfies FormConfig;
// → <form id="billing">, <input id="billing_email-input">, <label for="billing_email-input">The prefix is the outermost id segment, composing with group and array scoping:
{idPrefix}_{group}_{key}_{index} e.g. billing_address_street_0Use a valid id token: letters, digits, -, _. Whitespace or punctuation is replaced with _ (with a dev-mode warning), since a space would break the aria-describedby token list and for/id matching.
Writing E2E selectors or
aria-*references against a form that shares the page with another form? Set an explicitidPrefixso the ids you target are deterministic, then select#billing_email-inputrather than the auto-generated#df-2_email-input.
Next Steps
- Examples: Browse complete form examples
- Field Types: Per-field adapter props reference
- Building an Adapter: Build your own adapter