The component registry maps element type names from your spec to Angular component classes. It's the bridge between the declarative JSON spec and your Angular component tree.
Let's create a registry with defineAngularRegistry() from a plain object that maps type names to component classes:
import { defineAngularRegistry } from '@threadplane/render';import { TextComponent } from './text.component';import { CardComponent } from './card.component';import { ButtonComponent } from './button.component';import { ContainerComponent } from './container.component';export const uiRegistry = defineAngularRegistry({ Text: TextComponent, Card: CardComponent, Button: ButtonComponent, Container: ContainerComponent,});
The returned AngularRegistry object has two methods:
getEntry(name: string) -- returns the fully-normalized entry ({ component, fallback, schema?, description? }) for a registered name, or undefined if not registered. The resolved fallback is the entry's own renderer, or the library's default when the entry omits one.
names() -- returns an array of all registered type names
Fallbacks belong to registered entries. When an element's type is not registered, there is no entry to read and the element renders nothing. When the type is registered, the entry's configured fallback can fill a transient gap while state-bound props are still resolving. Once the real component mounts, it stays mounted -- later re-renders never revert to the fallback.
Every component rendered by @threadplane/render receives inputs conforming to the AngularComponentInputs interface. Your custom props from the spec are spread as additional inputs alongside the standard ones.
Here's a complete component designed to work with the rendering system:
import { Component, ChangeDetectionStrategy, input } from '@angular/core';import type { Spec } from '@json-render/core';@Component({ selector: 'app-card', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: ` <div class="card"> <h2>{{ title() }}</h2> <p>{{ description() }}</p> @if (loading()) { <div class="loading-indicator">Loading...</div> } </div> `,})export class CardComponent { // Custom props from the spec readonly title = input<string>(''); readonly description = input<string>(''); // Standard inputs from AngularComponentInputs readonly emit = input<(event: string) => void>(() => {}); readonly bindings = input<Record<string, string>>({}); readonly loading = input<boolean>(false); readonly childKeys = input<string[]>([]); readonly spec = input<Spec | null>(null);}
✓Input defaults
Always provide default values for your inputs. The rendering system spreads resolved props onto the component, but not all standard inputs are guaranteed to have values in every context.
Use set(path, value) for state writes, emit(event, payload?) for spec on handlers, and result(value) when the component has produced a value that the host should observe as a RenderResultEvent.