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;
}

Use the object form to configure a custom per-entry fallback (see Per-Component Fallbacks below). 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.

#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