Docsโ€บRenderโ€บA2UIโ€บcreateA2uiSurfaceStore()

createA2uiSurfaceStore()

Factory function that creates an A2uiSurfaceStore โ€” a reactive store that accumulates A2UI messages into a live Map of surfaces.

Import:

import { createA2uiSurfaceStore } from '@ngaf/chat';

Signature

function createA2uiSurfaceStore(): A2uiSurfaceStore

Returns: A2uiSurfaceStore โ€” a stateful store backed by Angular signals. Can be created outside an injection context.

A2uiSurfaceStore Interface

interface A2uiSurfaceStore {
  /** Apply an A2UI message, updating surfaces reactively. */
  apply(message: A2uiMessage): void;
 
  /** Signal containing all current surfaces, keyed by surfaceId. */
  readonly surfaces: Signal<Map<string, A2uiSurface>>;
 
  /** Returns a computed signal for a single surface by ID. */
  surface(surfaceId: string): Signal<A2uiSurface | undefined>;
}

apply(message)

Processes one A2uiMessage and updates the internal surfaces signal. All four message types are handled:

Message typeBehavior
createSurfaceCreates a new A2uiSurface entry with an empty component map and data model
updateComponentsMerges the provided components into the surface's component map by id โ€” existing components are replaced, others are kept
updateDataModelApplies a JSON Pointer patch to the surface's data model (see below)
deleteSurfaceRemoves the surface from the map entirely

Messages for unknown surface IDs are silently ignored (except createSurface, which registers the surface).

surfaces

A readonly Signal<Map<string, A2uiSurface>> containing all active surfaces. Each map operation produces a new Map reference so that Angular's change detection triggers correctly.

surface(surfaceId)

Returns a computed signal for a single surface. The signal emits undefined until a createSurface message registers it, and undefined again after deleteSurface removes it.

const dashboard = store.surface('dashboard');
// dashboard() is A2uiSurface | undefined

Data Model and Render-Lib State Sync

The surface's dataModel is synchronized into the render-lib StateStore when surfaceToSpec() converts the surface to a spec. The conversion sets state: surface.dataModel on the produced Spec, which initializes the render-lib's internal StateStore with the surface data. When components with _bindings update values (e.g., a text field changing), those updates flow through the render-lib StateStore, and each mutation emits a RenderStateChangeEvent through the render-lib event system. This means consumers observing the events output on A2uiSurfaceComponent see all data model changes as typed RenderStateChangeEvent objects with path, value, and snapshot fields.

updateDataModel Semantics

The updateDataModel message uses JSON Pointer (RFC 6901) paths to address values in the data model.

pathvalueEffect
undefined or '/'ObjectReplaces the entire data model
/some/pathAny valueSets dataModel[some][path] to value
/some/pathundefinedDeletes the value at that path
// Replace entire model
{"updateDataModel": {"surfaceId": "s1", "value": {"name": "Alice", "score": 42}}}
 
// Set a single field
{"updateDataModel": {"surfaceId": "s1", "path": "/score", "value": 99}}
 
// Delete a field
{"updateDataModel": {"surfaceId": "s1", "path": "/score"}}

Usage with createA2uiMessageParser

The surface store is designed to work with createA2uiMessageParser, which parses raw JSONL chunks into typed A2uiMessage objects.

import { createA2uiSurfaceStore } from '@ngaf/chat';
import { createA2uiMessageParser } from '@ngaf/a2ui';
import { effect } from '@angular/core';
 
const store = createA2uiSurfaceStore();
const parser = createA2uiMessageParser();
 
// Feed raw JSONL chunks as they arrive from the stream
function onChunk(chunk: string): void {
  const messages = parser.push(chunk);
  for (const msg of messages) {
    store.apply(msg);
  }
}
 
// React to surface changes
effect(() => {
  const surface = store.surface('dashboard')();
  if (surface) {
    console.log('Components:', [...surface.components.keys()]);
    console.log('Data model:', surface.dataModel);
  }
});
JSONL envelope format

The parser expects each line to be wrapped in an envelope object: {"createSurface": {...}}, {"updateComponents": {...}}, etc. The envelope key determines the message type; its value is the message payload.

A2uiSurface Shape

Each surface stored in the map has the following structure:

interface A2uiSurface {
  surfaceId: string;
  catalogId: string;
  theme?: A2uiTheme;
  components: Map<string, A2uiComponent>;
  dataModel: Record<string, unknown>;
}

The components map is keyed by component ID. The dataModel is a plain object that components reference via JSON Pointer paths in their props.

What's Next