ngx-json-render 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/fesm2022/ngx-json-render.mjs +54 -3
- package/fesm2022/ngx-json-render.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ngx-json-render.d.ts +16 -1
package/README.md
CHANGED
|
@@ -594,6 +594,18 @@ const bound = injectBoundProp<string>(() => ctx.props().value, () => ctx.binding
|
|
|
594
594
|
|
|
595
595
|
Share one store across renderers (or drive it from your own state management) by passing a core `StateStore` — `createStateStore()`, or `createStoreAdapter()` over Redux/NgRx/etc. — via the `store` input. `createStoreSetState(store)` adapts a whole-state updater to fine-grained path writes.
|
|
596
596
|
|
|
597
|
+
### What a state write re-renders
|
|
598
|
+
|
|
599
|
+
A write reaches only the components whose resolved props actually changed. Writing `/user/name` re-runs the template of the text that shows it, not the rest of the tree. So `ctx.props()` and `ctx.element()` are the signals to read: a template that reads anything else, such as a mutable object or `Date.now()`, is no longer refreshed by unrelated writes.
|
|
600
|
+
|
|
601
|
+
"Changed" is decided per prop value:
|
|
602
|
+
|
|
603
|
+
- Primitives compare by value.
|
|
604
|
+
- Objects and arrays compare by reference with the built-in store, which copies every path it writes.
|
|
605
|
+
- With an external `store`, objects and arrays always count as changed, because such a store may write into its snapshot in place.
|
|
606
|
+
|
|
607
|
+
Elements with a `$bindState` or `$bindItem` prop still re-run on every write. Their DOM can hold the user's input before state does, and the note below depends on them getting the chance to put it right.
|
|
608
|
+
|
|
597
609
|
### A note on inputs
|
|
598
610
|
|
|
599
611
|
If a catalog component renders `<input [value]="ctx.props().value">`, remember that one-way bindings do not re-assert the DOM when the bound value returns to its previously applied value while the user typed in between (e.g. `pushState` + `clearStatePath`). Sync imperatively instead — see `InputComponent` in the demo app for the pattern.
|
|
@@ -1091,6 +1091,32 @@ function repeatsItself(path) {
|
|
|
1091
1091
|
}
|
|
1092
1092
|
return false;
|
|
1093
1093
|
}
|
|
1094
|
+
/**
|
|
1095
|
+
* Whether two objects hold the same values under the same keys.
|
|
1096
|
+
*
|
|
1097
|
+
* `objectsByIdentity` decides what the same object or array reference means.
|
|
1098
|
+
* The internal store copies every path it writes and never touches a snapshot
|
|
1099
|
+
* it has handed out, so there the same reference is the same content. An
|
|
1100
|
+
* external store may write into its snapshot in place, so there only
|
|
1101
|
+
* primitives can be trusted to compare equal, and any object value counts as
|
|
1102
|
+
* changed.
|
|
1103
|
+
*/
|
|
1104
|
+
function sameValues(a, b, objectsByIdentity) {
|
|
1105
|
+
const keys = Object.keys(a);
|
|
1106
|
+
if (keys.length !== Object.keys(b).length)
|
|
1107
|
+
return false;
|
|
1108
|
+
for (const key of keys) {
|
|
1109
|
+
if (!Object.hasOwn(b, key))
|
|
1110
|
+
return false;
|
|
1111
|
+
const value = a[key];
|
|
1112
|
+
if (!Object.is(value, b[key]))
|
|
1113
|
+
return false;
|
|
1114
|
+
if (!objectsByIdentity && typeof value === 'object' && value !== null) {
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return true;
|
|
1119
|
+
}
|
|
1094
1120
|
/** The chain from the root down to `path`, as a line a human can follow. */
|
|
1095
1121
|
function describePath(path) {
|
|
1096
1122
|
const keys = [];
|
|
@@ -1138,7 +1164,22 @@ class JrElement {
|
|
|
1138
1164
|
return true;
|
|
1139
1165
|
return evaluateVisibility(el.visible, this.resolutionCtx());
|
|
1140
1166
|
}, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
1141
|
-
/**
|
|
1167
|
+
/**
|
|
1168
|
+
* The element with all prop expressions resolved.
|
|
1169
|
+
*
|
|
1170
|
+
* Resolving builds a new props object on every state write, whatever the
|
|
1171
|
+
* write touched. Handing that on as a change would re-run every catalog
|
|
1172
|
+
* template in the tree for one keystroke, so a result that holds the same
|
|
1173
|
+
* values as the last one counts as unchanged and keeps the old object.
|
|
1174
|
+
*
|
|
1175
|
+
* Two-way bound elements are the exception, and keep treating every
|
|
1176
|
+
* resolution as a change. Their component writes the user's input into the
|
|
1177
|
+
* DOM before it reaches state, so the DOM can differ from a prop whose
|
|
1178
|
+
* value never did: typing and a `clearStatePath` in the same turn take
|
|
1179
|
+
* the prop from '' through 'milk' back to '' with nobody reading 'milk'.
|
|
1180
|
+
* The README tells catalog authors to sync such inputs with an effect on
|
|
1181
|
+
* props, and that effect only gets to put the DOM right if it runs.
|
|
1182
|
+
*/
|
|
1142
1183
|
resolvedElement = computed(() => {
|
|
1143
1184
|
const el = this.rawElement();
|
|
1144
1185
|
if (!el)
|
|
@@ -1147,14 +1188,24 @@ class JrElement {
|
|
|
1147
1188
|
...el,
|
|
1148
1189
|
props: resolveElementProps(el.props ?? {}, this.resolutionCtx()),
|
|
1149
1190
|
};
|
|
1150
|
-
}, ...(ngDevMode ?
|
|
1191
|
+
}, { ...(ngDevMode ? { debugName: "resolvedElement" } : /* istanbul ignore next */ {}), equal: (a, b) => {
|
|
1192
|
+
if (a === b)
|
|
1193
|
+
return true;
|
|
1194
|
+
if (!a || !b || untracked(this.bindings))
|
|
1195
|
+
return false;
|
|
1196
|
+
const { props: aProps, ...aRest } = a;
|
|
1197
|
+
const { props: bProps, ...bRest } = b;
|
|
1198
|
+
// Everything but props is the spec element itself, never state.
|
|
1199
|
+
return (sameValues(aRest, bRest, true) &&
|
|
1200
|
+
sameValues(aProps, bProps, !untracked(this.root.store)));
|
|
1201
|
+
} });
|
|
1151
1202
|
/** Two-way binding paths ($bindState / $bindItem) by prop name. */
|
|
1152
1203
|
bindings = computed(() => {
|
|
1153
1204
|
const el = this.rawElement();
|
|
1154
1205
|
return el
|
|
1155
1206
|
? resolveBindings(el.props ?? {}, this.resolutionCtx())
|
|
1156
1207
|
: undefined;
|
|
1157
|
-
}, ...(ngDevMode ?
|
|
1208
|
+
}, { ...(ngDevMode ? { debugName: "bindings" } : /* istanbul ignore next */ {}), equal: (a, b) => a === b || (!!a && !!b && sameValues(a, b, true)) });
|
|
1158
1209
|
entry = computed(() => {
|
|
1159
1210
|
const el = this.rawElement();
|
|
1160
1211
|
return el ? this.root.resolveEntry(el.type) : undefined;
|