Render · API Reference

defineAngularRegistry()

Creates an AngularRegistry that maps element type names to Angular component classes.

Import

import { defineAngularRegistry } from '@threadplane/render';

Signature

function defineAngularRegistry(
  componentMap: Record<string, AngularComponentRenderer | RenderViewEntry>,
): AngularRegistry;

Parameters

ParameterTypeDescription
componentMapRecord<string, AngularComponentRenderer | RenderViewEntry>An object mapping type name strings to Angular component classes, or to { component, fallback? } objects

AngularComponentRenderer is defined as Type<unknown> -- any Angular component class. Each entry can be a bare component class or a RenderViewEntry object:

interface RenderViewEntry {
  component: AngularComponentRenderer;
  fallback?: AngularComponentRenderer;
  schema?: StandardSchemaV1;
  description?: string;
}

Use the object form to configure a custom per-entry fallback, a Standard Schema validator, or a description for higher-level tooling. A bare component class is shorthand for { component } paired with the library's default fallback.

Returns

An AngularRegistry object with a single entry accessor:

interface AngularRegistry {
  getEntry(name: string): NormalizedEntry | undefined;
  names(): string[];
}
 
interface NormalizedEntry {
  component: Type<unknown>;
  fallback: Type<unknown>;
  schema?: StandardSchemaV1;
  description?: string;
}
MethodDescription
getEntry(name)Returns the fully-normalized entry for a registered name, or undefined if not registered. The component, the resolved fallback (the entry's own or the library default), and any optional schema/description all hang off the returned object.
names()Returns an array of all registered type name strings

Usage

Basic Registry

import { defineAngularRegistry } from '@threadplane/render';
import { TextComponent } from './text.component';
import { CardComponent } from './card.component';
 
const registry = defineAngularRegistry({
  Text: TextComponent,
  Card: CardComponent,
});
 
registry.getEntry('Text')?.component;    // TextComponent
registry.getEntry('Card')?.component;    // CardComponent
registry.getEntry('Missing');            // undefined
registry.names();                        // ['Text', 'Card']

With provideRender()

import { provideRender, defineAngularRegistry } from '@threadplane/render';
 
export const appConfig: ApplicationConfig = {
  providers: [
    provideRender({
      registry: defineAngularRegistry({
        Text: TextComponent,
        Card: CardComponent,
        Button: ButtonComponent,
      }),
    }),
  ],
};

As a Component Input

@Component({
  imports: [RenderSpecComponent],
  template: `<render-spec [spec]="spec" [registry]="registry" />`,
})
export class MyComponent {
  registry = defineAngularRegistry({
    Text: TextComponent,
  });
}

Per-Component Fallbacks

Each entry can be a bare component class or a { component, fallback } object. The fallback mounts while any state-bound prop on the element is still unresolved -- useful for streaming UI, where structure arrives before data:

import { defineAngularRegistry } from '@threadplane/render';
import { TextComponent } from './text.component';
import { CardComponent } from './card.component';
import { CardSkeletonComponent } from './card-skeleton.component';
 
const registry = defineAngularRegistry({
  Text: TextComponent,
  Card: { component: CardComponent, fallback: CardSkeletonComponent },
});
 
registry.getEntry('Card')?.fallback; // CardSkeletonComponent (the configured fallback)
registry.getEntry('Text')?.fallback; // DefaultFallbackComponent (entry omits one)
registry.getEntry('Missing');        // undefined (not registered)

An entry that omits fallback -- including every bare-component entry like Text above -- falls back to the library's DefaultFallbackComponent. Once the real component mounts, the choice is monotonic for that element instance: a later prop resolving to undefined never reverts it to the fallback.

Schema-Gated Mounting

Add schema when a component should mount only after resolved props satisfy a Standard Schema validator such as Zod:

import { z } from 'zod';
import { defineAngularRegistry } from '@threadplane/render';
 
const registry = defineAngularRegistry({
  WeatherCard: {
    component: WeatherCardComponent,
    fallback: WeatherCardSkeletonComponent,
    schema: z.object({
      city: z.string(),
      temperature: z.number(),
    }),
    description: 'Shows current weather for a city.',
  },
});

While props are missing or fail validation, the fallback renders. Once the props pass validation and the component mounts, later updates do not move that element back to the fallback.

Internal Behavior

The function normalizes each input entry into a NormalizedEntry ({ component, fallback, schema?, description? }) and stores them in an internal Map for O(1) lookups. A bare component class is paired with DefaultFallbackComponent; an object entry keeps its own fallback (or the default) and preserves any schema/description:

function normalize(
  entry: AngularComponentRenderer | RenderViewEntry,
): NormalizedEntry {
  // Bare Type — register with the default fallback.
  if (typeof entry === 'function') {
    return { component: entry, fallback: DefaultFallbackComponent };
  }
  // Object form — preserve component; use configured fallback or default;
  // carry the optional schema/description through untouched.
  return {
    component: entry.component,
    fallback: entry.fallback ?? DefaultFallbackComponent,
    schema: entry.schema,
    description: entry.description,
  };
}
 
function defineAngularRegistry(
  componentMap: Record<string, AngularComponentRenderer | RenderViewEntry>,
): AngularRegistry {
  const map = new Map<string, NormalizedEntry>();
  for (const [name, entry] of Object.entries(componentMap)) {
    map.set(name, normalize(entry));
  }
  return {
    getEntry: (name: string) => map.get(name),
    names: () => [...map.keys()],
  };
}
Immutable after creation

The registry is immutable after creation. To add or remove components, create a new registry with the updated component map. The internal Map is created once and not exposed for mutation.

Type Name Conventions

Type names in the registry must match the type field in your spec elements exactly (case-sensitive):

// Registry
defineAngularRegistry({ Text: TextComponent });
 
// Spec -- must match exactly
{ type: 'Text', props: { label: 'Hello' } }   // matches
{ type: 'text', props: { label: 'Hello' } }   // does NOT match
defineAngularRegistryfunction

Build an AngularRegistry from a plain object mapping tool-call names to Angular components (or fully specified RenderViewEntry objects). The returned registry is consumed by both `provideRender` (to drive dynamic component rendering) and `provideChat` (via `renderRegistry`) so that a single `defineAngularRegistry` call wires both layers. **Entry forms** - Bare `Type<unknown>` — the component is paired with the built-in `DefaultFallbackComponent` while its props are still streaming. - `RenderViewEntry` object — lets you supply a custom `fallback` component, an optional Standard Schema (`schema`) used as a mount-readiness gate, and an optional `description` for model-facing tool registration. **Registry accessor** The returned object exposes a single `getEntry(name: string)` accessor that returns the fully-normalized NormalizedEntry (component + fallback + optional schema + optional description) or `undefined` when the name is not registered. Use `names()` to enumerate all registered names.

defineAngularRegistry(componentMap: RegistryInput): AngularRegistry

Parameters

ParameterTypeDescription
componentMapRegistryInputObject whose keys are tool-call names and whose values are either bare Angular component classes or RenderViewEntry objects.

Returns

AngularRegistry

Examples

import { defineAngularRegistry } from '@threadplane/render';
import { DayCardComponent } from './day-card.component';
import { LoadingSpinnerComponent } from './loading-spinner.component';
import { z } from 'zod';

export const registry = defineAngularRegistry({
  // Bare component — uses DefaultFallbackComponent while streaming.
  summary_card: SummaryCardComponent,

  // Full entry — custom fallback + schema-gated mounting.
  day_card: {
    component: DayCardComponent,
    fallback: LoadingSpinnerComponent,
    schema: z.object({ label: z.string(), day: z.number() }),
    description: 'Renders a single itinerary day card.',
  },
});

// Look up a registered entry at runtime:
const entry = registry.getEntry('day_card'); // NormalizedEntry | undefined