Introduction
@ngaf/render is the Angular rendering engine for json-render specs. It takes a declarative JSON specification and renders it into a live Angular component tree -- with reactive state, event handling, and conditional rendering built in.
Why @ngaf/render?
Building dynamic UIs from server-driven specifications is a common pattern in AI applications, form builders, and CMS-powered frontends. @ngaf/render bridges the gap between @json-render/core (a framework-agnostic spec evaluation engine) and Angular's component model.
Instead of writing imperative rendering logic, you describe your UI as a JSON spec and let the library handle the rest:
The library resolves Text from your component registry, evaluates the $state expression against the state store, and renders your TextComponent with label set to "Hello, world!". When the state changes, the component updates automatically via Angular Signals.
How It Relates to @json-render/core
@json-render/core provides the spec format and the evaluation engine -- it resolves prop expressions ($state, $item, $index, $bindState, $fn), evaluates visibility conditions, and resolves bindings. It is framework-agnostic and has no Angular dependency.
@ngaf/render is the Angular adapter layer. It provides:
- Component registry -- maps spec element types (like
"Text"or"Card") to Angular component classes - Signal-based state store -- an Angular Signals-backed implementation of the
StateStoreinterface from@json-render/core - Recursive rendering -- a component tree that walks the spec and dynamically renders Angular components via
NgComponentOutlet - Dependency injection integration --
provideRender()for global config,RENDER_CONTEXTfor child components, andREPEAT_SCOPEfor repeat iterations
Key Concepts
Specs
A spec is a JSON object that describes a UI tree. It has three parts:
root-- the key of the root elementelements-- a flat map of element keys toUIElementdefinitionsstate-- (optional) initial state for the state store
Registry
A registry maps element type names to Angular component classes. You define one with defineAngularRegistry():
State Store
The state store holds the reactive state that drives your UI. Values are accessed via JSON Pointer paths (like /user/name). The library provides signalStateStore(), which uses Angular Signals internally so that state changes trigger change detection automatically.
Component Input Contract
Every component rendered by the library receives a standard set of inputs defined by the AngularComponentInputs interface:
emit-- a function to dispatch eventsbindings-- a map of two-way binding pathsloading-- whether the spec is currently streamingchildKeys-- keys for recursive child renderingspec-- the full spec (for child resolution)- Plus any resolved props from the element definition
Events and Handlers
Elements can define event handlers via the on property. When a component calls emit('submit'), the library looks up the corresponding action and dispatches it to a registered handler function.
Architecture Overview
The rendering pipeline works as follows:
RenderSpecComponentreceives a spec, registry, and store. It resolves defaults fromRENDER_CONFIG(provided byprovideRender()) and provides aRENDER_CONTEXTto its children.RenderElementComponentreceives an element key and the spec. For each element, it:- Looks up the
UIElementdefinition fromspec.elements - Resolves the Angular component class from the registry
- Evaluates the
visiblecondition - Resolves prop expressions and bindings using
@json-render/core - Renders the component via
NgComponentOutletwith the resolved inputs
- Looks up the
- For elements with
repeat, the library iterates over the state array and creates a childInjectorwith aRepeatScopefor each item.