ngx-devextreme-zoneless 2.0.0 → 3.0.0
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 +85 -346
- package/fesm2022/ngx-devextreme-zoneless-core.mjs +397 -37
- package/fesm2022/ngx-devextreme-zoneless-core.mjs.map +1 -1
- package/fesm2022/ngx-devextreme-zoneless-data.mjs +340 -148
- package/fesm2022/ngx-devextreme-zoneless-data.mjs.map +1 -1
- package/fesm2022/ngx-devextreme-zoneless-editors.mjs +57 -57
- package/fesm2022/ngx-devextreme-zoneless-forms.mjs +408 -0
- package/fesm2022/ngx-devextreme-zoneless-forms.mjs.map +1 -0
- package/fesm2022/ngx-devextreme-zoneless-navigation.mjs +18 -18
- package/fesm2022/ngx-devextreme-zoneless-overlays.mjs +18 -18
- package/fesm2022/ngx-devextreme-zoneless-visualization.mjs +47 -33
- package/fesm2022/ngx-devextreme-zoneless-visualization.mjs.map +1 -1
- package/package.json +13 -3
- package/types/ngx-devextreme-zoneless-core.d.ts +139 -2
- package/types/ngx-devextreme-zoneless-core.d.ts.map +1 -1
- package/types/ngx-devextreme-zoneless-data.d.ts +151 -43
- package/types/ngx-devextreme-zoneless-data.d.ts.map +1 -1
- package/types/ngx-devextreme-zoneless-forms.d.ts +203 -0
- package/types/ngx-devextreme-zoneless-forms.d.ts.map +1 -0
- package/types/ngx-devextreme-zoneless-visualization.d.ts +38 -15
- package/types/ngx-devextreme-zoneless-visualization.d.ts.map +1 -1
- package/types/ngx-devextreme-zoneless.d.ts +1 -1
- package/types/ngx-devextreme-zoneless.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[](LICENSE)
|
|
4
4
|
[](https://context7.com/caffeinatedcoder/ngx-devextreme-zoneless)
|
|
5
|
-
[](https://www.npmjs.com/package/ngx-devextreme-zoneless)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
> **Independent community project.** DevExtreme is a registered trademark of
|
|
8
|
+
> Developer Express Inc. This library is not affiliated with, endorsed by, or
|
|
9
|
+
> sponsored by Developer Express Inc.
|
|
10
10
|
|
|
11
11
|
Strongly-typed **signal & zoneless change-detection adapters** for
|
|
12
12
|
[DevExtreme Angular](https://js.devexpress.com/Angular/) components.
|
|
@@ -24,16 +24,18 @@ Angular signal ──effect──▶ widget option / method
|
|
|
24
24
|
Angular signal ◀──set──── widget event (optionChanged, selectionChanged, …)
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
- **Angular** ≥
|
|
28
|
-
- **DevExtreme / devextreme-angular** ≥
|
|
27
|
+
- **Angular** ≥ 22.1 < 23 (built and verified against Angular 22.1.3, zoneless by default)
|
|
28
|
+
- **DevExtreme / devextreme-angular** ≥ 26.1 (built and verified against 26.1.4)
|
|
29
29
|
- **TypeScript** 6.0, `strict`, zero `any` in the public surface
|
|
30
30
|
|
|
31
|
-
> **Why this library exists:** DevExpress officially still requires zone.js
|
|
32
|
-
>
|
|
33
|
-
> ETA
|
|
34
|
-
>
|
|
35
|
-
>
|
|
36
|
-
>
|
|
31
|
+
> **Why this library exists:** DevExpress officially still requires zone.js.
|
|
32
|
+
> As of 2026-08-10 DevExpress is *"considering zoneless Angular support for a
|
|
33
|
+
> future release"* (ticket T1328486) — no ETA, and that thread is where an
|
|
34
|
+
> invite to test will be announced if a release is set. Running DevExtreme
|
|
35
|
+
> zoneless is therefore outside the vendor-supported configuration, and this
|
|
36
|
+
> library is the bridge. Its CI (a Playwright suite driving every binding
|
|
37
|
+
> through a real zoneless app on every push) is the compatibility net the
|
|
38
|
+
> vendor does not provide.
|
|
37
39
|
|
|
38
40
|
Everything is *derived from DevExtreme's own `Properties` declarations* via
|
|
39
41
|
template-literal and conditional types — option names, option value types and
|
|
@@ -78,315 +80,62 @@ Or import everything at once with `DX_SIGNAL_DIRECTIVES` (also available per
|
|
|
78
80
|
family: `DX_VALUE_DIRECTIVES`, `DX_NAVIGATION_DIRECTIVES`,
|
|
79
81
|
`DX_VISIBLE_DIRECTIVES`, `DX_DATA_DIRECTIVES`, `DX_VISUALIZATION_DIRECTIVES`).
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
### Grid state (dx-data-grid and dx-tree-list)
|
|
126
|
-
|
|
127
|
-
| Selector | Model |
|
|
128
|
-
|---|---|
|
|
129
|
-
| `[dxFocusedRowKey]` | `TKey \| null` (requires `focusedRowEnabled`; `null` clears) |
|
|
130
|
-
| `[dxFilterValue]` | `DxFilterExpression \| null` |
|
|
131
|
-
| `[dxPageIndex]`, `[dxPageSize]` | `number` (method-based — paging lives under the nested `paging` option) |
|
|
132
|
-
| `[dxGridState]` | `DxGridState \| null \| undefined` — the full layout state (`state()` / `stateStoring`) |
|
|
133
|
-
|
|
134
|
-
`[(dxGridState)]` is the layout-persistence story: bind it to a signal you
|
|
135
|
-
persist wherever you like. `undefined` pulls the current state on attach,
|
|
136
|
-
a saved state object restores the layout, `null` resets to defaults. Widget →
|
|
137
|
-
signal reads are debounced (`dxGridStateDebounce`, default 100 ms).
|
|
138
|
-
|
|
139
|
-
### Navigation & overlays
|
|
140
|
-
|
|
141
|
-
| Selector | Model |
|
|
142
|
-
|---|---|
|
|
143
|
-
| `dx-tabs`, `dx-tab-panel`, `dx-accordion`, `dx-gallery` + `[dxSelectedIndex]` | `number` |
|
|
144
|
-
| `dx-drawer[dxOpened]` | `boolean` |
|
|
145
|
-
| `dx-popup`, `dx-popover`, `dx-tooltip`, `dx-toast`, `dx-load-panel`, `dx-action-sheet` + `[dxVisible]` | `boolean` |
|
|
146
|
-
|
|
147
|
-
`[(dxVisible)]` tracks *every* way an overlay can close (shading click, escape,
|
|
148
|
-
close button, API) — the classic zoneless pain point.
|
|
149
|
-
|
|
150
|
-
### Visualization
|
|
151
|
-
|
|
152
|
-
| Selector | Model |
|
|
153
|
-
|---|---|
|
|
154
|
-
| `dx-chart[dxSelectedPoints]`, `dx-pie-chart[dxSelectedPoints]` | `readonly DxChartSelectedPoint[]` |
|
|
155
|
-
|
|
156
|
-
Chart selection is method-based; selected points are exposed as serializable
|
|
157
|
-
`{ seriesName, argument, value }` descriptors that can be stored and restored.
|
|
158
|
-
DevExtreme charts do not select points on click by themselves (their demos
|
|
159
|
-
wire `onPointClick: e => e.target.select()` manually), so these directives
|
|
160
|
-
toggle the clicked point's selection by default — opt out with
|
|
161
|
-
`[dxPointClickToggle]="false"` if you want to handle `onPointClick` yourself.
|
|
162
|
-
|
|
163
|
-
## Composition API
|
|
164
|
-
|
|
165
|
-
The directives are sugar. The same engine is available as injection-context
|
|
166
|
-
functions and covers **every option and event of every DevExtreme component**,
|
|
167
|
-
fully typed:
|
|
168
|
-
|
|
169
|
-
```ts
|
|
170
|
-
import {
|
|
171
|
-
bindDxOption, dxOptionSignal, dxEventSignal, injectDxInstance,
|
|
172
|
-
} from 'ngx-devextreme-zoneless';
|
|
173
|
-
|
|
174
|
-
@Component({ /* ... */ })
|
|
175
|
-
export class MyComponent {
|
|
176
|
-
// On the same element (inside a directive):
|
|
177
|
-
private readonly textBox = inject(DxTextBoxComponent, { self: true });
|
|
178
|
-
|
|
179
|
-
// Two-way signal for any option — names & types from DevExtreme Properties:
|
|
180
|
-
readonly placeholder = dxOptionSignal(this.textBox, 'placeholder');
|
|
181
|
-
readonly mode = dxOptionSignal(this.textBox, 'mode', { initialValue: 'search' });
|
|
182
|
-
|
|
183
|
-
// Latest payload of any widget event:
|
|
184
|
-
readonly focusOut = dxEventSignal(this.textBox, 'focusOut');
|
|
185
|
-
|
|
186
|
-
// Works with view children too (pass the viewChild signal itself):
|
|
187
|
-
private readonly grid = viewChild(DxDataGridComponent<Order, number>);
|
|
188
|
-
readonly filter = dxOptionSignal(this.grid, 'filterValue');
|
|
189
|
-
|
|
190
|
-
// Bind an existing signal to an option:
|
|
191
|
-
readonly keys = signal<number[] | undefined>([]);
|
|
192
|
-
constructor() {
|
|
193
|
-
bindDxOption(this.grid, 'selectedRowKeys', this.keys);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// The raw widget instance as a signal (undefined until created):
|
|
197
|
-
readonly widget = injectDxInstance(DxTextBoxComponent);
|
|
198
|
-
|
|
199
|
-
// Same, but for a view child — resets to undefined while the widget is
|
|
200
|
-
// gone and follows re-creation:
|
|
201
|
-
readonly gridWidget = dxInstanceSignal(this.grid);
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### Widget re-creation
|
|
206
|
-
|
|
207
|
-
Composition-API bindings on a **getter host reference** (a `viewChild(...)`
|
|
208
|
-
signal) survive widget re-creation: when an `@if`, a tab panel or re-created
|
|
209
|
-
popup content destroys the widget and later creates a new one, the binding
|
|
210
|
-
re-attaches automatically and the initial synchronization runs again — a
|
|
211
|
-
signal holding state therefore **restores it into the new widget**. The
|
|
212
|
-
attribute directives don't need any of this; they die and are re-created with
|
|
213
|
-
their element.
|
|
214
|
-
|
|
215
|
-
`onDxWidgetReady` is the underlying primitive: it fires once per widget
|
|
216
|
-
instance, and its callback may return a cleanup that runs when the widget
|
|
217
|
-
goes away.
|
|
218
|
-
|
|
219
|
-
### Components without a dedicated directive
|
|
220
|
-
|
|
221
|
-
The coverage model is simple: **a directive exists wherever a widget has
|
|
222
|
-
genuine user-mutable two-way state** (value, selection, visibility, index,
|
|
223
|
-
current date/view, form data, chart points). Every other component — and any
|
|
224
|
-
component DevExtreme ships in the future — is still fully supported through
|
|
225
|
-
the composition API, because `dxOptionSignal`, `dxEventSignal` and
|
|
226
|
-
`bindDxOption` are not written against a component list: their option names,
|
|
227
|
-
option value types and event payloads are computed from the `Properties`
|
|
228
|
-
declaration of whatever host component you hand them. Support doesn't
|
|
229
|
-
cliff-edge at the directive catalog; it just gets one notch less sugary.
|
|
230
|
-
|
|
231
|
-
A few real-world cases:
|
|
232
|
-
|
|
233
|
-
```ts
|
|
234
|
-
// PivotGrid — no two-way options (its mutable state lives in the
|
|
235
|
-
// PivotGridDataSource), but every event and option is reachable, typed:
|
|
236
|
-
private readonly pivot = inject(DxPivotGridComponent, { self: true });
|
|
237
|
-
readonly cellClick = dxEventSignal(this.pivot, 'cellClick');
|
|
238
|
-
readonly contextMenu = dxEventSignal(this.pivot, 'contextMenuPreparing');
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
```ts
|
|
242
|
-
// Scheduler beyond [(dxCurrentDate)] / [(dxCurrentView)] — appointment
|
|
243
|
-
// lifecycle as signals:
|
|
244
|
-
private readonly scheduler = inject(DxSchedulerComponent, { self: true });
|
|
245
|
-
readonly appointmentAdded = dxEventSignal(this.scheduler, 'appointmentAdded');
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
```ts
|
|
249
|
-
// Sortable (e.g. a Kanban board built from dx-sortable + lists) — purely
|
|
250
|
-
// event-driven, so state stays in your signals and events drive mutations:
|
|
251
|
-
private readonly sortable = viewChild(DxSortableComponent);
|
|
252
|
-
readonly reorder = dxEventSignal(this.sortable, 'reorder');
|
|
253
|
-
constructor() {
|
|
254
|
-
effect(() => {
|
|
255
|
-
const e = this.reorder();
|
|
256
|
-
if (e) this.cards.update((cards) => moveItem(cards, e.fromIndex, e.toIndex));
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
```ts
|
|
262
|
-
// Any option of any widget, two-way — e.g. Menu, ButtonGroup, Toolbar, ...:
|
|
263
|
-
readonly disabled = dxOptionSignal(this.menu, 'disabled');
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
For state that is neither an option nor a single event (method-based APIs),
|
|
267
|
-
implement a `DxBindingAdapter` and pass it to `bindDxState` — that is exactly
|
|
268
|
-
how the chart-selection and tree-view directives are built. And if you want
|
|
269
|
-
template sugar for a component we don't cover, a custom directive is ~10
|
|
270
|
-
lines by extending `DxValueDirectiveBase`, `DxVisibleDirectiveBase`, etc.
|
|
271
|
-
|
|
272
|
-
## Data loading (signals → DataSource)
|
|
273
|
-
|
|
274
|
-
Two-way state is one half of the zoneless problem; the other half is data
|
|
275
|
-
loading — DevExtreme components load through `DataSource`/stores, which know
|
|
276
|
-
nothing about signals.
|
|
277
|
-
|
|
278
|
-
**Array data in a signal** — `dxArrayStoreSource` wraps a
|
|
279
|
-
`Signal<readonly T[]>` in a `DataSource` that reloads whenever the signal
|
|
280
|
-
changes:
|
|
281
|
-
|
|
282
|
-
```ts
|
|
283
|
-
readonly orders = signal<readonly Order[]>([]);
|
|
284
|
-
readonly ordersSource = dxArrayStoreSource(this.orders, { key: 'id' });
|
|
285
|
-
// <dx-data-grid [dataSource]="ordersSource" />
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
**Remote data with signal-driven filters** — `dxReloadOn` triggers a reload
|
|
289
|
-
whenever dependency signals change, so external filter state needs no
|
|
290
|
-
hand-written `refresh()` plumbing:
|
|
291
|
-
|
|
292
|
-
```ts
|
|
293
|
-
readonly search = signal('');
|
|
294
|
-
|
|
295
|
-
// e.g. a DevExtreme.AspNet.Data endpoint:
|
|
296
|
-
readonly source = new DataSource({
|
|
297
|
-
store: createStore({ loadUrl: '/api/orders', key: 'id' }),
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
constructor() {
|
|
301
|
-
dxReloadOn(this.source, [this.search]);
|
|
302
|
-
}
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
The target can also be a callback (`dxReloadOn(() => this.grid()?.refresh(), [deps])`)
|
|
306
|
-
for anything that is not a `DataSource`.
|
|
307
|
-
|
|
308
|
-
**Recipe: full remote loading state as signals.** Pair a `CustomStore` with
|
|
309
|
-
signals for loading/error state — the store reads filters `untracked`, and
|
|
310
|
-
`dxReloadOn` drives reloads:
|
|
311
|
-
|
|
312
|
-
```ts
|
|
313
|
-
readonly search = signal('');
|
|
314
|
-
readonly isLoading = signal(false);
|
|
315
|
-
readonly loadError = signal<unknown>(undefined);
|
|
316
|
-
|
|
317
|
-
readonly source = new DataSource<Order, number>({
|
|
318
|
-
store: new CustomStore<Order, number>({
|
|
319
|
-
key: 'id',
|
|
320
|
-
loadMode: 'raw',
|
|
321
|
-
load: async () => {
|
|
322
|
-
this.isLoading.set(true);
|
|
323
|
-
try {
|
|
324
|
-
const query = untracked(this.search);
|
|
325
|
-
return await fetchOrders(query);
|
|
326
|
-
} catch (error) {
|
|
327
|
-
this.loadError.set(error);
|
|
328
|
-
throw error;
|
|
329
|
-
} finally {
|
|
330
|
-
this.isLoading.set(false);
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
}),
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
constructor() {
|
|
337
|
-
dxReloadOn(this.source, [this.search]);
|
|
338
|
-
}
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
This intentionally ships as a recipe rather than API: the shape will be
|
|
342
|
-
extracted from real consumer screens before it hardens into code.
|
|
343
|
-
|
|
344
|
-
## Semantics
|
|
345
|
-
|
|
346
|
-
- **Initial sync** (`initialSync`, default `'auto'`): the signal's value is
|
|
347
|
-
pushed into the widget on attach; if it is `undefined`, the widget's current
|
|
348
|
-
value is pulled into the signal instead.
|
|
349
|
-
- **`undefined` vs `null`**: `undefined` means *absent* — a signal holding
|
|
350
|
-
`undefined` is never pushed into the widget (that is what makes `'auto'`
|
|
351
|
-
initial sync and late-created widgets work). To *clear* an option through a
|
|
352
|
-
binding, use `null`: `status.set(null)` clears a select box,
|
|
353
|
-
`status.set(undefined)` does nothing. This mirrors DevExtreme's own
|
|
354
|
-
conventions — its clearable editors write `null`, not `undefined`.
|
|
355
|
-
- **Debounced typing**: DevExtreme text editors update their `value` option on
|
|
356
|
-
*blur* by default. Pair `[(dxValue)]` + `dxValueDebounce` with
|
|
357
|
-
`valueChangeEvent="input"` on the editor to get live per-keystroke updates:
|
|
358
|
-
|
|
359
|
-
```html
|
|
360
|
-
<dx-text-box [(dxValue)]="search" [dxValueDebounce]="300" valueChangeEvent="input" />
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
Without it, the binding appears to only fire on blur — that is the widget's
|
|
364
|
-
event timing, not the binding's.
|
|
365
|
-
- **Echo suppression**: updates only propagate when the value *structurally*
|
|
366
|
-
changed (`Date`-aware, deep for arrays/plain objects). Override per binding
|
|
367
|
-
via `equal` / `dxValueEqual`.
|
|
368
|
-
- **Debounce** applies to the widget → signal direction only, so typing into a
|
|
369
|
-
`dx-text-box` doesn't storm your `computed()` graph.
|
|
370
|
-
- **Teardown** is automatic via `DestroyRef` — event handlers are detached and
|
|
371
|
-
effects destroyed when the directive/component dies.
|
|
372
|
-
- **Re-attach**: with a getter host reference the binding follows widget
|
|
373
|
-
re-creation (see [Widget re-creation](#widget-re-creation)); per-widget
|
|
374
|
-
resources are disposed on each detach.
|
|
375
|
-
- **Zoneless**: no `NgZone`, no `markForCheck` — signal writes are the change
|
|
376
|
-
notification.
|
|
83
|
+
## Choosing your binding style
|
|
84
|
+
|
|
85
|
+
Three ways to put a value on a DevExtreme editor — the classic one, and the
|
|
86
|
+
two this package opens up:
|
|
87
|
+
|
|
88
|
+
| | A: zone.js + Reactive Forms | B: `[(dxValue)]` | C: `[formField]` + `[dxFieldState]` |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| Change detection | zone.js (opt-in since v21) | zoneless, native | zoneless, native |
|
|
91
|
+
| Vendor support | ✅ the supported config | outside support — netted by this repo's CI | same net |
|
|
92
|
+
| Error display | hand-rolled per field/rule | DIY | the widget's own UI, zero template markup |
|
|
93
|
+
| Schema-driven readonly/hidden/disabled | manual | — | yes, all three |
|
|
94
|
+
| Value type safety | typed forms | end-to-end compile error | restored via `[dxFieldState]` |
|
|
95
|
+
| Boilerplate per field | ~5–8 template lines | ~1 line | ~2 lines, flat |
|
|
96
|
+
|
|
97
|
+
Use **B** for state outside a form (filters, toolbars, settings), **C** for
|
|
98
|
+
forms, and **A** only where zone.js is staying anyway. Full code for all
|
|
99
|
+
three, the trade-offs, and the two rules that keep them honest:
|
|
100
|
+
**[Choosing your binding style](https://github.com/CaffeinatedCoder/ngx-devextreme-zoneless/blob/main/docs/binding-styles.md)**.
|
|
101
|
+
The smoke app renders them side by side (`npm run smoke` →
|
|
102
|
+
http://localhost:4299/compare) — including the classic syntax's measured
|
|
103
|
+
zoneless behaviour: field state arrives (Angular's reactive directives mark
|
|
104
|
+
for check on their own); the widget state a CVA never carries is what stays
|
|
105
|
+
invisible, and is what the rest of this package covers.
|
|
106
|
+
|
|
107
|
+
## What is covered
|
|
108
|
+
|
|
109
|
+
Two-way directives exist wherever a widget has genuine user-mutable state —
|
|
110
|
+
`[(dxValue)]` on every editor, selection and grid state on the data widgets,
|
|
111
|
+
`[(dxVisible)]`/`[(dxOpened)]`/`[(dxSelectedIndex)]` on overlays and
|
|
112
|
+
navigation, chart point selection — each typed to the widget's real value
|
|
113
|
+
type. The
|
|
114
|
+
**[directive catalog](https://github.com/CaffeinatedCoder/ngx-devextreme-zoneless/blob/main/docs/directives.md)**
|
|
115
|
+
is the authoritative table. Components without a dedicated directive
|
|
116
|
+
(PivotGrid, Sortable, Menu, …) are fully covered by the
|
|
117
|
+
**[composition API](https://github.com/CaffeinatedCoder/ngx-devextreme-zoneless/blob/main/docs/composition-api.md)**:
|
|
118
|
+
`dxOptionSignal`, `dxEventSignal` and `bindDxOption` work with **any** option
|
|
119
|
+
and event of **any** DevExtreme component, fully typed.
|
|
120
|
+
|
|
121
|
+
For data, `dxArrayStoreSource` puts a signal-held array behind a `DataSource`,
|
|
122
|
+
and `dxRemoteStore` wraps a server load function in `resource()` vocabulary —
|
|
123
|
+
loading/error signals, cancellation, signal-driven dependencies — speaking the
|
|
124
|
+
complete DevExtreme.AspNet.Data loadOptions wire protocol through your own
|
|
125
|
+
`fetch`:
|
|
126
|
+
**[data loading and remote data](https://github.com/CaffeinatedCoder/ngx-devextreme-zoneless/blob/main/docs/remote-data.md)**.
|
|
377
127
|
|
|
378
128
|
## Entry points & tree-shaking
|
|
379
129
|
|
|
380
|
-
The package ships per-family secondary entry points:
|
|
381
|
-
|
|
382
130
|
```
|
|
383
131
|
ngx-devextreme-zoneless → everything (re-exports the below)
|
|
384
|
-
ngx-devextreme-zoneless/core → engine, types, bases, data-source helpers
|
|
132
|
+
ngx-devextreme-zoneless/core → engine, types, bases, data-source/remote helpers
|
|
385
133
|
ngx-devextreme-zoneless/editors → [(dxValue)] directives
|
|
386
134
|
ngx-devextreme-zoneless/navigation → [(dxSelectedIndex)], [(dxOpened)], …
|
|
387
135
|
ngx-devextreme-zoneless/overlays → [(dxVisible)] directives
|
|
388
136
|
ngx-devextreme-zoneless/data → grid/form/scheduler/tree directives
|
|
389
137
|
ngx-devextreme-zoneless/visualization → chart selection, range selector
|
|
138
|
+
ngx-devextreme-zoneless/forms → [dxFieldState] (Signal Forms; NOT re-exported by the main entry)
|
|
390
139
|
```
|
|
391
140
|
|
|
392
141
|
Importing from the main entry is fine: bundlers drop the families you don't
|
|
@@ -395,39 +144,29 @@ directive is asserted to contain no Gantt, Scheduler, HtmlEditor, chart or
|
|
|
395
144
|
grid modules). Import from a family entry point when you want the split to be
|
|
396
145
|
explicit.
|
|
397
146
|
|
|
398
|
-
##
|
|
399
|
-
|
|
400
|
-
All building blocks are exported, e.g.:
|
|
401
|
-
|
|
402
|
-
```ts
|
|
403
|
-
DxHostOptionName<DxTextBoxComponent> // 'value' | 'placeholder' | 'mode' | ...
|
|
404
|
-
DxHostOptionValue<DxTextBoxComponent, 'mode'> // 'email' | 'password' | ... | undefined
|
|
405
|
-
DxHostEventName<DxDataGridComponent> // 'rowClick' | 'selectionChanged' | ...
|
|
406
|
-
DxHostEventArg<DxTextBoxComponent, 'valueChanged'>
|
|
407
|
-
DxHostValue<DxCheckBoxComponent> // boolean | null
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
## Development
|
|
147
|
+
## Documentation
|
|
411
148
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
tests.
|
|
149
|
+
| Page | What it covers |
|
|
150
|
+
|---|---|
|
|
151
|
+
| **[Choosing your binding style](https://github.com/CaffeinatedCoder/ngx-devextreme-zoneless/blob/main/docs/binding-styles.md)** | The same form three ways — classic zone.js, `[(dxValue)]`, Signal Forms — with syntax, boilerplate and capability compared |
|
|
152
|
+
| **[Directive catalog](https://github.com/CaffeinatedCoder/ngx-devextreme-zoneless/blob/main/docs/directives.md)** | Every two-way directive with its model type: editors, selection, grid state, navigation, overlays, visualization |
|
|
153
|
+
| **[Signal Forms](https://github.com/CaffeinatedCoder/ngx-devextreme-zoneless/blob/main/docs/signal-forms.md)** | `[dxFieldState]`: what crosses the vendor CVA, what the directive adds, why the field is named twice, debounce ownership |
|
|
154
|
+
| **[Composition API](https://github.com/CaffeinatedCoder/ngx-devextreme-zoneless/blob/main/docs/composition-api.md)** | `dxOptionSignal`/`dxEventSignal`/`bindDxOption` for any component, widget re-creation, custom adapters |
|
|
155
|
+
| **[Data loading & remote data](https://github.com/CaffeinatedCoder/ngx-devextreme-zoneless/blob/main/docs/remote-data.md)** | `dxArrayStoreSource`, `dxReloadOn`, and the `dxRemoteStore` contract: cancellation, error/retry, paging, `byKey`, `langParams` |
|
|
156
|
+
| **[Binding semantics & type helpers](https://github.com/CaffeinatedCoder/ngx-devextreme-zoneless/blob/main/docs/semantics.md)** | The engine contract: `undefined` vs `null`, initial sync, echo suppression, debounce, teardown, the exported type helpers |
|
|
157
|
+
|
|
158
|
+
Working on the package itself:
|
|
159
|
+
[development](https://github.com/CaffeinatedCoder/ngx-devextreme-zoneless/blob/main/docs/development.md)
|
|
160
|
+
— builds, the smoke app's four screens, the e2e suite and the tree-shaking
|
|
161
|
+
probe.
|
|
162
|
+
|
|
163
|
+
## Verified, not assumed
|
|
164
|
+
|
|
165
|
+
Running DevExtreme zoneless is outside the vendor-supported configuration, so
|
|
166
|
+
the claim this package makes is exactly as strong as its suite: a Playwright
|
|
167
|
+
run drives every binding family through a real zoneless app — both directions,
|
|
168
|
+
widget re-creation, slow and failing endpoints against a wire-protocol stub —
|
|
169
|
+
on every push. The compile-time surface has its own contract tests
|
|
170
|
+
([examples/type-tests.ts](examples/type-tests.ts)): option names, event names
|
|
171
|
+
and value types are pinned so a vendor rename fails the build instead of
|
|
172
|
+
shipping.
|