Addons decorate a field with inline content (a search icon, a clear button, a currency symbol, a password-visibility toggle) placed in the field's prefix or suffix slot. They're typed, JSON-safe, and ship as a first-class feature of every UI adapter.
Prerequisites. Addons attach to a field, not the form. You need a working ng-forge setup first:
provideDynamicForm(...with{Adapter}Fields())at the application config and a[dynamic-form]-bearing form using a field type that supports addons (today: every adapter'sinput). See Getting Started if you're new to ng-forge.
Quickstart
{
key: 'search',
type: 'input',
label: 'Search',
addons: [
{ slot: 'prefix', type: 'mat-icon', icon: 'search', ariaLabel: 'Search' },
{ slot: 'suffix', type: 'mat-button', icon: 'close', ariaLabel: 'Clear', preset: 'clear' },
],
}
Live example
A universal addon (works the same in every adapter, no per-type branching):
Available types
| Type | Renders | Notes |
|---|---|---|
mat-icon | <mat-icon>search</mat-icon> | Material Icons name. Add ariaLabel for non-decorative icons. |
mat-button | <button mat-button> / <button mat-icon-button> | Exactly one of preset / actionRef / action. Severity/color, label, icon all supported. |
text | <span> with DynamicText | Universal; supports plain strings, signals, observables, i18n keys. |
template | Named <ng-template> | Reference by templateKey. JSON-safe: backend ships the key, FE supplies the template. |
component | Arbitrary Angular component | Code-only; dropped from JSON-derived configs. |
How addons render
Addons render as direct <mat-form-field> children with matPrefix / matSuffix attribute directives applied to <df-addon-slot>, Material's native projection API.
The wrapper is dropped entirely when every addon is reactively hidden: no empty group element.
Provider setup
import { provideDynamicForm } from '@ng-forge/dynamic-forms';
import { withMaterialFields } from '@ng-forge/dynamic-forms-material';
// mat-icon + mat-button work out of the box.
provideDynamicForm(...withMaterialFields());
// Standalone: addon types without the field types.
// provideDynamicForm(...myCustomFields(), withMaterialAddons());
Reactive hidden and disabled
Both axes accept DynamicValue<boolean>: any of boolean, Signal<boolean>, or Observable<boolean>. Four equivalent shapes for the same toggle:
{ ..., hidden: true } // Static — JSON-safe.
{ ..., hidden: signal(false) } // Signal — code-only.
{ ..., hidden: computed(() => !hasValue()) } // Derived — code-only.
{ ..., hidden: visibility$ } // Observable — code-only.The classic "show clear button only when input has value" pattern, in your active adapter:
const hasValue = computed(() => (formValue()?.search?.length ?? 0) > 0);
{
slot: 'suffix',
type: 'mat-button',
icon: 'close',
ariaLabel: 'Clear',
preset: 'clear',
hidden: computed(() => !hasValue()),
}
When hidden resolves to true the addon is filtered out of the rendered slot entirely, and it reappears reactively as soon as hidden turns falsy again.
Authoring forms in JSON? Reactive values can't survive serialization; see Reactive addons from JSON.
Accessibility
Icon types emit aria-hidden="true" by default. When the icon conveys meaning (status, action), set ariaLabel. Icon-only button types (no label) require ariaLabel. TypeScript flags it at compile time; at runtime an icon-only addon missing ariaLabel is dropped with a [Dynamic Forms] warning rather than rendered, and the rest of the form is unaffected.
Troubleshooting
- Addon doesn't render at all. Check the console for
[Dynamic Forms]warnings. Common causes: the active adapter'swith*Fields()helper isn't inprovideDynamicForm; thetypestring belongs to a different adapter (e.g.prime-buttonin a Material form); the host field type doesn't support addons. - Icon-only button has no
ariaLabel. TypeScript and the runtime validator both refuse this; setariaLabel. For genuinely decorative icons, prefertype: 'text'or the adapter's*-icontype. actionRefwarning at click time. The handler name isn't registered. Did you callwithAddonActions({ runSearch: ... })for that name?- Inline function silently dropped. Configs with
source: 'json'strip functions onaction,hidden,disabled,loadingat validation time; they can't round-trip through JSON. See Reactive addons from JSON. - Material
MatFormFieldControlContentChild missing. Caused by wrapping the input in a template that breaks Material's content-projection query. Renderdirectly inside.
Where to next
- Presets and Actions: built-in click presets (
clear,reset,paste,copy,toggle-password-visibility),actionReffor registered handlers, and inlineactionfor code-only behaviour. - Custom Types: register your own addon type (rating widget, status pill, anything) with
withCustomAddon(...)and augment the type-level extensions seam.