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