The component registry maps element type names from your spec to Angular component classes. It is the bridge between the declarative JSON spec and your Angular component tree.
Creating a Registry
Use defineAngularRegistry() to create a registry from a plain object mapping type names to component classes:
import { defineAngularRegistry } from '@ngaf/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:
get(name: string) -- returns the component class for the given type name, or undefined if not registered
names() -- returns an array of all registered type names
Every component rendered by @ngaf/render receives inputs conforming to the AngularComponentInputs interface. Your custom props from the spec are spread as additional inputs alongside the standard ones.
Standard Inputs
Input
Type
Description
emit
(event: string) => void
Function to dispatch named events
bindings
Record<string, string>
Two-way binding paths: prop name to absolute state path
loading
boolean
Whether the spec is currently streaming
childKeys
string[]
Element keys for recursive child rendering
spec
Spec
The full spec object (for child resolution)
Custom Props
Any props defined in the element's props are resolved and passed as additional inputs. For example, given this element:
Your component receives label and size as inputs alongside the standard inputs.
Writing a Renderable Component
Here is a complete example of a 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.
Two-Way Bindings
When a prop uses $bindState, the bindings input receives a mapping from the prop name to the state path. This enables two-way binding patterns:
// In your spec{ type: 'Input', props: { value: { $bindState: '/form/email' }, label: 'Email', },}
Your component receives:
value resolved to the current state value (e.g., "test@example.com")
bindings set to { value: '/form/email' }
You can use the bindings map to write back to the store: