views()
Creates an immutable view registry mapping spec element type names to Angular components. A view registry drives <render-spec> when it is provided through provideViews(), and it is also the shape the chat components accept for their generative-UI path.
Usage
import { views } from '@threadplane/render';
const ui = views({
'plan-checklist': PlanChecklistComponent,
'file-preview': FilePreviewComponent,
'code-output': CodeOutputComponent,
});Provide it once, then render specs whose element type values match the registered names:
// app.config.ts
import { provideViews } from '@threadplane/render';
export const appConfig: ApplicationConfig = {
providers: [provideViews(ui)],
};<render-spec [spec]="spec" />The same registry is what the chat component accepts on its views input:
<chat [agent]="stream" [views]="ui" />Composition
Compose registries via object spread. The last key wins for overrides:
const all = views({
...thirdPartyViews,
...myViews,
'chart': MyCustomChart, // overrides thirdPartyViews['chart']
});Per-View Fallbacks
Each entry is either a bare component class or a { component, fallback } object. The fallback mounts while any resolved prop on the element is still undefined -- the same shape defineAngularRegistry() accepts:
const ui = views({
'plan-checklist': {
component: PlanChecklistComponent,
fallback: PlanSkeletonComponent,
},
'file-preview': FilePreviewComponent, // bare form uses the default fallback
});withViews() and overrideViews() accept the same object form in their addition and override maps.
API
views(map)
Creates a frozen ViewRegistry.
| Parameter | Type | Description |
|---|---|---|
map | Record<string, Type<unknown> | RenderViewEntry> | Name → component class, or a { component, fallback?, schema?, description? } entry |
| Returns | ViewRegistry | Frozen immutable registry |
withViews(base, additions)
Adds new views without overwriting existing entries. Existing keys in base are preserved.
const extended = withViews(base, {
'new-widget': NewWidget, // added
'existing': Other, // IGNORED — base already has 'existing'
});overrideViews(base, overrides)
Replaces entries in a registry. Keys in overrides win over base. Use this when you want to swap an existing renderer; use withViews when you want to add new node types without touching existing ones.
import { overrideViews } from '@threadplane/render';
const myRegistry = overrideViews(baseRegistry, {
'code-block': MyCodeBlockComponent,
});Returns a new frozen ViewRegistry. The base argument is not mutated.
withoutViews(base, ...names)
Removes views by name:
const restricted = withoutViews(base, 'dangerous-widget', 'internal-tool');provideViews(registry)
Angular DI provider for the VIEW_REGISTRY token. Works at the application injector or on a route:
// Global
providers: [provideViews(ui)]
// Route-scoped
{ path: 'planning', providers: [provideViews(planningViews)] }<render-spec> consumes VIEW_REGISTRY as a third-priority fallback in registry resolution. The full priority order is: the [registry] template input → RENDER_CONFIG.registry (from provideRender(...)) → VIEW_REGISTRY (from provideViews(...)) → an empty registry. So provideViews(myRegistry) drives rendering when no provideRender({ registry }) is wired and no [registry] input is bound. <render-element> never reads the token itself; it uses the registry <render-spec> already resolved and shared through RENDER_CONTEXT.
toRenderRegistry(registry)
Converts a ViewRegistry to the low-level AngularRegistry type the render components consume. Called internally by <render-spec> and by the chat components -- most developers do not need it directly.
View Components
View components are standard Angular standalone components. They receive resolved props as input() signals:
@Component({
selector: 'plan-checklist',
standalone: true,
template: `
<div class="border rounded-xl p-4">
<h4>{{ title() }}</h4>
<ng-content />
</div>
`,
})
export class PlanChecklistComponent {
readonly title = input<string>('Plan');
}The element type in the spec is matched against the registry key to resolve the component class, so the names in views({ ... }) are the allowlist of what a spec is permitted to render. For how the chat components decide that a streamed assistant message is a spec at all, see json-render vs A2UI.
Related
- defineAngularRegistry() -- the low-level
AngularRegistrythattoRenderRegistry()converts aViewRegistryinto - provideRender() -- global render config, including a registry
- json-render vs A2UI -- how chat classifies streamed content into the json-render, A2UI, and markdown paths
- Chat generative-UI guide -- wiring
views()into chat so an agent can stream specs that render inline