---
title: "Dynamic Fields Host Custom Field Guide"
slug: "dynamic-fields-host-custom-field-guide"
description: "Guia enterprise para criar, registrar e operar fields custom no host usando @praxisui/dynamic-fields."
doc_type: "guide"
document_kind: "host-guide"
component: "dynamic-fields"
category: "components"
audience:
  - "host"
  - "frontend"
  - "architect"
level: "advanced"
status: "active"
owner: "praxis-ui"
tags:
  - "dynamic-fields"
  - "host extension"
  - "custom field"
  - "environment initializer"
order: 36
icon: "extension"
toc: true
sidebar: true
search_boost: 1.1
reading_time: 20
estimated_setup_time: 35
version: "1.0"
related_docs:
  - "dynamic-fields-field-catalog"
  - "dynamic-fields-field-selection-guide"
  - "dynamic-fields-host-custom-field-troubleshooting"
  - "dynamic-fields-inventory"
  - "dynamic-form-overview"
keywords:
  - "ComponentRegistryService.register"
  - "ComponentMetadataRegistry.register"
  - "ENVIRONMENT_INITIALIZER"
  - "custom controlType"
last_updated: "2026-03-07"
---

# Dynamic Fields Host Custom Field Guide

## Objetivo

Permitir que times host criem novos fields sem forkar `@praxisui/dynamic-fields` ou `@praxisui/dynamic-form`.

## Pre-requisitos

- Host Angular com `ApplicationConfig` ou bootstrap equivalente.
- `@praxisui/dynamic-fields` instalado com providers ativos.
- Familiaridade basica com `FieldMetadata`, `controlType` e Angular Forms.

## Contract for a good custom field

Um field custom enterprise precisa atender quatro camadas:

1. `runtime render`: o registry precisa saber qual componente carregar para o `controlType`.
2. `forms integration`: o componente precisa funcionar com `ControlValueAccessor` e/ou `[formControl]`.
3. `metadata editor`: o editor precisa saber nome amigavel, icone e metadados do componente.
4. `hot metadata`: o componente precisa responder a patches sem rebuild global sempre que possivel.

## Runtime registry vs metadata registry

### Runtime registry

Responsavel por renderizar o componente no loader:

```ts
ComponentRegistryService.register('my-custom', () =>
  import('./my-custom-field.component').then((m) => m.MyCustomFieldComponent),
);
```

### Metadata registry

Responsavel por:

- nome amigavel no editor;
- icone;
- superficie editorial associada ao `controlType`.

O `ComponentDocMeta.id` precisa bater com o `controlType` de runtime.

## Canonical package-owned fields vs host custom fields

Regra de fronteira:

- fields mantidos por `@praxisui/dynamic-fields` devem entrar pela trilha canonica da lib: descriptor editorial + metadata derivado + catalogo derivado;
- fields custom do host continuam suportados via `ComponentMetadataRegistry.register(ComponentDocMeta)`;
- o host nao precisa copiar a trilha inteira da lib para continuar funcionando no `dynamic-form`;
- se o host quiser semantica editorial propria, ela deve nascer no seu proprio registry/contrato, nao por override local de catalogo ou patch no `dynamic-form`.

Na pratica, o `dynamic-form` tenta primeiro a resolucao canonica editorial e, se ela nao existir para um `controlType` do host, usa o `ComponentDocMeta` registrado pelo proprio host como fallback tecnico controlado.

## Minimal component requirements

### Forms compatibility

O componente host deve:

- implementar `ControlValueAccessor`, ou
- aceitar `[formControl]`/`formControlName` de forma correta.

Sem isso, o field pode ate renderizar, mas quebra integracao com o formulario dinamico.

### Hot metadata compatibility

Com base no runtime documentado em `hot-metadata-updates.md`, prefira:

- `setInputMetadata(metadata)` no componente, ou
- um `WritableSignal` para metadata.

Isso permite que o dynamic form reaplique metadata viva sem rebuild estrutural sempre que a mudanca nao for estrutural.
Use como referencia operacional governada o arquivo `projects/praxis-dynamic-form/docs/hot-metadata-updates.md`.

## Example: ApplicationConfig with ENVIRONMENT_INITIALIZER

```ts
import { ApplicationConfig, ENVIRONMENT_INITIALIZER, inject } from '@angular/core';
import { providePraxisDynamicFields } from '@praxisui/dynamic-fields';
import { ComponentRegistryService } from '@praxisui/dynamic-fields';
import { ComponentMetadataRegistry, type ComponentDocMeta } from '@praxisui/core';

const myCustomMeta: ComponentDocMeta = {
  id: 'my-custom',
  title: 'My Custom Field',
  icon: 'extension',
  category: 'host-fields',
  description: 'Field custom do host para selecionar SLA operacional.',
};

export const appConfig: ApplicationConfig = {
  providers: [
    ...providePraxisDynamicFields(),
    {
      provide: ENVIRONMENT_INITIALIZER,
      multi: true,
      useValue: () => {
        const registry = inject(ComponentRegistryService);
        registry.register('my-custom', () =>
          import('./fields/my-custom-field.component').then((m) => m.MyCustomFieldComponent),
        );
      },
    },
    {
      provide: ENVIRONMENT_INITIALIZER,
      multi: true,
      useValue: () => {
        const metadataRegistry = inject(ComponentMetadataRegistry);
        metadataRegistry.register(myCustomMeta);
      },
    },
  ],
};
```

## Example: component skeleton

```ts
import { Component, input, signal } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, ReactiveFormsModule } from '@angular/forms';
import type { FieldMetadata } from '@praxisui/core';

@Component({
  selector: 'app-my-custom-field',
  standalone: true,
  imports: [ReactiveFormsModule],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: MyCustomFieldComponent,
    },
  ],
  template: `
    <input [formControl]="control" />
  `,
})
export class MyCustomFieldComponent implements ControlValueAccessor {
  readonly control = new FormControl('');
  readonly metadata = signal<FieldMetadata | null>(null);

  setInputMetadata(metadata: FieldMetadata): void {
    this.metadata.set(metadata);
  }

  writeValue(value: unknown): void {
    this.control.setValue(value, { emitEvent: false });
  }

  registerOnChange(fn: (value: unknown) => void): void {
    this.control.valueChanges.subscribe(fn);
  }

  registerOnTouched(fn: () => void): void {
    // ligue ao blur/touch do host
  }

  setDisabledState(disabled: boolean): void {
    disabled ? this.control.disable({ emitEvent: false }) : this.control.enable({ emitEvent: false });
  }
}
```

## Using the custom field in form contract

```ts
const fieldMetadata = [
  {
    name: 'sla',
    label: 'SLA operacional',
    controlType: 'my-custom',
  },
];
```

## Provider defaults vs no-defaults

### `providePraxisDynamicFields()`

- registra providers core;
- inclui defaults do selector registry;
- e a escolha comum para host app padrao.

### `providePraxisDynamicFieldsNoDefaults()`

- desabilita o mapa default de selector -> controlType;
- use apenas quando o host quer governar totalmente o mapa base.

## Selector registry / aliases

Nao confunda:

- `ComponentRegistryService.register(...)`: carrega o componente Angular real.
- `ComponentMetadataRegistry.register(...)`: registra nome/icone/superficie editorial.
- `FieldSelectorRegistry` / aliases: resolve seletor/roteamento complementar em modo config-first.

Em cenarios enterprise:

- prefira `controlType` estavel e unico;
- use aliases apenas quando um programa de migracao exigir compatibilidade.

## Checklist for host teams

- `runtime registra`: `ComponentRegistryService.register(...)`.
- `metadata registra`: `ComponentMetadataRegistry.register(...)`.
- `controlType alinhado`: o mesmo id em runtime e metadata.
- `forms compatível`: CVA ou `[formControl]`.
- `hot update compatível`: `setInputMetadata` e/ou signal metadata.
- `tema/tokens`: sem quebrar contraste, estados e largura no host.
