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