Audience: Low‑code builders (Dynamic Content Editor) and developers wiring dynamic actions / elements.
App composer components often need lightweight shared state for itself, boardlet to boardlet communication (app scope) or app to app communication(global). The Data Hub provides:
{{ someKey}} just works with local override semantics)ON_DATA_HUB_CHANGE so elements can react declaratively when relevant data changesUse it whenever you need transient cross‑action or cross‑element data that does not warrant persistence or a domain signal store.
| Situation | Use Data Hub? | Scope | Rationale |
|---|---|---|---|
| Store last clicked table row for the same widget or filters used on by one boardlet | Yes | Local | Highest precedence, isolated by Content Id |
| Share a filter across several widgets within one micro‑app | Yes | App | Limited blast radius; survives page navigation inside the app |
| Share a settings across two apps (navigate to shared dashboard in another app) | Yes | Global | Survive page navigation between app |
| Large dataset / pagination cache | No | — | Use dedicated data service / API caching |
| Long‑term preferences | No | — | Persist via backend or local storage layer |
Rule of thumb: Start as Local, promote to App only when two or more contents collaborate, promote to Global only if truly cross‑app.
Three maps exist simultaneously:
appKey)contentId)At render time they form the Dynamic Rendering Context:
context = {
...Global, ...App(appKey), ...Local(contentId), // Flattened (precedence Local > App > Global)
global: Global,
app: App(appKey),
local: Local(contentId),
contentId,
appKey,
// further helpers (environmentOrigin, etc.)
}
Therefore a key like selection can exist at all three scopes. selection resolves to the Local value if present; if not then selection will come from App and then finally from Global. Scope (local, app, global) value always remains accessible via <scope>.selection.
Chain Dynamic Actions:
| Action (Editor) | Scope Impact |
|---|---|
| Local Data Hub: Set Value | Mutates Local map for target contentId (calculated automatically) |
| App Data Hub: Set Value | Mutates App map for inferred appKey |
| Global Data Hub: Set Value | Mutates Global map |
Each Set action defines a Parameters Map (rows). For each row you specify: Target Key, Source Type, Value/Expression.
| Source Type | Example Input | Result |
|---|---|---|
| Literal | true |
boolean true |
Literal + {{variables}} |
Username {{global.currentUser.name}} |
Username John |
| Existing Value | New Value | Result |
|---|---|---|
| (absent) | any | Created |
| Plain object | Plain object | Shallow merge (new props overwrite) |
| Anything else | anything | Replacement |
Tip: To force a full replace of a nested object, first set it to null, then set the new object.
{{ selection.rowId }} or explicit {{ local.selection.rowId }}ON_DATA_HUB_CHANGE is an element event you can attach actions to. Behind the scenes each dynamic element subscribes to the Data Hub state if (and only if) it declares this event. On any change (global/app/local) the element receives an event payload shaped like:
{
"elementContext": "<the element's own context() snapshot>",
...local,
...app,
...global,
"local": { /* local scope map */ },
"app": { /* app scope map */ },
"global": { /* global scope map */ }
}
Use cases:
selection changes elsewhereBest Practices:
Row Click → Local Set (selection = JSON Interpolated: { "rowId":"{{clickedRow.id}}", "ts":"{{timestamp}}" })
Button Click chain:
selection)selection){{selection.rowId}})rowDetails = response)Input Change → App Set (activeFilter = user input). All boardlet reference activeFilter or app.activeFilter. Boardlet can also listen for On Data Hub Change
Add event ON_DATA_HUB_CHANGE to a panel element; first action is a Condition verifying selection changed; then API fetch details.
| Pattern | Guidance |
|---|---|
| Generic key reused widely | Add prefix (report.filters, tbl.selection) |
| Temporary chain scratch values | Prefix with underscore (_tempPayload) and keep Local |
On destroy event| Symptom | Cause | Fix |
|---|---|---|
| Blank interpolation | Key not set yet | Ensure Set runs first; default {{ myKey || '—' }} |
| Stale nested data | Shallow merge kept old props | Set to null then replace OR overwrite all fields |
| Unexpected override | Same key at Local/App | Namespace or explicit global. / app. access |
DynamicElementComponentV2 registers a watchState on the injected DataHub only if ON_DATA_HUB_CHANGE is configured, pushing merged maps (local/app/global) plus flattened keys to action invocations.DynamicEventTypes.ON_DATA_HUB_CHANGE (see dynamic-event-type.ts) and option with translate key DynamicActions.Shared.Events.ON_DATA_HUB_CHANGE (see dynamic-event-options.const.ts).interface DataHubChangePayload {
elementContext: Record<string, unknown>;
// flattened merged keys (local > app > global)
// plus explicit namespaces:
local: Record<string, unknown>;
app: Record<string, unknown>;
global: Record<string, unknown>;
// other flattened keys appear at top level
}
Pseudo:
for (const key of Object.keys(incoming)) {
const current = existing[key];
const nextVal = incoming[key];
if (current === undefined) {
existing[key] = nextVal;
} else if (isPlainObject(current) && isPlainObject(nextVal)) {
existing[key] = { ...current, ...nextVal };
} else {
existing[key] = nextVal;
}
}
distinctUntilChanged with deep equality (E1Utility.isEqual) throttles ON_DATA_HUB_CHANGE dispatches; still avoid high‑frequency churn (e.g. rapid typing) unless necessary.