DocsRenderGetting StartedIntroduction

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:

const spec: Spec = {
  root: 'greeting',
  elements: {
    greeting: {
      type: 'Text',
      props: { label: { $state: '/message' } },
    },
  },
  state: { message: 'Hello, world!' },
};
<render-spec [spec]="spec" [registry]="registry" />

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 StateStore interface 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_CONTEXT for child components, and REPEAT_SCOPE for 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 element
  • elements -- a flat map of element keys to UIElement definitions
  • state -- (optional) initial state for the state store

Registry

A registry maps element type names to Angular component classes. You define one with defineAngularRegistry():

const registry = defineAngularRegistry({
  Text: TextComponent,
  Card: CardComponent,
  Button: ButtonComponent,
});

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 events
  • bindings -- a map of two-way binding paths
  • loading -- whether the spec is currently streaming
  • childKeys -- keys for recursive child rendering
  • spec -- 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:

  1. RenderSpecComponent receives a spec, registry, and store. It resolves defaults from RENDER_CONFIG (provided by provideRender()) and provides a RENDER_CONTEXT to its children.
  2. RenderElementComponent receives an element key and the spec. For each element, it:
    • Looks up the UIElement definition from spec.elements
    • Resolves the Angular component class from the registry
    • Evaluates the visible condition
    • Resolves prop expressions and bindings using @json-render/core
    • Renders the component via NgComponentOutlet with the resolved inputs
  3. For elements with repeat, the library iterates over the state array and creates a child Injector with a RepeatScope for each item.

Next Steps