Page History
(this page was created automatically. In case of formatting issues, please visit the official Wiki Page)
Audience: Low‑code builders (Dynamic Content Editor) and developers wiring dynamic actions / elements.
0. Why The Data Hub Exists
App composer components often need lightweight shared state for itself, boardlet to boardlet communication (app scope) or app to app communication(global). In earlier iterations we used "global parameters" (added to the URL / shared via route state) for cross‑element exchange. That approach has limits:
| Legacy Global Parameters Limitation | Impact |
|---|---|
| Mostly primitive / flat values | Hard to pass rich objects (forced JSON stringify hacks) |
| Coupled to URL (exposed) | Internal keys visible & bookmarkable unintentionally |
| Risk of leaking internal / sensitive names | Needed obfuscation / naming discipline |
| Hard to scope (effectively global only) | Accidental cross‑app collisions |
| Navigation side‑effects | Changing a value could dirty browser history |
Data Hub solves these by offering an in‑memory, non‑URL, structured store with three explicit scopes and predictable precedence.
The Data Hub provides:
- Zero‑config ephemeral state (in‑memory, auto‑clears on reload)
- Simple key/value API with three intention‑revealing scopes (Local → App → Global) to minimise accidental coupling
- Deterministic merge & precedence so templates stay concise (
<span v-pre>{{ someKey}}</span>just works with local override semantics) - Event reactivity through
ON_DATA_HUB_CHANGEso elements can react declaratively when relevant data changes - A uniform contract consumed by the Dynamic Rendering Context so all dynamic elements read state the same way
Use it whenever you need temporary data sharing between actions or elements—especially for richer values you do NOT want in the page URLtransient cross‑action or cross‑element data that does not warrant persistence or a domain signal store.
1. When To Use (Decision Guide)
| 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.
2. Core Mental Model
Three maps exist simultaneously:
- Global Map (singleton for the session)
- App Map (namespaced by current
appKey) - Local Map (namespaced by
contentId)
When something on the page runs, the system builds a usable "context" made from Global, then App, then Local data (Local beats App, App beats Global if the same key exists). So if selection exists in Local, that one shows; otherwise it falls back to App, then Global. You can still explicitly read each scope: local.selection, app.selection, global.selection.
Additional always‑present helper keys:
| Key | Description |
|---|---|
contentId | Current dynamic content instance id |
appKey | Current application key (micro‑app) |
global | Full global map |
app | App map for current appKey |
local | Local map for current 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.
3. How To Set Values (Action Editor)
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.
Supported Source Patterns
...
| Source Type | Example Input | What You Get | Result | |
|---|---|---|---|---|
| LiteralPlain text / number / boolean | true | boolean true | ||
Literal + \{\{variables\}\} | Username \{\ | Text with placeholders | User: {{global.currentUser.name\}\} | Replaced with actual user nameUsername John |
Merge Semantics (Per Key)
| Existing Value | New Value | Result |
|---|---|---|
| (absent) | any | Created |
| Plain object | Plain object | Shallow merge (new props overwrite) |
| Anything else | anything | Replacement |
Tip: Replacing an object but keeping stale nested fields? First set the key To force a full replace of a nested object, first set it to null, then set the new object.
4. Reading Values
- Direct in app composer components:
<span v-pre>{{ selection.rowId }}</span>or explicit<span v-pre>{{ local.selection.rowId }}</span> - Via Get actions inside an action chain when an intermediate action (e.g. API Invoke) needs the value.
Prefer direct template access; use Get sparingly to keep chains lean.
5. Reactivity With ON_DATA_HUB_CHANGE
ON_DATA_HUB_CHANGE lets an element automatically fire an action chain whenever any Data Hub value changes (Local, App, or Global). Add it only where you need automatic reactions.
5.1 What Triggers It?
Whenever a Set/Remove/Clear action actually changes a value (not just writing the same thing again).
5.2 Payload Structure
Inside the chain you can directly use the same placeholders as in templates. All flattened keys are available (with Local taking precedence). You can still reach the exact scope using local.<key>, app.<key>, or global.<key>.
...
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:
- Auto refresh detail panel when
selectionchanges elsewhere - Trigger conditional API pre‑fetch prefetch when a prerequisite key appears (e.g.
rowDetailsafterselectionset) - Cascade enrichment: selection → fetch → store → label updates
5.4 Filtering Inside the Chain
Because the event fires for ANY change, always gate expensive logic:
- Add a Condition action checking the key(s) you care about.
- (Optional) Maintain a previous snapshot locally: store
lastProcessedSelectionand compare.
Example condition idea: Only continue if the row actually changed. You could store lastProcessedSelection locally and compare {{ selection.rowId !== (lastProcessedSelection?.rowId) }}; then update lastProcessedSelection at the end.
5.5 Performance Considerations
- Namespacing keys keeps unrelated changes from touching objects you depend on.
- Avoid putting everything in one huge object (split into smaller keys like
selection,selectionMeta).
5.6 When NOT To Use The Event
Best Practices:
- Scope your follow‑up actions: check for the specific key change (e.g. by storing a hash or using a Condition action) to avoid redundant work.
- Avoid chaining expensive API calls on every minor unrelated key update; consider isolating keys by namespacing.Display‑only: if a component just shows context values you usually do not need
ON_DATA_HUB_CHANGE; the rendering system already re‑supplies updated context. Use the event only for side effects (API calls, further Set actions, navigation, dialogs, etc.).
6. Practical Recipes
6.1 Store a Table Selection (Local)
Row Click → Local Set (selection = JSON Interpolated: { "rowId":"<span v-pre>{{clickedRow.id}}</span>", "ts":"<span v-pre>{{timestamp}}</span>" })
6.2 Use Selection In Button API Call
Button Click chain:
- Local Get (
selection) - Check context change (
selection) - API Invoke (body includes
<span v-pre>{{selection.rowId}}</span>) - Local Set (
rowDetails= response)
6.3 Shared Filter Across Widgets
Input Change → App Set (activeFilter = user input). All boardlet reference activeFilter or app.activeFilter. Boardlet can also listen for On Data Hub Change
6.4 React To Data Changes
Add event ON_DATA_HUB_CHANGE to a panel element; first action checks if selection actually changed; next action fetches details and stores them.
6.5 DynamicLabel Examples
A. Show A Value Stored In Data Hub
- A table row click sets Local key
currentRow={ "id": "{{clickedRow.id}}", "name": "{{clickedRow.name}}" }. - In the label text field use:
Selected: {{currentRow.name}}. - The label updates immediately after the Set action (no extra event needed).
Result: Label updates automatically after the Set action without needing ON_DATA_HUB_CHANGE.
B. Prevent Flicker With Progressive Loading
- Set placeholder: Local Set
currentRow={ "name": "Loading..." }. - Run API action.
- On success Set
currentRowagain with real data. - Label swaps from placeholder to real content.
C. Access Scoped Values Explicitly
If both global.currentUser and Local currentUser exist: Welcome {{currentUser.name || global.currentUser.name}} ensures Local override but falls back gracefully.
6.6 Patterns For Other Components
All other dynamic components work the same: use {{ someKey }} (flattened), or explicitly {{ app.filterX }}, {{ global.tenant.id }}.
is a Condition verifying selection changed; then API fetch details.
7. Naming & Collision Strategy
| Pattern | Guidance |
|---|---|
| Generic key reused widely | Add prefix (appNameShortcutreport.filters, appNameShortcuttbl.selection) |
| Temporary chain scratch values | Prefix with underscore (_tempPayload) and keep Local |
8. Cleaning / Resetting
- Provide a Clear Local Data
On destroyevent - Navigation: optionally clear App keys via dedicated Clear action (if available) or set them to neutral defaults
- To purge multiple nested leftovers, replace parent object instead of merging
9. Troubleshooting Quick Table
| Symptom | Cause | Fix |
|---|---|---|
| Blank interpolation | Key not set yet | Ensure Set runs first; default <span v-pre>{{ myKey || '—' }}</span> |
| 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 |
| Label not updating | Using custom text set via component action only once | Ensure you update via Set Value or re‑invoke component action; or rely on context interpolation instead |
10. Technical Reference (For Developers)
10.1 Precedence & Event Wiring
- Merge order inside context(): Global < App < Local.
DynamicElementComponentV2registers awatchStateon the injectedDataHubonly ifON_DATA_HUB_CHANGE
...
10. Quick Reminders (No‑Code Friendly)
...
- is configured, pushing merged maps (local/app/global) plus flattened keys to action invocations.
- Event enumeration:
DynamicEventTypes.ON_DATA_HUB_CHANGE(seedynamic-event-type.ts) and option with translate keyDynamicActions.Shared.Events.
...
ON_DATA_HUB_CHANGE
...
- (see
dynamic-event-options.const.ts).
10.2 Event Payload Shape (Simplified)
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
}
10.3 Merge Algorithm (Set Value Actions)
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;
}
}
10.4 Performance Notes
- Shallow object merge keeps cost low; avoid nesting large mutable graphs under one key.
distinctUntilChangedwith deep equality (E1Utility.isEqual) throttlesON_DATA_HUB_CHANGEdispatches; still avoid high‑frequency churn (e.g. rapid typing) unless necessary
...
- .