Add a custom field type to any existing ng-forge adapter — no need to build a full adapter from scratch.

Scenario

You're using Material, Bootstrap, or another adapter and need a field type that the adapter doesn't provide — for example, a rich text editor, a file uploader, or a custom rating widget.

1. Create Your Field Component

Implement ValueFieldComponent<T> from the core package:

import { ChangeDetectionStrategy, Component, output } from '@angular/core';
import { ValueFieldComponent } from '@ng-forge/dynamic-forms';

@Component({
  selector: 'app-rich-text-field',
  template: `<label [textContent]="field().label"></label>
    <my-rich-editor [value]="field().value()" (valueChange)="valueChange.emit($event)" />`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RichTextFieldComponent implements ValueFieldComponent<string> {
  // Injected by the form engine — contains value, label, props, errors, etc.
  readonly field = input.required<ResolvedValueField<string>>();
  readonly valueChange = output<string>();
}

2. Define the Field Type

import { FieldTypeDefinition, valueFieldMapper } from '@ng-forge/dynamic-forms/integration';
import { RichTextFieldComponent } from './rich-text-field.component';

const richTextField: FieldTypeDefinition = {
  type: 'rich-text',
  loadComponent: () => RichTextFieldComponent,
  mapper: valueFieldMapper,
  renderReadyWhen: ['field'],
};

3. Provide Alongside Your Adapter

Pass your custom field definition to provideDynamicForm() alongside the adapter's fields:

import { provideDynamicForm } from '@ng-forge/dynamic-forms';
import { withMaterialFields } from '@ng-forge/dynamic-forms-material';

export const appConfig: ApplicationConfig = {
  providers: [
    provideDynamicForm(
      ...withMaterialFields(), // all Material field types
      richTextField, // your custom field
    ),
  ],
};

4. Use It in a Form

const config = {
  fields: [
    { key: 'name', type: 'input', value: '', label: 'Name' },
    { key: 'body', type: 'rich-text', value: '', label: 'Content' },
    { key: 'submit', type: 'submit', label: 'Save' },
  ],
} as const satisfies FormConfig;

Key Points

  • Custom fields work with all conditional logic, validation, and value derivation — no special handling needed
  • You can add as many custom fields as you need alongside any adapter
  • The mapper function translates the resolved field to your component's inputs — there are multiple available mappers like valueFieldMapper for standard value-based fields, optionsFieldMapper for option fields, datepickerFieldMapper for date fields, checkboxFieldMapper for boolean fields, and more. You can also write your own custom mapper if needed.
  • If your custom component declares field = input.required(...), the renderer waits for field automatically when using a built-in mapper. Use explicit renderReadyWhen: ['field'] only for custom mappers that supply other reactive inputs, or renderReadyWhen: [] to opt out

Going Further

If you need a completely custom adapter (no existing adapter as base), or want to implement the config cascade (defaultProps) for your custom fields, see the full Custom Integrations guide.

Next Steps

  • Custom Integrations — Build a complete adapter for any UI library from scratch
  • Type Safety — Leverage TypeScript inference for form values and field types
  • Events — Dispatch and subscribe to form events from custom field components