@smartbit4all/ng-client 7.0.5 → 7.0.7
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/MIGRATION-7.0.md +1722 -1722
- package/README.md +199 -199
- package/WIDGETS.md +206 -206
- package/fesm2022/smartbit4all-ng-client.mjs +149 -172
- package/fesm2022/smartbit4all-ng-client.mjs.map +1 -1
- package/package.json +1 -1
- package/smartbit4all-ng-client-7.0.7.tgz +0 -0
- package/types/smartbit4all-ng-client.d.ts +1 -3
- package/smartbit4all-ng-client-7.0.5.tgz +0 -0
package/MIGRATION-7.0.md
CHANGED
|
@@ -1,1722 +1,1722 @@
|
|
|
1
|
-
# @smartbit4all/ng-client 6.x → 7.0 migration guide
|
|
2
|
-
|
|
3
|
-
> 7.0.0 targets **Angular 22.0.8 / TypeScript 6.0.3 / Material 22.0.6**, with PrimeNG fully
|
|
4
|
-
> removed (Material/CDK only), every library component `OnPush`, and **no zone.js** — the
|
|
5
|
-
> configuration 7.0 is tested in. Angular's `moment` date adapter is replaced by `date-fns`.
|
|
6
|
-
> It is a major because a host has to act; nothing here is a silent behaviour change that
|
|
7
|
-
> you could ignore.
|
|
8
|
-
|
|
9
|
-
## Where to start
|
|
10
|
-
|
|
11
|
-
1. **Run the codemod** — [Migrating a host: the codemod](#migrating-a-host-the-codemod). It
|
|
12
|
-
does the mechanical part (the module imports, the provider configuration, the dead legacy
|
|
13
|
-
calls) and prints `file:line` for everything that needs a decision.
|
|
14
|
-
2. Read [Full standalone + `provideSmartNgClient()`](#full-standalone--providesmartngclient-phase-32--the-big-one).
|
|
15
|
-
That section rewrites your `app.module.ts`; everything else is small next to it.
|
|
16
|
-
3. Read [Dates](#dates-moment--date-fns-and-the-timezone-contract-phase-38). It is the one
|
|
17
|
-
change that needs action **at install time**, and the one that breaks at *runtime* on the
|
|
18
|
-
first screen with a date field rather than at build time.
|
|
19
|
-
4. Then the rest in order. The **PrimeNG removal** sections (1.1–1.10) are API changes; the
|
|
20
|
-
**hop sections** carry the framework defaults you inherit (OnPush, fetch vs XHR, `?.`).
|
|
21
|
-
5. If you write your own widget, `WIDGETS.md` next to this file is the protocol.
|
|
22
|
-
|
|
23
|
-
Measured across the 12 hosts in scope on 2026-07-28, so you know what to expect: 12–17
|
|
24
|
-
`Smart*Module` imports per host, 1–20 string-keyed providers, `.cssClass =` on an action
|
|
25
|
-
entry at 72 sites in 7 hosts, `this.uiActionModels =` in 5 hosts, `UseUiAction` in 6.
|
|
26
|
-
|
|
27
|
-
**How big is this for *your* host?** Codemod dry-run counts for the five hosts in scope
|
|
28
|
-
(2026-07-30). The first column the codemod applies for you; the second is the list it refuses to
|
|
29
|
-
guess at, which is the real work:
|
|
30
|
-
|
|
31
|
-
| host | mechanical | needs a decision |
|
|
32
|
-
|---|---|---|
|
|
33
|
-
| p014 *(done — the reference)* | 121 | ~100 |
|
|
34
|
-
| p043 | 121 | 96 |
|
|
35
|
-
| app-tournament | 86 | 34 |
|
|
36
|
-
| app-finance-ai | 62 | 18 |
|
|
37
|
-
| app-fitnessmirror | 61 | 14 |
|
|
38
|
-
|
|
39
|
-
The spread is not about host size — all five are within 274–841 source files — but about which
|
|
40
|
-
APIs they happened to use. p043 is p014's twin and repeats its whole decision list; the other
|
|
41
|
-
three have **no `.cssClass =` sites at all**, and their lists are dominated by `smart-modules`,
|
|
42
|
-
the 3-line `moment-adapter` swap, and `zone-js` (which is **optional** — dropping zone.js is not
|
|
43
|
-
required). Take a small one first if you want the process shaken out cheaply.
|
|
44
|
-
|
|
45
|
-
**One number that is now zero: the library install.** p014 was migrated against an unpublished
|
|
46
|
-
build and paid for it in hand-copies, tarballs and `ERESOLVE`. 7.0.0 is on npm, so a host today
|
|
47
|
-
writes `"@smartbit4all/ng-client": "^7.0.0"` and runs `npm i`. None of that thread applies to you.
|
|
48
|
-
|
|
49
|
-
## Migrating a host: the codemod
|
|
50
|
-
|
|
51
|
-
It lives in the `platform-angular2` repository — `tools/ng-client-7-codemod/` — and you run
|
|
52
|
-
it with plain Node, from a checkout, against your own source directory. Nothing to install.
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
node tools/ng-client-7-codemod/migrate.mjs ../my-host/ui-angular/src --dry-run # look first
|
|
56
|
-
node tools/ng-client-7-codemod/migrate.mjs ../my-host/ui-angular/src # then write
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
It **refuses to run on a dirty git tree** (its diff is how you review it, and `git checkout .`
|
|
60
|
-
is how you undo it), it is **idempotent**, and it prints a verdict for every rule it knows —
|
|
61
|
-
including `no sites found`, because a codemod that silently matched nothing looks exactly
|
|
62
|
-
like one that had nothing to do.
|
|
63
|
-
|
|
64
|
-
**What it rewrites:** the `Smart*Module` imports (into the standalone declarables of the
|
|
65
|
-
mapping table below), the string-keyed providers and both `forRoot()` payloads (into one
|
|
66
|
-
`provideSmartNgClient(config, ...features)` call), the redundant core services in the root
|
|
67
|
-
providers array, `this.useQueryLists = true` / `handleDataChangeSubscriptions()`,
|
|
68
|
-
`[parentSmartComponent]` and the dead `[parent]` inputs,
|
|
69
|
-
`execute(x.uiAction, x)` → `execute(x)`, the `async` / `await` around
|
|
70
|
-
`getActionDescriptor`, the `SmartdialogService` `super(…)` call and its constructor
|
|
71
|
-
parameters, and PrimeNG module imports **no template in your repository renders**.
|
|
72
|
-
|
|
73
|
-
**What it refuses to guess at**, printing `file:line` and the section here that explains it:
|
|
74
|
-
`UseUiAction` → `UiActionExecutor`, `this.uiActionModels =`, `entry.cssClass =`, `addForm()`,
|
|
75
|
-
the 4th argument of `addGrid()`, `getActionDescriptor(…).then(…)`, the moment adapter,
|
|
76
|
-
component-level providers, every remaining `primeng/*` import, and `::ng-deep` selectors that
|
|
77
|
-
reach into the form widget.
|
|
78
|
-
|
|
79
|
-
**What it deliberately does not do:** convert *your* components to standalone. That is
|
|
80
|
-
Angular's own schematic (`ng generate @angular/core:standalone`) and it is not required — an
|
|
81
|
-
NgModule's `imports:` array takes standalone components, which is exactly what the codemod
|
|
82
|
-
puts there. Do the library migration first, get the app running, then decide about standalone
|
|
83
|
-
separately.
|
|
84
|
-
|
|
85
|
-
Afterwards: run your formatter, swap the date packages ([Dates §1](#1-swap-the-peer-dependency)),
|
|
86
|
-
build (expect one `NG8001` per template element whose component is not imported yet — that
|
|
87
|
-
loop terminates), and **smoke the app**, because the DI changes are invisible to the compiler.
|
|
88
|
-
|
|
89
|
-
## Breaking changes
|
|
90
|
-
|
|
91
|
-
### Dialog stack: PrimeNG dynamicdialog → MatDialog (Phase 1.1)
|
|
92
|
-
|
|
93
|
-
- `SmartdialogService` and `SmartViewContextDialogService` constructors no longer
|
|
94
|
-
take a PrimeNG `DialogService` parameter. If your host extends or instantiates
|
|
95
|
-
these services, drop the argument (MatDialog is injected internally).
|
|
96
|
-
|
|
97
|
-
Both arguments PrimeNG contributed are gone, so the base constructor lost two of
|
|
98
|
-
its four parameters:
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
// 6.x
|
|
102
|
-
constructor(dialog: MatDialog, dialogService: DialogService, inject: Injector,
|
|
103
|
-
@Inject(COMPONENT_LIBRARY) compLib: ComponentLibrary) {
|
|
104
|
-
super(dialog, dialogService, inject, compLib);
|
|
105
|
-
}
|
|
106
|
-
// 7.0
|
|
107
|
-
constructor(dialog: MatDialog, inject: Injector) {
|
|
108
|
-
super(dialog, inject);
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
**The codemod does this** (rule `smartdialog-super`), including the constructor
|
|
113
|
-
parameters and the now-unused `primeng/dynamicdialog` import — except where a
|
|
114
|
-
dropped parameter carries an accessibility modifier (`protected dialogService: …`),
|
|
115
|
-
which declares a class *property* the body may still read. Those are reported.
|
|
116
|
-
- The `ComponentLibrary` switch no longer has a PrimeNG branch in the dialog
|
|
117
|
-
layer — all dialogs open via `MatDialog` regardless of `COMPONENT_LIBRARY`.
|
|
118
|
-
|
|
119
|
-
### smart-form widgets: PrimeNG removed (Phase 1.2)
|
|
120
|
-
|
|
121
|
-
- `ComparableDropdownDirective` and `ComparableMultiselectDirective` are removed
|
|
122
|
-
(they decorated the PrimeNG `p-dropdown` / `p-multiSelect` elements, which no
|
|
123
|
-
longer exist). Use `SmartformwidgetComponent.compareItems` semantics via
|
|
124
|
-
`mat-select [compareWith]`, which the widget template already wires up.
|
|
125
|
-
- `SmartformwidgetComponent` constructor no longer takes the injected
|
|
126
|
-
`COMPONENT_LIBRARY` token; the widget renders Material-only.
|
|
127
|
-
- `SmartformwidgetComponent` removed PrimeNG-only public members:
|
|
128
|
-
`errorMessages`, `getControlMessages()`, `onPrimeRichTextEditorContentChanged()`,
|
|
129
|
-
`safeDropDownBlur()`/`onDropDownShow()`/`onDropDownInput()`,
|
|
130
|
-
`safeCalendarBlur()`/`onCalendarShow()`/`onCalendarInput()`, `defaultDate`.
|
|
131
|
-
- `SmartFormService.setValuesFromModel()` / `toFormGroup()` /
|
|
132
|
-
`createFormControls()` no longer take a `ComponentLibrary` parameter.
|
|
133
|
-
- `SmartViewContextModule` no longer imports the PrimeNG form modules
|
|
134
|
-
(Dropdown, MultiSelect, Calendar, Chips, InputSwitch, Checkbox, InputNumber,
|
|
135
|
-
InputText, InputTextarea, InputMask, FloatLabel, Messages) and no longer
|
|
136
|
-
provides `Dropdown` / `MultiSelect`.
|
|
137
|
-
|
|
138
|
-
### fileupload: PrimeNG removed (Phase 1.4)
|
|
139
|
-
|
|
140
|
-
- `SmartFileUploaderComponent` (selector `smart-file-uploader4sc`) is removed
|
|
141
|
-
from the public API. It was a PrimeNG `p-fileUpload` wrapper declared in
|
|
142
|
-
`SmartNgClientModule` but referenced nowhere; the Material-style
|
|
143
|
-
`SmartfileuploaderComponent` (selector `smartfileuploader`) is the uploader.
|
|
144
|
-
- The internal `PrimeFileUploaderComponent` (selector `prime-file-uploader`)
|
|
145
|
-
is removed; `smart-upload-widget` always renders `smartfileuploader`.
|
|
146
|
-
- `UploadWidgetComponent` constructor no longer takes the injected
|
|
147
|
-
`COMPONENT_LIBRARY` token, and its PrimeNG-only members are gone
|
|
148
|
-
(`fileUploadPrime`, `uploadFiles()`).
|
|
149
|
-
- `SmartNgClientModule` and `SmartViewContextModule` no longer import the
|
|
150
|
-
PrimeNG `FileUploadModule`; `SmartViewContextModule` also dropped the dead
|
|
151
|
-
`OverlayPanelModule` import (Phase 1.3).
|
|
152
|
-
|
|
153
|
-
### editor: Quill 1.3 → 2, quill-emoji removed (Phase 1.5)
|
|
154
|
-
|
|
155
|
-
- Peer dependencies changed: `quill` `^1.3.7` → `^2.0.3`, `ngx-quill`
|
|
156
|
-
`16.2.1 - 24.0.5` → `^25.3.3`. Hosts must bump both together (ngx-quill 25.x
|
|
157
|
-
is the Quill 2 line for Angular 17; later Angular hops will raise it further).
|
|
158
|
-
- `quill-emoji` peer dependency is **removed** (dead, Quill 1-only, unmaintained).
|
|
159
|
-
If a host needs emoji support, pick a Quill 2-compatible module on its own.
|
|
160
|
-
- `@types/quill` must be removed from host devDependencies — Quill 2 ships its
|
|
161
|
-
own TypeScript types, and the stale `@types/quill` 1.x conflicts with them.
|
|
162
|
-
- The bundled `quill.snow.css` (imported by the smart-form widget) is now the
|
|
163
|
-
Quill 2.0.3 stylesheet; hosts that import `quill/dist/quill.snow.css`
|
|
164
|
-
themselves must serve the Quill 2 version.
|
|
165
|
-
- `SmartViewContextModule` no longer imports the PrimeNG `EditorModule`
|
|
166
|
-
(`<p-editor>` died with Phase 1.2; RICH_TEXT renders via ngx-quill).
|
|
167
|
-
|
|
168
|
-
### smart-diagram: p-chart replaced by direct chart.js (Phase 1.6)
|
|
169
|
-
|
|
170
|
-
- `SmartDiagramComponent` renders its own `<canvas>` and instantiates chart.js
|
|
171
|
-
directly; the PrimeNG `<p-chart>` wrapper (and the `ChartModule` import in
|
|
172
|
-
`SmartDiagramModule`) is gone. Chart.js controllers are now registered by the
|
|
173
|
-
component itself (`Chart.register(...registerables)`), so no `chart.js/auto`
|
|
174
|
-
import is needed anywhere.
|
|
175
|
-
- The public `chart` property (and `getChart()`) is now the chart.js `Chart`
|
|
176
|
-
instance instead of the PrimeNG `UIChart` component. `getBase64Image()` and
|
|
177
|
-
`refresh()` keep their signatures (delegating to `toBase64Image()` /
|
|
178
|
-
`update()`).
|
|
179
|
-
- `SmartDiagramComponent` constructor no longer takes the injected
|
|
180
|
-
`COMPONENT_LIBRARY` token; the component has a single render path.
|
|
181
|
-
- DOM/CSS hooks changed: the internal structure is
|
|
182
|
-
`div.chart-host > div.chart-container > canvas`; host styles targeting
|
|
183
|
-
`::ng-deep p-chart` no longer match. The aspect-ratio classes
|
|
184
|
-
(`default-aspect-ratio` / `pie-aspect-ratio`) are now applied on
|
|
185
|
-
`.chart-host` and size `.chart-container`.
|
|
186
|
-
|
|
187
|
-
### smart-grid: PrimeNG branch removed (Phase 1.7)
|
|
188
|
-
|
|
189
|
-
- `SmartGridComponent` renders Material-only. The `@if (compLib === PRIMENG)`
|
|
190
|
-
template branch (the inline `p-table` / `p-paginator` / `p-menu` /
|
|
191
|
-
`p-multiSelect` grid) is gone; the grid always renders through the PrimeNG-free
|
|
192
|
-
`smart-table` (the `#table` slot), `mat-tree`, `app-smart-grid-card`, and
|
|
193
|
-
`mat-paginator`.
|
|
194
|
-
- `SmartGridComponent` constructor no longer takes the injected
|
|
195
|
-
`COMPONENT_LIBRARY` token, nor the `SmartDatePipe` / `SmartDateTimePipe` /
|
|
196
|
-
`SmartTimePipe` (they only fed the removed inline `p-table` cell renderer);
|
|
197
|
-
those pipes are dropped from the component `providers` too.
|
|
198
|
-
- Removed PrimeNG-only public members: `columns`, `menuButtons`, `menu`,
|
|
199
|
-
`onOptionsClick()`, `previousmultiSortMeta`, `gridSort()`, `lazyLoad()`,
|
|
200
|
-
`headerChange()`, `onColOrder()`, `getOrderColumNames()`, `getColValue()`,
|
|
201
|
-
`onPrimeChangePage()`, `onRowSelect()`, `onRowUnselect()`, `onSelectAllRow()`,
|
|
202
|
-
`getImageResourceIcons()`, `getImageResourceStyle()`, `getRowColumnAction()`,
|
|
203
|
-
`showCellToolbar()`, `shouldShowOptionsButton()`, `calculateMenuActions()`,
|
|
204
|
-
`createCellToActionMap()`, `getRowMenuActionModelArray()`, `rowTrackByFn()`,
|
|
205
|
-
`cellToActionMap`, `columnMetaByName`, and the `_headerToolbar`
|
|
206
|
-
(`#headerToolbar`) view child. The `headerToolbar` getter now returns the
|
|
207
|
-
smart-table's header toolbar unconditionally (its only remaining consumer,
|
|
208
|
-
`SmartComponentApiClient`, is unaffected).
|
|
209
|
-
- `SmartGridModule` no longer imports the PrimeNG `TableModule`, `ButtonModule`,
|
|
210
|
-
`MenuModule`, `PaginatorModule`, `MultiSelectModule`; `MatTooltipModule` is
|
|
211
|
-
added (the grid refresh button is now a Material `mat-icon-button` +
|
|
212
|
-
`matTooltip` instead of `pButton` + `pTooltip`).
|
|
213
|
-
- Note: this is the PrimeNG-branch deletion only. The `smart-grid` internals
|
|
214
|
-
rewrite to `mat-table` + `MatPaginator` and the `smart-grid`/`smart-table`
|
|
215
|
-
merge remain a Phase 3 (post-Angular-22) change.
|
|
216
|
-
|
|
217
|
-
### misc widgets: PrimeNG branches removed (Phase 1.8)
|
|
218
|
-
|
|
219
|
-
The remaining components that still carried a dead PrimeNG branch alongside an
|
|
220
|
-
already-active Material one are now Material-only. In every case the Material
|
|
221
|
-
branch was the rendered path under `COMPONENT_LIBRARY = MATERIAL` (the value all
|
|
222
|
-
7.0 hosts use), so this is render-neutral; the `COMPONENT_LIBRARY` token, where
|
|
223
|
-
these components still injected it, is now ignored by them.
|
|
224
|
-
|
|
225
|
-
- `ExpandableSectionComponent` renders Material-only (the `p-accordion` branch is
|
|
226
|
-
gone; always `mat-expansion-panel`). Its constructor no longer takes the
|
|
227
|
-
injected `COMPONENT_LIBRARY` token. `SmartExpandableSectionModule` no longer
|
|
228
|
-
imports the PrimeNG `AccordionModule`.
|
|
229
|
-
- `UiActionButtonComponent` renders Material-only (the `pButton`/`pRipple` branch
|
|
230
|
-
is gone; always `mat-button`). Its constructor no longer takes the optional
|
|
231
|
-
`COMPONENT_LIBRARY` token (it previously defaulted to `PRIMENG` when unset).
|
|
232
|
-
`getType()` no longer has a PrimeNG class-map branch — it returns the
|
|
233
|
-
`mat-mdc-*` class strings unconditionally. (The dynamic `getbtnClass()`'s
|
|
234
|
-
`p-button-<color>` class token was later dropped in Phase 1.9.)
|
|
235
|
-
- `UiActionToolbarComponent` constructor no longer takes the injected
|
|
236
|
-
`COMPONENT_LIBRARY` token. The dead `getType()` / `getbtnClass()` methods
|
|
237
|
-
(never referenced by the template) were removed, and the scroll-affordance
|
|
238
|
-
buttons use the Material icons `arrow_back` / `arrow_forward` unconditionally
|
|
239
|
-
(the PrimeNG `chevron-left` / `chevron-right` fallback is gone).
|
|
240
|
-
- `UiActionConfirmDialogComponent` and `UiActionInputDialogComponent` render
|
|
241
|
-
their close button as a Material `mat-icon-button` unconditionally (the `@else`
|
|
242
|
-
`p-button` branch is gone); both constructors no longer take the injected
|
|
243
|
-
`COMPONENT_LIBRARY` token.
|
|
244
|
-
- `SmartIconModule` no longer imports the PrimeNG `BadgeModule` (badges render
|
|
245
|
-
via the custom `ui-badge` component, unaffected).
|
|
246
|
-
- `SmartViewContextModule` no longer imports the PrimeNG `ButtonModule`,
|
|
247
|
-
`TooltipModule`, `ToastModule`, `ImageModule`, or `OrderListModule` (all
|
|
248
|
-
unused after the branch deletions). The `primeng/api` `SharedModule`
|
|
249
|
-
(`PrimeSharedModule`) import was later dropped in Phase 1.9's final sweep.
|
|
250
|
-
|
|
251
|
-
### PrimeNG package dependency removed (Phase 1.9)
|
|
252
|
-
|
|
253
|
-
The `primeng` npm package is gone. Most importantly it is dropped from
|
|
254
|
-
`@smartbit4all/ng-client`'s own **`peerDependencies`** (in
|
|
255
|
-
`projects/smart-ng-client/package.json`, which flows into the published
|
|
256
|
-
`npms/smart-ng-client/package.json`) — that entry is what previously forced every
|
|
257
|
-
consuming host to install PrimeNG. It is also removed from the dev workspace
|
|
258
|
-
(`package.json`, `package-lock.json`, `node_modules`), and both PrimeNG theme
|
|
259
|
-
stylesheets (`primeng/resources/themes/saga-blue/theme.css`,
|
|
260
|
-
`primeng/resources/primeng.min.css`) are removed from the app-playground `styles`
|
|
261
|
-
in `angular.json`. Hosts that still list `primeng` as a dependency or load its
|
|
262
|
-
theme CSS should remove both; 7.0 renders entirely on Material/CDK.
|
|
263
|
-
|
|
264
|
-
#### Your own PrimeNG usage
|
|
265
|
-
|
|
266
|
-
Removing the peer dependency does not remove PrimeNG from *your* code, and the measured
|
|
267
|
-
hosts carry more of it than their authors expect — mostly as **dead module imports**. Across
|
|
268
|
-
the 12 hosts, `DialogService` is imported 56 times, but 8 of those in p014 turned out to be
|
|
269
|
-
nothing but the base-constructor argument above, and four PrimeNG modules in its
|
|
270
|
-
`app.module.ts` rendered nothing at all.
|
|
271
|
-
|
|
272
|
-
So the codemod separates the two cases, and the separation is decidable rather than
|
|
273
|
-
guessed:
|
|
274
|
-
|
|
275
|
-
- **`primeng-modules`** — a PrimeNG NgModule in an `imports:` array is **deleted** when none
|
|
276
|
-
of its selectors appears in any template in the repository. The scan runs over `.html`
|
|
277
|
-
*and* `.ts` (for inline templates) before anything is rewritten, and it distinguishes an
|
|
278
|
-
element (`<p-tree>`) from an attribute directive (`pTooltip`) so that a leftover
|
|
279
|
-
`class="p-button"` is not read as a component still in use. What the scan found is printed
|
|
280
|
-
in the header, because it is what licenses the deletions.
|
|
281
|
-
- **`primeng-imports`** — everything else from `primeng/*` is reported with its replacement.
|
|
282
|
-
These are the real ports.
|
|
283
|
-
|
|
284
|
-
The replacements, for what the hosts actually import:
|
|
285
|
-
|
|
286
|
-
| PrimeNG | Material / CDK |
|
|
287
|
-
|---|---|
|
|
288
|
-
| `p-button` / `pButton` | `MatButtonModule` — `mat-button`, `mat-icon-button` |
|
|
289
|
-
| `p-tree` | the library's `<smart-tree>`, or `MatTreeModule` |
|
|
290
|
-
| `p-progressSpinner` | `MatProgressSpinnerModule` — `<mat-spinner>` |
|
|
291
|
-
| `pTooltip` | `MatTooltipModule` (`matTooltip`), or `SmartTooltipDirective` |
|
|
292
|
-
| `p-sidebar` | `MatSidenavModule` |
|
|
293
|
-
| `pInputText` | `MatInputModule` — `matInput` inside `<mat-form-field>` |
|
|
294
|
-
| `p-fieldset` | `MatCardModule`, or `<fieldset>` + `<mat-divider>` |
|
|
295
|
-
| `p-divider` | `MatDividerModule` |
|
|
296
|
-
| `p-accordion` | `MatExpansionModule`, or `<smart-expandable-section>` |
|
|
297
|
-
| `p-table` | the library's `<smart-grid>`, or `MatTableModule` |
|
|
298
|
-
| `p-editor` | ngx-quill's `<quill-editor>` (already a library dependency) |
|
|
299
|
-
| `p-badge` / `pBadge` | `MatBadgeModule`, or `UiBadgeComponent` / `UiBadgeDirective` |
|
|
300
|
-
| `p-menu`, `p-overlayPanel` | `MatMenuModule`, or the CDK Overlay |
|
|
301
|
-
| `p-inputSwitch` | `MatSlideToggleModule` |
|
|
302
|
-
| `p-dialog` | `MatDialog` — open a component instead of toggling `[visible]` |
|
|
303
|
-
| `DialogService` | `MatDialog`. There is **no `header` option**: put `<h2 mat-dialog-title>` in the component |
|
|
304
|
-
| `DynamicDialogRef` | `MatDialogRef` — `afterClosed()` replaces `onClose` |
|
|
305
|
-
| `DynamicDialogConfig` | `MAT_DIALOG_DATA` for the data, `MatDialogRef` for the rest |
|
|
306
|
-
| `PrimeNGConfig` | **delete it.** `setTranslation()` of day and month names is already covered by `MAT_DATE_LOCALE` + `SmartDateFnsAdapter` — the date-fns `hu` locale carries both the names and the Monday week start. **Confirmed on a running host** (p014, 2026-07-29, against a live backend): the datepicker opens on `2026. JÚL.` with `H K Sz Cs P Sz V`, i.e. Hungarian and Monday-first, exactly what the deleted `firstDayOfWeek: 1` + `dayNamesMin` used to buy. See [Dates](#dates-moment--date-fns-and-the-timezone-contract-phase-38) |
|
|
307
|
-
| `MessageService` | `MatSnackBar` |
|
|
308
|
-
| `ConfirmationService` | the library's `UiActionConfirmDialogService` |
|
|
309
|
-
| `MenuItem` | the library's `UiActionModel`, or a plain `<mat-menu>` item |
|
|
310
|
-
| `SharedModule` (`primeng/api`) / `PrimeTemplate` | a plain `<ng-template>` — PrimeNG needed `pTemplate`, Material does not |
|
|
311
|
-
|
|
312
|
-
`primeicons` is a separate package. Nothing in 7.0 needs it, but nothing breaks if you keep
|
|
313
|
-
it either — `pi pi-*` class names are just a font.
|
|
314
|
-
|
|
315
|
-
### `QuillModule.forRoot()` in your root module
|
|
316
|
-
|
|
317
|
-
`provideSmartNgClient()` calls `importProvidersFrom(QuillModule.forRoot())` itself — ngx-quill
|
|
318
|
-
has no provider function, so the module is the only way to reach its root configuration. A
|
|
319
|
-
root module that also calls it is configuring quill twice, so the codemod removes the call
|
|
320
|
-
and its import. In a *feature* module the same call is that module's own configuration and is
|
|
321
|
-
left alone.
|
|
322
|
-
|
|
323
|
-
**Keep `ngx-quill` and `quill` in your `package.json`.** They are peer dependencies of the
|
|
324
|
-
library — the host provides them. npm installs peers automatically, which is why hosts have
|
|
325
|
-
got away with not declaring them, but any install that runs with `--legacy-peer-deps` drops
|
|
326
|
-
them silently and the next build fails on an import that has always worked. Two of the twelve
|
|
327
|
-
measured hosts were in exactly that state.
|
|
328
|
-
|
|
329
|
-
**7.0 adds four peer dependencies 6.x did not have**, and they bite in a way the TypeScript
|
|
330
|
-
errors do not prepare you for:
|
|
331
|
-
|
|
332
|
-
```bash
|
|
333
|
-
npm install ngx-mask@^22.0.0 fast-equals@^5.0.1 wavesurfer.js@^7.9.5 @angular/youtube-player@^22.0.0
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
If you install the library the normal way, npm brings these in for you. If you *swap a local
|
|
337
|
-
build into `node_modules` by hand* — which is how you would try the library before it is
|
|
338
|
-
published — npm never runs, so nothing installs them. The symptom arrives **after** the last
|
|
339
|
-
TypeScript error is fixed, as five `Module not found` errors reported against the library's
|
|
340
|
-
own `fesm2022` bundle rather than against any file of yours:
|
|
341
|
-
|
|
342
|
-
```
|
|
343
|
-
./node_modules/@smartbit4all/ng-client/fesm2022/smartbit4all-ng-client.mjs:7:0-60 -
|
|
344
|
-
Error: Module not found: Error: Can't resolve 'ngx-mask'
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
Four packages, five errors: `wavesurfer.js` is missing twice, once for its `record` plugin.
|
|
348
|
-
|
|
349
|
-
- `UiActionButtonComponent.getbtnClass()` no longer emits the dead
|
|
350
|
-
`p-button-<color>` class token — it returns only `sb4-<color>` (nothing styled
|
|
351
|
-
`.p-button-*`; the platform button theme keys off `sb4-<color>` in
|
|
352
|
-
`custom-theme.scss`). Hosts with their own `.p-button-<color>` overrides for
|
|
353
|
-
sb4 action buttons should migrate them to `.sb4-<color>`.
|
|
354
|
-
- `SmartViewContextModule` no longer imports the `primeng/api` `SharedModule`
|
|
355
|
-
(`PrimeSharedModule`); no template in the module used `pTemplate`.
|
|
356
|
-
- `SmartComponentLayoutModule` no longer imports `InputGroupModule` /
|
|
357
|
-
`InputGroupAddonModule` (they were dead — no `p-inputGroup` template existed).
|
|
358
|
-
|
|
359
|
-
The `ComponentLibrary` enum still exists with only its `MATERIAL` member in
|
|
360
|
-
active use; its `PRIMENG` member and the remaining dead `ComponentLibrary.PRIMENG`
|
|
361
|
-
code branches (icon/menu/widget components, dead `.p-*`/`.pi` CSS) are removed in
|
|
362
|
-
a follow-up internal-cleanup step (1.10) — they are not a package dependency and
|
|
363
|
-
do not affect hosts. `deviceInfo.componentLibrary` continues to report
|
|
364
|
-
`material` to the backend.
|
|
365
|
-
|
|
366
|
-
### ComponentLibrary / COMPONENT_LIBRARY removed entirely (Phase 1.10)
|
|
367
|
-
|
|
368
|
-
7.0 is Material-only, so the `ComponentLibrary` abstraction (which had no second
|
|
369
|
-
implementation once PrimeNG was gone) is deleted outright.
|
|
370
|
-
|
|
371
|
-
- **Host action required:** `ComponentLibrary` (enum) and `COMPONENT_LIBRARY`
|
|
372
|
-
(injection token) are no longer exported from `@smartbit4all/ng-client`. Hosts
|
|
373
|
-
that provide `{ provide: COMPONENT_LIBRARY, useValue: ComponentLibrary.MATERIAL }`
|
|
374
|
-
in their root providers must **remove that provider and its import** — it now
|
|
375
|
-
fails to compile (no exported member). No replacement is needed; every component
|
|
376
|
-
renders Material-only.
|
|
377
|
-
- Every `@Inject(COMPONENT_LIBRARY)` constructor argument is gone from the library
|
|
378
|
-
(smart-icon, click/hover tiered menu, photo-capture / voice-record widgets,
|
|
379
|
-
smart-voice-recorder, smart-file-editor, smart-multi-file-editor, sortable
|
|
380
|
-
widget, smart-filter-editor-content, validation-result-page, message-dialog, the
|
|
381
|
-
dialog services `SmartdialogService` / `SmartViewContextDialogService` /
|
|
382
|
-
`SmartViewContextErrorDialogService` / the ui-action confirm/input/file-upload
|
|
383
|
-
dialog services, and `PdfViewerDialogService` in `@smartbit4all/document-explorer`).
|
|
384
|
-
The `SmartdialogService` base constructor is now `(dialog, injector)` — subclasses
|
|
385
|
-
that called `super(dialog, dialogService, injector, compLib)` keep the first and
|
|
386
|
-
third arguments only. See [Phase 1.1](#dialog-stack-primeng-dynamicdialog--matdialog-phase-11);
|
|
387
|
-
the codemod rewrites it.
|
|
388
|
-
- `SmartViewContextService` no longer exposes the public `componentLibrary` field
|
|
389
|
-
and no longer injects the token; it now writes the literal `'material'` into
|
|
390
|
-
`deviceInfo.componentLibrary` on every view-context update (the backend keys its
|
|
391
|
-
icon set on this string — `images-playground.properties` has `.material=` vs
|
|
392
|
-
`.primeng=` variants — so the value keeps flowing; the DTO field is unchanged).
|
|
393
|
-
- All dead `ComponentLibrary.PRIMENG` branches and their PrimeNG icon names
|
|
394
|
-
(`chevron-right`, `pi pi-*`, `power-off`, `trash`, `file`, `exclamation-circle`,
|
|
395
|
-
`play-circle`/`stop-circle`, etc.) are removed; the Material icon path is now
|
|
396
|
-
unconditional. Dead `.p-*` / `.pi` CSS rules were purged from the library
|
|
397
|
-
(smart-icon, smartform, smartformwidget, the file editors/uploader,
|
|
398
|
-
ui-action-file-upload-dialog) and from the app-playground `custom-theme.scss`;
|
|
399
|
-
the app-playground `index.html` no longer loads the `primeicons` CDN stylesheet.
|
|
400
|
-
- After 1.10, `grep -rin primeng projects` reports a single residual: the
|
|
401
|
-
auto-generated `deviceInfo.ts` DTO doc comment (`(Material, PrimeNg)`), which
|
|
402
|
-
mirrors the backend OpenAPI field description and is regenerated from it — left
|
|
403
|
-
untouched by design (the field itself is intentionally kept).
|
|
404
|
-
|
|
405
|
-
### Angular 18 hop (Phase 2, 17 → 18)
|
|
406
|
-
|
|
407
|
-
The library line now builds against Angular 18. Host-relevant changes:
|
|
408
|
-
|
|
409
|
-
- **Peer dependency bumps** (hosts must upgrade together with the lib):
|
|
410
|
-
`@angular/*` `^18.0.0`, `@angular/youtube-player` `^18.2.14` (was lagging on
|
|
411
|
-
`^16` — its `^16 || ^17` peer range breaks a strict `npm install` on Angular
|
|
412
|
-
18, so it can no longer lag), `ngx-quill` `^26.0.0` (the Angular 18 line;
|
|
413
|
-
quill stays `^2.0.3`), `ngx-mask` `^18.0.0`, and — for
|
|
414
|
-
`@smartbit4all/document-explorer` — `ngx-extended-pdf-viewer` `^21.0.0`
|
|
415
|
-
(the 19.x/20.x lines cap their Angular peer below 18).
|
|
416
|
-
- **`parchment` hoisting quirk (ngx-quill 26):** the ngx-quill 26 typings
|
|
417
|
-
reference `import("parchment")`, but npm may leave `parchment` nested under
|
|
418
|
-
`node_modules/quill/` (even after `npm dedupe`), which fails the host build
|
|
419
|
-
with `TS2307: Cannot find module 'parchment'`. Fix: add
|
|
420
|
-
`"parchment": "^3.0.0"` to the host devDependencies so it is hoisted to the
|
|
421
|
-
top level (quill 2 depends on parchment 3, so versions cannot conflict).
|
|
422
|
-
- **Material theming (M2 compat APIs):** the `ng update @angular/material@18`
|
|
423
|
-
schematic rewrites M2 theme SCSS to the prefixed compat API
|
|
424
|
-
(`mat.define-palette` → `mat.m2-define-palette`,
|
|
425
|
-
`mat.$indigo-palette` → `mat.$m2-indigo-palette`,
|
|
426
|
-
`mat.define-light-theme` → `mat.m2-define-light-theme`, …). The theme stays
|
|
427
|
-
visually **M2** — no M3 switch happens at this hop; hosts running the
|
|
428
|
-
schematic get this rewrite automatically.
|
|
429
|
-
- **`HttpClientModule` deprecation:** the `ng update @angular/core@18`
|
|
430
|
-
migration replaces `HttpClientModule` imports with
|
|
431
|
-
`provideHttpClient(withInterceptorsFromDi())`. Not a library breaking
|
|
432
|
-
change (the lib's generated API services were migrated internally), but
|
|
433
|
-
hosts will get the same automatic migration in their own modules.
|
|
434
|
-
- TypeScript requirement raised to `>=5.4` (the repo builds on 5.5).
|
|
435
|
-
|
|
436
|
-
### Angular 19 hop (Phase 2, 18 → 19)
|
|
437
|
-
|
|
438
|
-
The library line now builds against Angular 19. Host-relevant changes:
|
|
439
|
-
|
|
440
|
-
- **Peer dependency bumps** (hosts must upgrade together with the lib):
|
|
441
|
-
`@angular/*` `^19.0.0`, `@angular/youtube-player` `^19.0.0`,
|
|
442
|
-
`ngx-quill` `^27.0.0` (the Angular 19 line; quill stays `^2.0.3`),
|
|
443
|
-
`ngx-mask` `^19.0.0`, and — for `@smartbit4all/document-explorer` —
|
|
444
|
-
`ngx-extended-pdf-viewer` `^22.0.0` (the 21.x line caps its Angular peer
|
|
445
|
-
below 19).
|
|
446
|
-
- **ngx-extended-pdf-viewer 22 input rename:** the `[showScrollingButton]`
|
|
447
|
-
input no longer exists — it is `[showScrollingButtons]` (plural,
|
|
448
|
-
`ResponsiveVisibility`). Hosts that use `<ngx-extended-pdf-viewer>` directly
|
|
449
|
-
must rename the binding, otherwise the template fails with NG8002.
|
|
450
|
-
- **Standalone-by-default:** in Angular 19 components/directives/pipes are
|
|
451
|
-
standalone unless declared otherwise. The `ng update @angular/core@19`
|
|
452
|
-
migration automatically adds `standalone: false` to every NgModule-declared
|
|
453
|
-
declarable in the host codebase — run it and review the (large, mechanical)
|
|
454
|
-
diff. Declarables not referenced by any NgModule are skipped by the
|
|
455
|
-
schematic.
|
|
456
|
-
- **Material theming:** the `ng update @angular/material@19` schematic splits
|
|
457
|
-
`@include mat.core()` into `@include mat.elevation-classes()` +
|
|
458
|
-
`@include mat.app-background()`. No visual change; hosts running the
|
|
459
|
-
schematic get this rewrite automatically.
|
|
460
|
-
- **New NG8111 extended diagnostic** (warning): "Function in event binding
|
|
461
|
-
should be invoked". Uninvoked event bindings like
|
|
462
|
-
`(click)="(someCallback)"` — previously silently dead — are now flagged at
|
|
463
|
-
build time. Warning only, the build stays green.
|
|
464
|
-
- TypeScript requirement stays satisfied by 5.5 (Angular 19 supports
|
|
465
|
-
TS 5.5–5.8).
|
|
466
|
-
|
|
467
|
-
### Angular 20 hop (Phase 2, 19 → 20)
|
|
468
|
-
|
|
469
|
-
The library line now builds against Angular 20. Host-relevant changes:
|
|
470
|
-
|
|
471
|
-
- **Peer dependency bumps** (hosts must upgrade together with the lib):
|
|
472
|
-
`@angular/*` `^20.0.0`, `@angular/youtube-player` `^20.0.0`,
|
|
473
|
-
`ngx-quill` `^28.0.0` (the Angular 20 line; quill stays `^2.0.3`),
|
|
474
|
-
`ngx-mask` `^20.0.0`, and — for `@smartbit4all/document-explorer` —
|
|
475
|
-
`ngx-extended-pdf-viewer` `^23.0.0` (the 22.x line caps its Angular peer
|
|
476
|
-
below 20). No pdf-viewer API rename at this bump (unlike 22.x).
|
|
477
|
-
- **TypeScript 5.9 required** (Angular 20 supports 5.8–5.9); `ng update`
|
|
478
|
-
bumps it automatically. TS 5.9 adds **TS2872 "This kind of expression is
|
|
479
|
-
always truthy"**, which turns previously-silent dead `||` alternatives into
|
|
480
|
-
build errors — expect one or two in any older codebase.
|
|
481
|
-
- **Material 20 button labels no longer set `white-space: nowrap`.** Material
|
|
482
|
-
20 dropped the MDC stylesheet (`@material/button`), which used to carry it.
|
|
483
|
-
Multi-word button labels now wrap, and any rule that lets the button size to
|
|
484
|
-
its content (the lib's `.mdc-button { height: fit-content }`) makes the
|
|
485
|
-
button grow taller. The lib restores the pre-20 look inside
|
|
486
|
-
`ui-action-button` (`.mdc-button__label { white-space: nowrap }`); **hosts
|
|
487
|
-
that render Material buttons of their own must add the same rule** if they
|
|
488
|
-
relied on single-line labels (typical symptom: a navigation bar that
|
|
489
|
-
suddenly wraps and overflows into scroll arrows).
|
|
490
|
-
- **Material 20 renamed the MDC-era CSS custom properties**: e.g.
|
|
491
|
-
`--mdc-text-button-container-height` → `--mat-button-text-container-height`.
|
|
492
|
-
The `ng update @angular/material@20` schematic rewrites the ones it finds in
|
|
493
|
-
`.css`/`.scss` files; token names embedded in TS strings or host templates
|
|
494
|
-
must be renamed by hand.
|
|
495
|
-
- **`DOCUMENT` moved from `@angular/common` to `@angular/core`.** Automatic
|
|
496
|
-
migration; `@angular/common`'s re-export is deprecated.
|
|
497
|
-
- **`moduleResolution: "node"` → `"bundler"`** in `tsconfig.json` (and any
|
|
498
|
-
`tsconfig.lib.json` that sets it explicitly). Automatic migration; it changes
|
|
499
|
-
how sub-path exports resolve, so a host with hand-written `paths` entries
|
|
500
|
-
should re-verify its build.
|
|
501
|
-
- **Workspace generation defaults**: the CLI adds a `schematics` block to
|
|
502
|
-
`angular.json` pinning the pre-20 file-naming style (`type: "component"`,
|
|
503
|
-
`typeSeparator: "."`). Cosmetic — it only affects newly generated files.
|
|
504
|
-
- Optional migrations **not** run here (deferred to the modernization phase):
|
|
505
|
-
`use-application-builder` (esbuild), `control-flow-migration`,
|
|
506
|
-
`router-current-navigation`.
|
|
507
|
-
- The `InjectFlags`, `TestBed.get` → `TestBed.inject` and
|
|
508
|
-
`provideServerRendering` migrations were no-ops for this codebase.
|
|
509
|
-
- The NG8111 warnings introduced at the 19 hop are no longer emitted by the
|
|
510
|
-
Angular 20 compiler (the dead bindings themselves are unchanged).
|
|
511
|
-
|
|
512
|
-
### Angular 21 hop (Phase 2, 20 → 21)
|
|
513
|
-
|
|
514
|
-
The library line now builds against Angular 21. Host-relevant changes:
|
|
515
|
-
|
|
516
|
-
- **Peer dependency bumps** (hosts must upgrade together with the lib):
|
|
517
|
-
`@angular/*` `^21.0.0`, `@angular/youtube-player` `^21.0.0`,
|
|
518
|
-
`ngx-quill` `^29.0.0` (the Angular 21 line; quill stays `^2.0.3`),
|
|
519
|
-
`ngx-mask` `^21.0.0`, and — for `@smartbit4all/document-explorer` —
|
|
520
|
-
`ngx-extended-pdf-viewer` `^25.0.0` (the 24.x line still caps its Angular
|
|
521
|
-
peer below 21). No pdf-viewer API rename at this bump.
|
|
522
|
-
- **`ngx-quill` 30.x is intentionally *not* used.** 30.x is the "zoneless"
|
|
523
|
-
line and drops the `zone.js` peer dependency; the 7.0 lib is still
|
|
524
|
-
zone-based. Hosts that have already gone zoneless may use 30.x, but the
|
|
525
|
-
lib is only validated against 29.x.
|
|
526
|
-
- **TypeScript stays 5.9** and **zone.js stays 0.15** (Angular 21 accepts
|
|
527
|
-
`~0.15.0 || ~0.16.0`). **Node** must satisfy
|
|
528
|
-
`^20.19.0 || ^22.12.0 || >=24.0.0`.
|
|
529
|
-
- **`MatCommonModule` is removed from `@angular/material/core` (breaking).**
|
|
530
|
-
It was already deprecated in Material 20 (`@breaking-change 21.0.0`). Any
|
|
531
|
-
host NgModule that imports it will fail to compile with
|
|
532
|
-
`Unknown reference` — and because a broken NgModule cascades, the real
|
|
533
|
-
error usually shows up as a flood of `NG8001: '<some-component>' is not a
|
|
534
|
-
known element` / `NG6002: … does not appear to be an NgModule class`
|
|
535
|
-
further down the build log. **Fix: just delete the import and the array
|
|
536
|
-
entry.** `MatCommonModule` had no template surface; it only
|
|
537
|
-
(a) applied the `cdk-high-contrast-*` body classes — `A11yModule` still
|
|
538
|
-
does this, and Material components that need it import `A11yModule`
|
|
539
|
-
themselves — and (b) re-exported `BidiModule`. If your host actually uses
|
|
540
|
-
the `Dir` directive or injects `Directionality`, import
|
|
541
|
-
`BidiModule` from `@angular/cdk/bidi` explicitly.
|
|
542
|
-
The lib dropped it from all 10 of its own modules.
|
|
543
|
-
- **`provideZoneChangeDetection()` is now added at bootstrap** by the
|
|
544
|
-
`ng update` migration, e.g.
|
|
545
|
-
`bootstrapModule(AppModule, { applicationProviders: [provideZoneChangeDetection()] })`.
|
|
546
|
-
This keeps zone-based change detection explicit; behaviour is unchanged.
|
|
547
|
-
- **The control-flow migration (`*ngIf` → `@if`) now runs as a *mandatory*
|
|
548
|
-
`ng update` migration**, not an optional one. It rewrites every structural
|
|
549
|
-
directive in the workspace. The lib **reverted** it to keep the version hop
|
|
550
|
-
reviewable — `NgIf`/`NgForOf`/`NgSwitch` still ship in Angular 21, so
|
|
551
|
-
`*ngIf`/`*ngFor` keep working. Hosts may keep or revert it as they prefer;
|
|
552
|
-
if you want it as a separate change, run
|
|
553
|
-
`npx ng generate @angular/core:control-flow` on its own commit.
|
|
554
|
-
- **New `NG8107` extended diagnostic** (warning): an optional chain `?.`
|
|
555
|
-
whose left side can no longer be `null`/`undefined` under Angular 21's
|
|
556
|
-
sharpened template inference. Warning only — expect a batch of them in any
|
|
557
|
-
older template set.
|
|
558
|
-
- **`tsconfig.json` loses its explicit `lib` array** (the CLI derives it from
|
|
559
|
-
`target`). Automatic migration.
|
|
560
|
-
- The Material and CDK v21 migration schematics made no source changes, and
|
|
561
|
-
the `Router.lastSuccessfulNavigation` migration was a no-op. The optional
|
|
562
|
-
`router-current-navigation` migration was not run.
|
|
563
|
-
- The `.mdc-button__label { white-space: nowrap }` workaround introduced at
|
|
564
|
-
the Angular 20 hop is **still required on Material 21** — the class still
|
|
565
|
-
exists in the button template and Material still ships no `white-space`
|
|
566
|
-
rule for it.
|
|
567
|
-
|
|
568
|
-
### Angular 22 hop (Phase 2, 21 → 22) — final hop
|
|
569
|
-
|
|
570
|
-
The library line now builds against **Angular 22**. This is the biggest
|
|
571
|
-
host-facing hop of the series: it changes the **change-detection default**, the
|
|
572
|
-
**HTTP backend default** and the **meaning of `?.` in templates**. All three are
|
|
573
|
-
handled by `ng update` migrations, but you must let them run.
|
|
574
|
-
|
|
575
|
-
- **Peer dependency bumps** (hosts must upgrade together with the lib):
|
|
576
|
-
`@angular/*` `^22.0.0`, `@angular/youtube-player` `^22.0.0`,
|
|
577
|
-
`ngx-quill` `^31.0.0` (the Angular 22 line; quill stays `^2.0.3`),
|
|
578
|
-
`ngx-mask` `^22.0.0`, and — for `@smartbit4all/document-explorer` —
|
|
579
|
-
`ngx-extended-pdf-viewer` `^28.0.0` (25/26/27 all cap their Angular peer
|
|
580
|
-
below 22). **No pdf-viewer API rename at this bump.**
|
|
581
|
-
`@ngx-translate/core` `^14` still works (its peer is `>=13`).
|
|
582
|
-
- **TypeScript must be 6.0** (`@angular/compiler-cli` peers `>=6.0 <6.1`);
|
|
583
|
-
**zone.js stays 0.15** (`~0.15.0 || ~0.16.0`) and **rxjs is unchanged**.
|
|
584
|
-
**Node** must satisfy `^22.22.3 || ^24.15.0 || >=26.0.0` — note the
|
|
585
|
-
**22.22.3** floor, which is stricter than Angular 21's `^22.12.0`.
|
|
586
|
-
|
|
587
|
-
#### 1. `ChangeDetectionStrategy.OnPush` is the new default
|
|
588
|
-
|
|
589
|
-
In Angular 22 the enum is redefined: `OnPush = 0` (default), `Eager = 1`, and
|
|
590
|
-
`Default = 1` is kept as a **deprecated alias of `Eager`**. Any component
|
|
591
|
-
without an explicit `changeDetection` therefore switches from CheckAlways to
|
|
592
|
-
OnPush — a real behaviour change for components that mutate their own state
|
|
593
|
-
without signals or `markForCheck()`.
|
|
594
|
-
|
|
595
|
-
An `ng update` schematic **automatically adds
|
|
596
|
-
`changeDetection: ChangeDetectionStrategy.Eager` to every component that had
|
|
597
|
-
none**, which keeps behaviour bit-identical. The lib took exactly this route
|
|
598
|
-
(114 components) and did **not** opt into OnPush at this hop. Recommended host
|
|
599
|
-
approach: let the schematic run, verify your app, and move components to OnPush
|
|
600
|
-
deliberately afterwards.
|
|
601
|
-
|
|
602
|
-
Watch out for components the schematic can skip — it only touches components it
|
|
603
|
-
can resolve. After updating, grep for `@Component` files with no
|
|
604
|
-
`changeDetection` and decide about each one; a component that is dynamically
|
|
605
|
-
instantiated but never statically declared can slip through.
|
|
606
|
-
|
|
607
|
-
#### 2. `provideHttpClient` now defaults to the fetch backend
|
|
608
|
-
|
|
609
|
-
Angular 22 switches `HttpClient` from `XMLHttpRequest` to `fetch`. An
|
|
610
|
-
`ng update` migration inserts **`withXhr()`** into your `provideHttpClient(...)`
|
|
611
|
-
calls to preserve the old backend:
|
|
612
|
-
|
|
613
|
-
```ts
|
|
614
|
-
provideHttpClient(withXhr(), withInterceptorsFromDi())
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
The lib does this in `SmartViewContextModule`. **Hosts that call
|
|
618
|
-
`provideHttpClient` themselves must do the same** (or consciously move to
|
|
619
|
-
fetch). This matters most for **upload progress**: `reportProgress` events
|
|
620
|
-
behave differently on the fetch backend, so if your host reports upload
|
|
621
|
-
progress, keep `withXhr()` until you have retested it.
|
|
622
|
-
|
|
623
|
-
#### 3. `?.` in templates now yields `undefined`, not `null`
|
|
624
|
-
|
|
625
|
-
Angular 22's safe-navigation operator returns `undefined` where it used to
|
|
626
|
-
return `null`. Where that difference is observable, an `ng update` migration
|
|
627
|
-
wraps the expression in the **`$safeNavigationMigration()` compiler builtin**:
|
|
628
|
-
|
|
629
|
-
```html
|
|
630
|
-
[ngStyle]="calcStyle($safeNavigationMigration(diagramModel?.descriptor?.style))"
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
This is an official builtin (handled like `$any()` — the compiler unwraps it and
|
|
634
|
-
the type-checker sees straight through it), not a temporary marker, so wrapped
|
|
635
|
-
templates are safe to keep. The same `ng update` run also sets the
|
|
636
|
-
`nullishCoalescingNotNullable` and `optionalChainNotNullable` extended
|
|
637
|
-
diagnostics to `suppress` in your tsconfigs, which silences the `NG8107`
|
|
638
|
-
warnings the Angular 21 hop introduced.
|
|
639
|
-
|
|
640
|
-
#### 4. `ComponentFactoryResolver` is removed (breaking)
|
|
641
|
-
|
|
642
|
-
`ComponentFactoryResolver` is gone and `ComponentFactory` is no longer public
|
|
643
|
-
(it only survives as `ɵRender3ComponentFactory`). Code like this stops
|
|
644
|
-
compiling with `TS2305` / `NG2003`:
|
|
645
|
-
|
|
646
|
-
```ts
|
|
647
|
-
constructor(private resolver: ComponentFactoryResolver) {}
|
|
648
|
-
ngAfterViewInit() {
|
|
649
|
-
const factory = this.resolver.resolveComponentFactory(MyComponent);
|
|
650
|
-
this.vcRef.createComponent(factory);
|
|
651
|
-
}
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
Replace it with the type-based overload — behaviour is identical (v22 builds the
|
|
655
|
-
same factory internally and resolves the injector from the parent):
|
|
656
|
-
|
|
657
|
-
```ts
|
|
658
|
-
ngAfterViewInit() {
|
|
659
|
-
this.vcRef.createComponent(MyComponent);
|
|
660
|
-
}
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
**Public-API change in the lib:** `ComponentFactoryService` no longer exposes
|
|
664
|
-
its `factory` field. `createComponent()` / `destroyComponent()` keep their
|
|
665
|
-
signatures, so hosts that only call those need no change.
|
|
666
|
-
|
|
667
|
-
#### 5. TypeScript 6.0 deprecates `baseUrl` and `downlevelIteration`
|
|
668
|
-
|
|
669
|
-
TS 6.0 raises **`TS5101`** for both options ("deprecated and will stop
|
|
670
|
-
functioning in TypeScript 7.0"). Two ways out:
|
|
671
|
-
|
|
672
|
-
- Remove them. `downlevelIteration` is a no-op at `target: ES2022`. `baseUrl`
|
|
673
|
-
can only go once every non-relative first-party import (`from 'projects/…'`,
|
|
674
|
-
`from 'src/…'`) is relative or covered by a `paths` entry.
|
|
675
|
-
- Or silence them for now with `"ignoreDeprecations": "6.0"` in
|
|
676
|
-
`compilerOptions`. This is what the lib workspace does, because `baseUrl` is
|
|
677
|
-
still load-bearing there. It only buys time until TS 7.
|
|
678
|
-
|
|
679
|
-
#### 6. Other notes
|
|
680
|
-
|
|
681
|
-
- **Material 22 removed no further modules or tokens** — the CDK and Material
|
|
682
|
-
v22 migration schematics made no source changes. CDK 22 moves to a
|
|
683
|
-
package `exports` map (the per-entry-point sub-directories are gone from
|
|
684
|
-
`node_modules/@angular/cdk`), but every `@angular/cdk/*` import still
|
|
685
|
-
resolves; this only matters if you referenced those paths on disk.
|
|
686
|
-
- The `.mdc-button__label { white-space: nowrap }` workaround introduced at the
|
|
687
|
-
Angular 20 hop is **still required on Material 22** — the class still appears
|
|
688
|
-
in Material's button template and Material still ships no `white-space` rule
|
|
689
|
-
for it.
|
|
690
|
-
- **The control-flow migration did not re-run at this hop** (it was mandatory at
|
|
691
|
-
21). The lib still ships `*ngIf`/`*ngFor` templates; `NgIf`/`NgForOf`/
|
|
692
|
-
`NgSwitch` continue to work in Angular 22.
|
|
693
|
-
- **The webpack-based builders are deprecated.** `@angular-devkit/build-angular`
|
|
694
|
-
now prints a notice recommending `@angular/build`. Nothing breaks yet, but
|
|
695
|
-
plan the switch.
|
|
696
|
-
- `strictTemplates` is on by default in v22; the lib workspace was already
|
|
697
|
-
strict, so nothing changed. If your host was not strict, the `ng update`
|
|
698
|
-
migration adds `"strictTemplates": false` to keep it that way.
|
|
699
|
-
|
|
700
|
-
### The third-party packages the library does *not* own (Phase 2)
|
|
701
|
-
|
|
702
|
-
Every hop section above lists the library's own peer dependencies. Your host almost
|
|
703
|
-
certainly carries packages the library never sees, and those have their own Angular
|
|
704
|
-
peer ceilings — each one can stop a hop dead. Measured on p014 (2026-07-29), which
|
|
705
|
-
went 17 → 22 with the library still installed:
|
|
706
|
-
|
|
707
|
-
- **`@swimlane/ngx-charts` needs a ladder of its own**: `20 → 22 → 22 → 23 → 24 → 25`
|
|
708
|
-
across the five hops. At the Angular 21 hop it peer-caps the CDK, and because
|
|
709
|
-
`ng update`'s own `npm` step is **not** `--force`d (even when you passed `--force`
|
|
710
|
-
to `ng update`), the install fails there while `package.json` has already been
|
|
711
|
-
written correctly. The fix both times was to redo the install by hand, not to
|
|
712
|
-
re-run the update. `p043` and `app-tournament` both carry ngx-charts `^20.4.1`
|
|
713
|
-
and will hit this; `app-fitnessmirror` and `app-finance-ai` do not.
|
|
714
|
-
- **`ngx-extended-pdf-viewer` moved to `.mjs`** at the 21 hop and stopped shipping
|
|
715
|
-
`pdf-3.10.560-es5.min.js`. If your `angular.json` names that file under `scripts`
|
|
716
|
-
or `assets`, the build fails on a missing file rather than on anything Angular.
|
|
717
|
-
- **`karma` and `@angular/build`**: `@angular/build` 22 declares
|
|
718
|
-
`peerOptional karma@^6.4.0`. A host pinned to `~6.3.0` gets a wall of
|
|
719
|
-
`npm warn ERESOLVE overriding peer dependency` — **warnings, exit code 0**, so it
|
|
720
|
-
is not blocking. It becomes a *hard* install failure the moment `@angular/build`
|
|
721
|
-
is a direct devDependency rather than a nested one, which is what happened in the
|
|
722
|
-
library workspace. `karma@~6.4.4` clears it.
|
|
723
|
-
|
|
724
|
-
The general shape: **`ng update` needs `--force` at every hop** (the library and
|
|
725
|
-
PrimeNG both peer-cap at Angular 17 until the very end), and `--force` covers the
|
|
726
|
-
Angular schematics but not the `npm install` they trigger. When that install fails,
|
|
727
|
-
check `package.json` before assuming the hop did nothing — it is usually already
|
|
728
|
-
correct.
|
|
729
|
-
|
|
730
|
-
### Housekeeping (Phase 3.0)
|
|
731
|
-
|
|
732
|
-
#### The dialog's built-in Ok/Cancel buttons are gone
|
|
733
|
-
|
|
734
|
-
`smartdialog.component.html` no longer renders an `Ok` and a `Cancel` button.
|
|
735
|
-
They were dead: their `(click)` handlers were the no-op expressions
|
|
736
|
-
`(click)="(data.okCallback)"` / `(click)="(data.cancelCallback)"` — a bare
|
|
737
|
-
property read, not a call — so pressing them did nothing, and the whole block
|
|
738
|
-
was behind `*ngIf="!data.customComponent"`, which is never true for a host
|
|
739
|
-
dialog (`SmartDialog.ngAfterViewInit` unconditionally instantiates
|
|
740
|
-
`data.customComponent`).
|
|
741
|
-
|
|
742
|
-
**The `okCallback` / `cancelCallback` model fields stay.** Hosts set and invoke
|
|
743
|
-
them from their own `customComponent` dialogs, and that keeps working
|
|
744
|
-
unchanged. Only the library-rendered buttons are gone. The `actionCallback` /
|
|
745
|
-
`actionLabel` button — the one with a real handler (`onActionClick()`) — is
|
|
746
|
-
untouched.
|
|
747
|
-
|
|
748
|
-
Action needed: none, unless you relied on the library rendering those two
|
|
749
|
-
buttons — in which case they never worked, and you should render them in your
|
|
750
|
-
own dialog component.
|
|
751
|
-
|
|
752
|
-
### Full standalone + `provideSmartNgClient()` (Phase 3.2) — **the big one**
|
|
753
|
-
|
|
754
|
-
This is the change that rewrites your `app.module.ts`. Everything else in this
|
|
755
|
-
guide is small next to it.
|
|
756
|
-
|
|
757
|
-
#### Every NgModule is gone
|
|
758
|
-
|
|
759
|
-
The library no longer ships a single `@NgModule`. All 58 are deleted —
|
|
760
|
-
`SmartNgClientModule`, `SmartViewContextModule`, `SmartSessionModule`,
|
|
761
|
-
`SmartGridModule`, `SmarttableModule`, `SmarttreeModule`, `SmartdialogModule`,
|
|
762
|
-
`SmartIconModule`, `SmartNavbarModule`, `SmartFilterModule`,
|
|
763
|
-
`SmartFilterEditorModule`, `SmartComponentLayoutModule`,
|
|
764
|
-
`SmartExpandableSectionModule`, `SmartMapModule`, `SmartDiagramModule`,
|
|
765
|
-
`SmartNavigationModule`, `SmartValidationModule`, `SmartGenericPagesModule`,
|
|
766
|
-
`ComponentFactoryServiceModule`, `SharedModule`, `SmartTabGroupModule`,
|
|
767
|
-
`SmartDocuStoreExplorerModule` and the generated OpenAPI `ApiModule`s.
|
|
768
|
-
|
|
769
|
-
Every component, directive and pipe is now **standalone**. Import the ones your
|
|
770
|
-
templates use directly, in the component that uses them:
|
|
771
|
-
|
|
772
|
-
```ts
|
|
773
|
-
@Component({
|
|
774
|
-
selector: 'app-my-page',
|
|
775
|
-
templateUrl: './my-page.component.html',
|
|
776
|
-
imports: [SmartGridComponent, UiActionToolbarComponent, SmartEmbeddedSlotDirective],
|
|
777
|
-
})
|
|
778
|
-
export class MyPageComponent extends SmartComponent<MyModel> { … }
|
|
779
|
-
```
|
|
780
|
-
|
|
781
|
-
**The compiler tells you what is missing** — one `NG8001: '<smart-grid>' is not
|
|
782
|
-
a known element` per unresolved element, per template. Work through them; there
|
|
783
|
-
is no guessing involved. In practice ~16 module imports become ~40 component
|
|
784
|
-
imports spread across the components that actually use them.
|
|
785
|
-
|
|
786
|
-
**The compiler does NOT tell you about the DI changes below.** Nothing fails at
|
|
787
|
-
build time if you get those wrong; you find out on the first page load. Smoke
|
|
788
|
-
your app.
|
|
789
|
-
|
|
790
|
-
#### `provideSmartNgClient(config, ...features)` replaces the module imports
|
|
791
|
-
|
|
792
|
-
```ts
|
|
793
|
-
bootstrapApplication(AppComponent, {
|
|
794
|
-
providers: [
|
|
795
|
-
provideAnimations(),
|
|
796
|
-
provideRouter(ROUTES),
|
|
797
|
-
provideSmartNgClient(
|
|
798
|
-
{
|
|
799
|
-
gridMenuIcon: 'more_horiz',
|
|
800
|
-
treeMenuIcon: 'more_horiz',
|
|
801
|
-
aclEditingViewName: Pages.ACL_MATRIX_PAGE,
|
|
802
|
-
invalidSmartlinkPageName: Pages.INVALID_SMARTLINK_PAGE_NAME,
|
|
803
|
-
namedValidators: [MY_VALIDATOR_FACTORY],
|
|
804
|
-
},
|
|
805
|
-
withSmartMap({ engine: MapEngine.LEAFLET }),
|
|
806
|
-
withSmartDiagram({ customOptions: [MY_CHART] })
|
|
807
|
-
),
|
|
808
|
-
{ provide: HTTP_INTERCEPTORS, useClass: MyLoadingInterceptor, multi: true },
|
|
809
|
-
],
|
|
810
|
-
});
|
|
811
|
-
```
|
|
812
|
-
|
|
813
|
-
The core is always wired and cannot be forgotten: session, view context, the
|
|
814
|
-
three BFF interceptors, layout, form, grid, table, tree, dialog, icon, navbar,
|
|
815
|
-
filter, filter editor, validation, expandable section, navigation, shared and
|
|
816
|
-
the generic pages. That is the point — the session and header interceptors used
|
|
817
|
-
to be easy to leave out, and leaving them out fails *silently* (no
|
|
818
|
-
`Authorization` header).
|
|
819
|
-
|
|
820
|
-
Only two features are opt-in, because they pull heavy third-party code that not
|
|
821
|
-
every host renders: `withSmartMap()` (leaflet / Google Maps) and
|
|
822
|
-
`withSmartDiagram()` (chart.js). There is **no `withSmartTabGroup()`** —
|
|
823
|
-
`smart-tab-group` had no component left, only an empty module, and is deleted.
|
|
824
|
-
|
|
825
|
-
`provideSmartNgClient()` calls `provideHttpClient()` itself, with
|
|
826
|
-
`withInterceptorsFromDi()` and its own three functional interceptors, and leaves the backend
|
|
827
|
-
on the framework default (fetch). Your own class-based `HTTP_INTERCEPTORS` keep working and
|
|
828
|
-
still run after the library's three. Pass extra `HttpClient` features in
|
|
829
|
-
`config.httpFeatures` rather than making a second `provideHttpClient()` call — see
|
|
830
|
-
[HTTP](#http-the-library-no-longer-forces-the-xhr-backend-phase-38), which is also where the
|
|
831
|
-
6.x "do not call it twice" trap went.
|
|
832
|
-
|
|
833
|
-
#### String-keyed providers become typed config fields
|
|
834
|
-
|
|
835
|
-
Delete these from your providers array and pass them to `provideSmartNgClient`:
|
|
836
|
-
|
|
837
|
-
| 6.x provider | 7.0 config field |
|
|
838
|
-
|---|---|
|
|
839
|
-
| `{ provide: 'gridMenuIcon', useValue: … }` | `gridMenuIcon` |
|
|
840
|
-
| `{ provide: 'treeMenuIcon', useValue: … }` | `treeMenuIcon` |
|
|
841
|
-
| `{ provide: 'searchPageName' / 'searchComponentName', … }` | `searchPageName` / `searchComponentName` |
|
|
842
|
-
| `{ provide: 'genericPageName' / 'genericComponentName', … }` | `genericPageName` / `genericComponentName` |
|
|
843
|
-
| `{ provide: 'subjectSelectorPageName' / 'subjectSelectorComponentName', … }` | `subjectSelectorPageName` / `subjectSelectorComponentName` |
|
|
844
|
-
| `{ provide: 'validationResultPageName' / 'validationResultComponentName', … }` | `validationResultPageName` / `validationResultComponentName` |
|
|
845
|
-
| `{ provide: 'noPermissionPageName' / 'noPermissionComponentName', … }` | `noPermissionPageName` / `noPermissionComponentName` |
|
|
846
|
-
| `{ provide: 'invalidSmartlinkPageName' / 'invalidSmartlinkComponentName', … }` | `invalidSmartlinkPageName` / `invalidSmartlinkComponentName` |
|
|
847
|
-
| `{ provide: 'aclEditingViewName', useValue: … }` | `aclEditingViewName` |
|
|
848
|
-
| `{ provide: DIALOG_DISABLE_CLOSE, useValue: … }` | `dialogDisableClose` |
|
|
849
|
-
| `{ provide: MAT_DATE_LOCALE, useValue: 'hu-HU' }` | `dateLocale` — **or keep the provider as it is**, which is what the codemod does; see [Dates §2](#2-leave-your-mat_date_locale-provider-alone) |
|
|
850
|
-
| `SmartWidgetSettings.useUtc = true` (a static, not a provider) | **delete it** — the flag is gone with the moment adapter; see [Dates §4](#4-smartngclientconfiguseutcdates-is-removed) |
|
|
851
|
-
| `{ provide: NAMED_VALIDATOR, useValue: F, multi: true }` | `namedValidators: [F]` |
|
|
852
|
-
| `{ provide: SMART_DEFAULT_VIEW_COMPONENTS, useValue: E, multi: true }` | `defaultViewComponents: [E]` |
|
|
853
|
-
| `SmartValidationModule.forRoot([...])` | `namedValidators` — **the element type changes**, see below |
|
|
854
|
-
| `SmartDiagramModule.forRoot([...])` | `withSmartDiagram({ customOptions })` |
|
|
855
|
-
| `{ provide: MAP_ENGINE, useValue: … }` | `withSmartMap({ engine })` |
|
|
856
|
-
|
|
857
|
-
`'pageName'`, `'componentName'` and `'treeId'` stay as string tokens on purpose:
|
|
858
|
-
they identify one component instance rather than configuring the library, and
|
|
859
|
-
hosts also provide them at component level.
|
|
860
|
-
|
|
861
|
-
You can also delete these from your **root** providers array — the core provides them:
|
|
862
|
-
`SmartSessionService`, `SmartViewContextService`, `SmartNavigationService`,
|
|
863
|
-
`SmartIconService`, `SmartFilterEditorService`, `SmartFormService`,
|
|
864
|
-
`SmartCookieService`, `NamedValidatorService`, `ComponentFactoryService`, and the date
|
|
865
|
-
adapter wiring (`MAT_DATE_FORMATS`, `DateAdapter`).
|
|
866
|
-
|
|
867
|
-
⚠️ **`namedValidators` takes factories, not providers.** `SmartValidationModule.forRoot()`
|
|
868
|
-
took `Provider[]`, so hosts wrote the wrapper by hand:
|
|
869
|
-
|
|
870
|
-
```ts
|
|
871
|
-
// 6.x — validator.factories.ts
|
|
872
|
-
export const VALIDATOR_PROVIDER_EMPTY_INPUT: Provider = {
|
|
873
|
-
provide: NAMED_VALIDATOR, useValue: VALIDATOR_FACTORY_EMPTY_INPUT, multi: true,
|
|
874
|
-
};
|
|
875
|
-
// app.module.ts
|
|
876
|
-
imports: [SmartValidationModule.forRoot([VALIDATOR_PROVIDER_EMPTY_INPUT])]
|
|
877
|
-
```
|
|
878
|
-
|
|
879
|
-
`provideSmartNgClient()` adds that wrapper itself, so the config takes the bare
|
|
880
|
-
`ValidatorFactory[]`. Export the factories and pass those:
|
|
881
|
-
|
|
882
|
-
```ts
|
|
883
|
-
// 7.0
|
|
884
|
-
export const VALIDATOR_FACTORY_EMPTY_INPUT: ValidatorFactory = { … };
|
|
885
|
-
provideSmartNgClient({ namedValidators: [VALIDATOR_FACTORY_EMPTY_INPUT] })
|
|
886
|
-
```
|
|
887
|
-
|
|
888
|
-
The codemod moves the argument into the config and **reports it**, because the entries are
|
|
889
|
-
normally named constants declared in another file and only you know what each one wraps. If
|
|
890
|
-
you miss the note, `TS2322: Type 'Provider' is not assignable to type 'ValidatorFactory'`
|
|
891
|
-
lands on every element.
|
|
892
|
-
|
|
893
|
-
⚠️ **The same line on a `@Component` is not the same thing.**
|
|
894
|
-
`providers: [SmartFilterEditorService]` on a page component asks for one instance *per page*,
|
|
895
|
-
which is usually deliberate — p014 does it in five components. Leave those alone; the codemod
|
|
896
|
-
does.
|
|
897
|
-
|
|
898
|
-
#### The module → standalone mapping
|
|
899
|
-
|
|
900
|
-
What each `Smart*Module` used to export, reduced to what 7.0 still exports publicly. An
|
|
901
|
-
NgModule's `imports:` array accepts standalone components, so this substitution is all a host
|
|
902
|
-
that is still NgModule-based needs to keep compiling — the codemod performs it.
|
|
903
|
-
|
|
904
|
-
| 6.x module | 7.0 imports |
|
|
905
|
-
|---|---|
|
|
906
|
-
| `SmartNgClientModule` | the union of the fourteen it re-exported (everything below except the generic pages, the filter editor, the map and the diagram) — trim it to what your templates use |
|
|
907
|
-
| `SmartComponentLayoutModule` | `SmartComponentLayoutComponent` |
|
|
908
|
-
| `SmartViewContextModule` | `UiActionToolbarComponent`, `UiActionButtonComponent`, `UiActionDialogButtonComponent`, `SmartformComponent`, `SmartfileuploaderComponent`, `SmartFileEditorComponent`, `SmartMultiFileEditorComponent`, `SmartVoiceRecorderComponent`, `SmartEmbeddedSlotDirective`, `HighlightPipe` |
|
|
909
|
-
| `SmartGridModule` | `SmartGridComponent` |
|
|
910
|
-
| `SmarttreeModule` | `SmartTreeComponent` |
|
|
911
|
-
| `SmartdialogModule` | `SmartDialog` |
|
|
912
|
-
| `SmartIconModule` | `SmartIconComponent`, `UiBadgeComponent`, `UiBadgeDirective` |
|
|
913
|
-
| `SmartNavbarModule` | `SmartNavbarComponent` |
|
|
914
|
-
| `SmartFilterModule` | `SmartFilterComponent` |
|
|
915
|
-
| `SmartFilterEditorModule` | `SmartFilterEditorContentComponent` |
|
|
916
|
-
| `SmartExpandableSectionModule` | `ExpandableSectionComponent` |
|
|
917
|
-
| `SmartSessionModule` | `SmartSessionTimerComponent` |
|
|
918
|
-
| `SmartGenericPagesModule` | `SearchPageComponent`, `GenericPageComponent`, `SubjectSelectorComponent`, `InvalidSmartlinkComponent` |
|
|
919
|
-
| `SmartMapModule` | `SmartMapComponent` + `withSmartMap({ engine })` |
|
|
920
|
-
| `SmartDiagramModule` | `SmartDiagramComponent` + `withSmartDiagram({ customOptions })` |
|
|
921
|
-
| `SharedModule` | `SmartTooltipDirective`, `SmartDatePipe`, `SmartDateTimePipe`, `SmartTimePipe` |
|
|
922
|
-
| `SmartValidationModule` | nothing — `forRoot()`'s validators become `namedValidators` |
|
|
923
|
-
| `SmarttableModule` | nothing — `SmarttableComponent` is no longer public; render a `<smart-grid>` |
|
|
924
|
-
| `SmartNavigationModule`, `ComponentFactoryServiceModule` | nothing — they only carried services, which the core provides |
|
|
925
|
-
| `SmartTabGroupModule` | nothing — deleted; it had no component left |
|
|
926
|
-
| `SmartDocuStoreExplorerModule` (`@smartbit4all/document-explorer`) | `SmartDocuStoreExplorerComponent`, `FolderContentComponent`, `PdfViewerDialogPageComponent` |
|
|
927
|
-
|
|
928
|
-
Two things this table will not tell you, and the compiler will not either: an import you no
|
|
929
|
-
longer need is invisible (unused entries in an NgModule `imports:` array are not reported),
|
|
930
|
-
and a name you already bind from elsewhere — a `SmartDatePipe` of your own — must not be
|
|
931
|
-
imported twice. The codemod reports both cases rather than guessing.
|
|
932
|
-
|
|
933
|
-
#### Your `SmartComponent` subclasses silently become OnPush
|
|
934
|
-
|
|
935
|
-
This is an **Angular 22 fact rather than something 7.0.0 introduces**, but it
|
|
936
|
-
lands on you at the same moment, so it is worth stating plainly: `SmartComponent`
|
|
937
|
-
is an abstract `@Component`, and each of your subclasses carries its own
|
|
938
|
-
`@Component` decorator. On Angular 22 a component without an explicit
|
|
939
|
-
`changeDetection` gets the new `OnPush` default. p014 has 75 such subclasses,
|
|
940
|
-
p043 has 70.
|
|
941
|
-
|
|
942
|
-
Let the `ng update` schematic add `changeDetection: ChangeDetectionStrategy.Eager`
|
|
943
|
-
to all of them (that is what the library did), verify your app, and move to
|
|
944
|
-
OnPush deliberately afterwards. A subclass that mutates its own state outside a
|
|
945
|
-
signal and without `markForCheck()` will stop repainting under OnPush.
|
|
946
|
-
|
|
947
|
-
#### Removed from the public API
|
|
948
|
-
|
|
949
|
-
A major is the only chance to narrow the surface, so these are no longer
|
|
950
|
-
exported. All of them are library internals; none is used by p014, p043,
|
|
951
|
-
app-formmate or `@smartbit4all/document-explorer`:
|
|
952
|
-
|
|
953
|
-
- `SmarttableComponent`, `SmarttableService` — use `<smart-grid>`, which renders
|
|
954
|
-
the table internally. **All `SmartTable*` model types stay** (`SmartTableType`,
|
|
955
|
-
`SmartTableInterfaceTypeEnum`, `SmartTableHeader`, …). p043 imports
|
|
956
|
-
`SmarttableComponent` in `ad-groups.component.ts` but never uses it — delete
|
|
957
|
-
that one import line.
|
|
958
|
-
- `GoogleMap`, `LeafletMap`, `AbstractMap` — select an engine with
|
|
959
|
-
`withSmartMap({ engine })`.
|
|
960
|
-
- `SmartFilterParamComponent`, `SmartFilterParamsComponent`,
|
|
961
|
-
`SmartFilterExpressionItemComponent`, `SmartFilterExpressionItemsComponent` —
|
|
962
|
-
internals of `SmartFilterEditorContentComponent`, which stays public.
|
|
963
|
-
- `HoverMenuComponent`, `ClickMenuComponent` — rendered by the ui-action toolbar.
|
|
964
|
-
- `UiActionConfirmDialogComponent`, `UiActionInputDialogComponent` — opened by
|
|
965
|
-
their services, which stay public.
|
|
966
|
-
- `UploadWidgetComponent`, `PhotoCaptureWidgetComponent`,
|
|
967
|
-
`VoiceRecordWidgetComponent` — contents of the ui-action upload dialog.
|
|
968
|
-
- `SmartNgClientService` — deleted; it was an empty stub with no members.
|
|
969
|
-
- The generated `ApiModule`s — they had no usages and their `forRoot()` told you
|
|
970
|
-
to import `HttpClientModule`, which no longer applies.
|
|
971
|
-
|
|
972
|
-
Two internal string tokens are gone as well, replaced by real `InjectionToken`s
|
|
973
|
-
(`'confirmDialogService'`, `'textFieldDialogService'`, `'fileUploadDialogService'`).
|
|
974
|
-
No host provided them, so no action is needed.
|
|
975
|
-
|
|
976
|
-
#### Behaviour notes
|
|
977
|
-
|
|
978
|
-
- `<mat-slide-toggle>` in the form widget no longer carries a `value` binding,
|
|
979
|
-
`<mat-nested-tree-node>` no longer carries an interpolated `matTreeNodeToggle`,
|
|
980
|
-
and the ui-action menus no longer carry `[subActions]`. All four were dead
|
|
981
|
-
bindings that the NgModules' `CUSTOM_ELEMENTS_SCHEMA` had been hiding — they
|
|
982
|
-
wrote a DOM property nobody read. Rendering is unchanged.
|
|
983
|
-
- The dead `ExpandableGridComponent` is deleted (it was declared in
|
|
984
|
-
`SmartGridModule` but its selector was used nowhere).
|
|
985
|
-
|
|
986
|
-
### Widget ↔ SmartComponent: the model routing is inverted (Phase 3.4)
|
|
987
|
-
|
|
988
|
-
A widget no longer waits to be collected by the screen component it lives in: it finds its
|
|
989
|
-
`SmartComponentApiClient` through DI and registers itself. `SmartComponent` carries the
|
|
990
|
-
channel as an inherited host directive, so **no host component has to be changed for the
|
|
991
|
-
routing itself** — everything below is the *legacy surface* that went with the old
|
|
992
|
-
collection path. The protocol is written up in `WIDGETS.md` next to this file; the decision
|
|
993
|
-
record behind it is ADR-0005 in the `platform-angular2` working copy.
|
|
994
|
-
|
|
995
|
-
#### What a host must delete (all of it mechanical, all reported by the compiler)
|
|
996
|
-
|
|
997
|
-
| Delete | Why |
|
|
998
|
-
|---|---|
|
|
999
|
-
| `this.useQueryLists = true;` | The property is gone. Its setter rejected anything but `true`, so every branch behind it was unreachable — this was a no-op that logged a warning. |
|
|
1000
|
-
| `[parentSmartComponent]="this"` on `<smart-component-layout>` | The input is gone; the layout injects the client. `strictTemplates` reports leftovers as **NG8002**. |
|
|
1001
|
-
| `this.addForm(id, smartForm, formComponent)` | Gone. A `<smartform>` that displays the client's model registers itself. Two call styles existed; where the return value was used, it was the `smartForm` argument — use that (which makes those lines self-assignments, so they just go). **Delete the scaffolding around the call too**: in p014 the 21 calls left behind 9 empty `if (this.formX) { }` guards and 3 `new Promise((r) => setTimeout(r, 500)).then(() => { })` wrappers whose only content had been the registration. The compiler does not complain about either. |
|
|
1002
|
-
| `this.widgets.delete(…)` / `this.widgets.set(…)` / `this.formWidgets…` | The maps are gone (they were only written by the legacy path and read by nothing). |
|
|
1003
|
-
| the 4th argument of `addGrid(smartGrid, options, useAsDefaultGrid, componentReference)` | `addGrid()` keeps the options and the default-grid role; the services it used to copy into the grid model (`uiActionService`, `uiActionDescriptorService`, `serviceToUse`, `viewContextService`) are the grid's own DI job now. |
|
|
1004
|
-
| `override getSmartXxxQL()` (e.g. the two `throw new Error` overrides in a `FilterPageService`) | The eight abstract getters are gone from `SmartComponentApiClient`. |
|
|
1005
|
-
| `this.getSmartGridsQL()` and friends | If a component really wants its own widgets, it declares the view query itself: `@ViewChildren(SmartGridComponent) grids!: QueryList<SmartGridComponent>` — same reach as the removed getter had. |
|
|
1006
|
-
| `handleDataChangeSubscriptions()` | Gone. A form publishes its own value-change keys when it constructs. |
|
|
1007
|
-
|
|
1008
|
-
Removed from the public API in the same step, in case a host imported them directly:
|
|
1009
|
-
`SmartComponentLayoutUtility` and its `EmbeddedSlotInfo` interface (the whole file — its
|
|
1010
|
-
collectors walked the layout tree for widgets, which nothing does now), and the `[parent]`
|
|
1011
|
-
inputs of `<smart-map>` / `<smart-diagram>` (they were bound to `parentSmartComponent` and read
|
|
1012
|
-
by nothing; both widgets resolve the client themselves). `initActions()` also runs less often:
|
|
1013
|
-
only when the model or its action list changes, no longer after every layout render, grid view
|
|
1014
|
-
init and filter model change — a host override that relied on one of those extra calls has to
|
|
1015
|
-
key off `dataChanged` instead.
|
|
1016
|
-
|
|
1017
|
-
The [codemod](#migrating-a-host-the-codemod) deletes the first two rows and the last one, and
|
|
1018
|
-
lists the rest — `addForm()`, the 4th `addGrid()` argument, the getter overrides — with their
|
|
1019
|
-
line numbers, because each of those is a small decision rather than a substitution.
|
|
1020
|
-
|
|
1021
|
-
#### What stays
|
|
1022
|
-
|
|
1023
|
-
- `SmartService` — a view-less client. Its `@ViewChildren` never populated anyway; they are
|
|
1024
|
-
simply gone now.
|
|
1025
|
-
- `initActions()` — still the hook a host overrides to build its own action list. The base
|
|
1026
|
-
implementation only asks for a render; toolbars pull their actions themselves.
|
|
1027
|
-
- `setUpDefaultTree()` / `createTreeService()`, `addGrid()`, `submitForms()`,
|
|
1028
|
-
`getInvalidFields()`, `uiActionModels` (now a getter over the model's actions).
|
|
1029
|
-
|
|
1030
|
-
#### `uiActionModels` is read-only now — assigning to it no longer compiles
|
|
1031
|
-
|
|
1032
|
-
It became a getter over the `actionModels` computed, so a host that *replaced* the list in
|
|
1033
|
-
place gets `TS2540: Cannot assign to 'uiActionModels' because it is a read-only property`.
|
|
1034
|
-
Measured in **p014 and p043**, `document-storage-editor.component.ts`:
|
|
1035
|
-
|
|
1036
|
-
```ts
|
|
1037
|
-
// before — no longer compiles
|
|
1038
|
-
this.uiActionModels = this.uiActionModels.filter((a) => a.uiAction.code !== 'CANCEL');
|
|
1039
|
-
|
|
1040
|
-
// after — filter into a field of your own and bind it
|
|
1041
|
-
protected readonly visibleActions = signal<UiActionModel[]>([]);
|
|
1042
|
-
// …
|
|
1043
|
-
this.visibleActions.set(this.uiActionModels.filter((a) => a.uiAction.code !== 'CANCEL'));
|
|
1044
|
-
```
|
|
1045
|
-
|
|
1046
|
-
```html
|
|
1047
|
-
<smart-ui-action-toolbar [uiActionModels]="visibleActions()"></smart-ui-action-toolbar>
|
|
1048
|
-
```
|
|
1049
|
-
|
|
1050
|
-
A plain field works just as well; what matters is that the **array reference changes**, which
|
|
1051
|
-
is what tells the toolbar to re-render. See also the entry-freeze note in the Phase 3.7
|
|
1052
|
-
section.
|
|
1053
|
-
|
|
1054
|
-
#### Behavioural deltas, accepted
|
|
1055
|
-
|
|
1056
|
-
- **Wider reach.** A widget in a host's *own* sub-component now participates, where a view
|
|
1057
|
-
query could not see it (measured on p014/p043: 5 toolbars and 3–4 forms per host). A form
|
|
1058
|
-
takes part only if it displays the client's component model, which is what the forms the
|
|
1059
|
-
queries used to find have in common; a form that renders something else of its own (a
|
|
1060
|
-
search box, a field editor) does not.
|
|
1061
|
-
- Two widgets sharing one identifier both reload; the old map kept only the last.
|
|
1062
|
-
- `getInvalidFields()` collects in subscription order rather than view order — visible only
|
|
1063
|
-
in the order of the names in the validation dialog.
|
|
1064
|
-
- A toolbar entry is tracked by a synthesized key (action code + ordinal among entries with
|
|
1065
|
-
the same code), so a button's DOM node — with its focus and ripple — follows its action
|
|
1066
|
-
across reordering. Rendering is otherwise identical.
|
|
1067
|
-
|
|
1068
|
-
#### `[smartComponentDetached]`
|
|
1069
|
-
|
|
1070
|
-
The one brake on the automatic registration. Put it on an element whose subtree deliberately
|
|
1071
|
-
renders something other than the client's model; widgets below it resolve an empty slot and
|
|
1072
|
-
neither reload nor take part in submit/validation.
|
|
1073
|
-
|
|
1074
|
-
```html
|
|
1075
|
-
<div smartComponentDetached>
|
|
1076
|
-
<smart-grid [smartGrid]="ownGrid" [uuid]="ownUuid"></smart-grid>
|
|
1077
|
-
</div>
|
|
1078
|
-
```
|
|
1079
|
-
|
|
1080
|
-
#### `SmartSubject` is deleted
|
|
1081
|
-
|
|
1082
|
-
It was a no-op wrapper over `Subject`: `subscription.add(this.unsubscribe$.subscribe())`
|
|
1083
|
-
registers the teardown in the wrong direction, so after `destroy$.next()`, `.complete()` or
|
|
1084
|
-
`.unsubscribe()` subscribers still received values. Replace `new SmartSubject(destroy$)`
|
|
1085
|
-
with `new Subject()`; where a subscription really must die with a component, use
|
|
1086
|
-
`takeUntil(this._destroy$)` (which is what actually stopped delivery all along) or
|
|
1087
|
-
`takeUntilDestroyed()`. No host used it.
|
|
1088
|
-
|
|
1089
|
-
### Toolbars resolve their own actions and own the execution context (Phase 3.5)
|
|
1090
|
-
|
|
1091
|
-
One rule decides which actions a toolbar shows, and the toolbar — not each list entry —
|
|
1092
|
-
says who performs them. `WIDGETS.md`, *Toolbars*, is the short version; the decision record
|
|
1093
|
-
behind it is ADR-0006 in the `platform-angular2` working copy.
|
|
1094
|
-
|
|
1095
|
-
> A toolbar renders the actions **addressed to its `id`** (`uiAction.toolbar == id`). The
|
|
1096
|
-
> list comes from an explicit `[uiActionModels]` binding if there is one, otherwise from the
|
|
1097
|
-
> screen component above it in the DOM. **Without an `id` it never pulls** — "unaddressed" is
|
|
1098
|
-
> not an address, so a toolbar with neither an id nor a binding shows nothing.
|
|
1099
|
-
|
|
1100
|
-
`[uiActionModels]="uiActionModels"` therefore keeps meaning "put my page's unaddressed
|
|
1101
|
-
actions here" and needs no change.
|
|
1102
|
-
|
|
1103
|
-
#### `UseUiAction` is deleted; `UseUiAction2` is now `UiActionExecutor`
|
|
1104
|
-
|
|
1105
|
-
There is one executor interface. If a host class implements `UseUiAction` — a `submit` /
|
|
1106
|
-
`reSubscribeToChange` subject handshake — replace the two subjects with the two methods:
|
|
1107
|
-
|
|
1108
|
-
```ts
|
|
1109
|
-
// before
|
|
1110
|
-
export class DelegateInboxDialogService implements UseUiAction {
|
|
1111
|
-
submit: Subject<void> = new Subject();
|
|
1112
|
-
reSubscribeToChange: Subject<void> = new Subject();
|
|
1113
|
-
…
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
// after
|
|
1117
|
-
export class DelegateInboxDialogService implements UiActionExecutor {
|
|
1118
|
-
submitForm(validate: boolean): void {
|
|
1119
|
-
this.submit.next(); // keep the subject if a component subscribes to it
|
|
1120
|
-
}
|
|
1121
|
-
getInvalidFields(): SmartFormInvalidFields {
|
|
1122
|
-
return { invalidFieldKeys: [], invalidFieldNames: [] };
|
|
1123
|
-
}
|
|
1124
|
-
…
|
|
1125
|
-
}
|
|
1126
|
-
```
|
|
1127
|
-
|
|
1128
|
-
`Subject.next()` is synchronous, so a component that subscribed to `submit` has already run
|
|
1129
|
-
by the time `submitForm()` returns — which is exactly what the old handshake tried to
|
|
1130
|
-
express. Keep `reSubscribeToChange` only if something subscribes to it; the library no longer
|
|
1131
|
-
fires it.
|
|
1132
|
-
|
|
1133
|
-
This also **fixes a silent hang**: the old branch `await`ed `submit.toPromise()`, which only
|
|
1134
|
-
settles when somebody `complete()`s the subject. Three of the five implementors never did, so
|
|
1135
|
-
a `submit`/`model` action on those services waited forever.
|
|
1136
|
-
|
|
1137
|
-
#### `UiActionService.execute()` lost its first argument
|
|
1138
|
-
|
|
1139
|
-
It was always `uiActionModel.uiAction`:
|
|
1140
|
-
|
|
1141
|
-
```ts
|
|
1142
|
-
- this.uiActionService.execute(uiActionModel.uiAction, uiActionModel);
|
|
1143
|
-
+ this.uiActionService.execute(uiActionModel);
|
|
1144
|
-
```
|
|
1145
|
-
|
|
1146
|
-
#### The execution context moved from the entry to the toolbar
|
|
1147
|
-
|
|
1148
|
-
| what | bind on the toolbar | entry field (still honoured) |
|
|
1149
|
-
|---|---|---|
|
|
1150
|
-
| who performs the action | `[executor]` | `serviceToUse` — **deprecated** |
|
|
1151
|
-
| which widget it runs against | `[widgetId]` | `widgetId` — **deprecated**, wins if set |
|
|
1152
|
-
| which row inside it | `[nodeId]` | `nodeId` — **deprecated**, wins if set |
|
|
1153
|
-
| extra params for every action | `[actionParams]` | (clone the `UiAction` — no longer necessary) |
|
|
1154
|
-
|
|
1155
|
-
`[executor]` defaults to the screen component the toolbar sits under, which is what almost
|
|
1156
|
-
every entry named. Bind it only when the actions belong to another API — a tree service, a
|
|
1157
|
-
filter editor, a dialog service of your own:
|
|
1158
|
-
|
|
1159
|
-
```html
|
|
1160
|
-
<smart-ui-action-toolbar [uiActionModels]="actions" [executor]="myService"></smart-ui-action-toolbar>
|
|
1161
|
-
```
|
|
1162
|
-
|
|
1163
|
-
`[actionParams]` replaces cloning a `UiAction` per row just to inject a model:
|
|
1164
|
-
|
|
1165
|
-
```html
|
|
1166
|
-
<smart-ui-action-toolbar
|
|
1167
|
-
[uiActionModels]="rowActions"
|
|
1168
|
-
[widgetId]="gridId"
|
|
1169
|
-
[nodeId]="row.id"
|
|
1170
|
-
[actionParams]="{ model: row }"
|
|
1171
|
-
></smart-ui-action-toolbar>
|
|
1172
|
-
```
|
|
1173
|
-
|
|
1174
|
-
#### Removed from the public API
|
|
1175
|
-
|
|
1176
|
-
| Removed | Replacement |
|
|
1177
|
-
|---|---|
|
|
1178
|
-
| `UseUiAction` | `UiActionExecutor` (see above) |
|
|
1179
|
-
| the third `SmartTable` constructor parameter and `getServiceToUse()` | nothing — the executor no longer travels through the table (0 host call sites) |
|
|
1180
|
-
| `SmartformComponent.getToolbars()` | nothing collects toolbars any more; each one resolves its own actions |
|
|
1181
|
-
| `ISmartFilterEditorService.get/setComplexToolbars()` and the `submit` / `reSubscribeToChange` members | as above |
|
|
1182
|
-
| `FileEditorToolbarComponent` | a plain `<smart-ui-action-toolbar>` with `[actionParams]` |
|
|
1183
|
-
| `SmartGridComponent.setupToolbar()` / `.toolbar` / `.headerToolbar` | the id is a binding; there is nothing to reach into |
|
|
1184
|
-
|
|
1185
|
-
#### Behavioural deltas, accepted
|
|
1186
|
-
|
|
1187
|
-
- A widget renders a toolbar exactly when its model carries a `toolbarId`, with no type list.
|
|
1188
|
-
A `toolbarId` on a type whose template has no place for a toolbar (`RECORDING_UPLOADER`)
|
|
1189
|
-
now logs a warning instead of being silently dropped.
|
|
1190
|
-
- Only the actions a toolbar actually shows are scheduled. Since 3.4 a toolbar armed every
|
|
1191
|
-
scheduled action of the view, so one action could fire from several toolbars at once.
|
|
1192
|
-
- A grid row's actions are no longer cloned per row; the row model travels as
|
|
1193
|
-
`[actionParams]`. The multi-file editor's per-file toolbars appear when their own list
|
|
1194
|
-
resolves rather than when a hidden sibling's did, and the tree's `doAction` clones instead
|
|
1195
|
-
of mutating the caller's `UiAction`.
|
|
1196
|
-
|
|
1197
|
-
### The form widget's rendering decisions (Phase 3.6)
|
|
1198
|
-
|
|
1199
|
-
Nothing here needs a code change in a host — there is no API to adapt to. What
|
|
1200
|
-
changes is what the widget puts on the page, so read this before you compare
|
|
1201
|
-
screenshots.
|
|
1202
|
-
|
|
1203
|
-
#### Every widget carried a spare empty wrapper; it is gone
|
|
1204
|
-
|
|
1205
|
-
The `COMPONENT` branch's wrapper had no type guard, so
|
|
1206
|
-
`<div class="widgetContainer"><div class="widgetContent"><ng-template
|
|
1207
|
-
#customComponent>` rendered after **every** widget of every type. Measured on the
|
|
1208
|
-
playground: 14 widgets, 28 `.widgetContainer` — one spare, empty, per widget.
|
|
1209
|
-
|
|
1210
|
-
The library's own CSS puts no box properties on `.widgetContainer`, so most pages
|
|
1211
|
-
will look identical. **Yours may not**, if you style that class with anything that
|
|
1212
|
-
occupies space. Both in-house hosts do, on one page:
|
|
1213
|
-
|
|
1214
|
-
```css
|
|
1215
|
-
/* p014 + p043, dossier-publication.component.css */
|
|
1216
|
-
:host ::ng-deep .widgetContainer { margin-bottom: 0.5rem !important; }
|
|
1217
|
-
```
|
|
1218
|
-
|
|
1219
|
-
There, every widget loses half a rem of trailing space. Grep your stylesheets for
|
|
1220
|
-
`.widgetContainer` and check the pages the matching selectors reach. Rules that
|
|
1221
|
-
need content still match what they always matched — an empty wrapper never
|
|
1222
|
-
satisfied `.label.widgetContainer` or `.widgetContainer:has(…)`.
|
|
1223
|
-
|
|
1224
|
-
#### `MATRIX` and `YOUTUBE_PLAYER` now obey `isVisible` and get their `cssClass`
|
|
1225
|
-
|
|
1226
|
-
Those two branches sat outside the `@if (isVisible)` guard and outside the
|
|
1227
|
-
`<div [ngClass]="cssClass" class="container">` every other type lives in, so
|
|
1228
|
-
`isVisible: false` did not hide them and `cssClass` never reached them. Both are
|
|
1229
|
-
inside now. If a backend view relies on a matrix rendering while marked invisible,
|
|
1230
|
-
it will stop rendering.
|
|
1231
|
-
|
|
1232
|
-
#### A container keeps its invisible children
|
|
1233
|
-
|
|
1234
|
-
A `CONTAINER` widget's `ngOnInit` used to overwrite `widgetInstance.valueList`
|
|
1235
|
-
with the visible subset, destroying the invisible children in the model the
|
|
1236
|
-
backend sent. The visible subset is derived per check now, so the model stays
|
|
1237
|
-
whole and a later `applyConstraints()` that flips a child's `isVisible` brings it
|
|
1238
|
-
back — which it could not before. The DOM is unchanged: the same children render.
|
|
1239
|
-
|
|
1240
|
-
Side effect worth knowing: the model walks that recurse into a container's
|
|
1241
|
-
`valueList` (`applyComponentConstraints`, `copyValueFromFormToWidgetBeans`,
|
|
1242
|
-
`translateWidgets`) now see the invisible children too. Their form controls always
|
|
1243
|
-
existed — `createFormControls` runs over the whole model before any widget renders
|
|
1244
|
-
— so this only makes the widget beans agree with the controls.
|
|
1245
|
-
|
|
1246
|
-
#### A YouTube shorts link resolves
|
|
1247
|
-
|
|
1248
|
-
`parseYoutubeUrl` threw away its own `replace('/shorts/', '/watch?v=')`, so a
|
|
1249
|
-
shorts link produced no player. It plays now.
|
|
1250
|
-
|
|
1251
|
-
#### Removed from the public API
|
|
1252
|
-
|
|
1253
|
-
| Removed | Replacement |
|
|
1254
|
-
|---|---|
|
|
1255
|
-
| `SmartformwidgetComponent` | nothing — no host imported the class; `<smartform>` still renders it. 7.1 splits it into per-type components |
|
|
1256
|
-
| `SmartWidgetSettings` (its only member was `static useUtc`) | nothing — **delete the assignment** |
|
|
1257
|
-
|
|
1258
|
-
3.6 moved the `useUtc` static into `SmartNgClientConfig.useUtcDates`; **3.8 then removed the
|
|
1259
|
-
field altogether**, together with the moment adapter it fed. If you set
|
|
1260
|
-
`SmartWidgetSettings.useUtc = true` before bootstrap, delete that line and read
|
|
1261
|
-
[Dates §4](#4-smartngclientconfiguseutcdates-is-removed) — the timezone contract is fixed now,
|
|
1262
|
-
and the flag defaulted to `false` everywhere anyway.
|
|
1263
|
-
|
|
1264
|
-
#### What deliberately did not change
|
|
1265
|
-
|
|
1266
|
-
A required field is marked in both label positions, by two different mechanisms:
|
|
1267
|
-
a shown label (`showLabel: true`) is the widget's own `<h4>` and carries a literal
|
|
1268
|
-
` *`; a `mat-label` sits inside a `mat-form-field`, which renders
|
|
1269
|
-
`.mat-mdc-form-field-required-marker` from the control's own validators. That is
|
|
1270
|
-
why `getWidgetLabel` ties its asterisk to `showLabel` — dropping the condition
|
|
1271
|
-
would mark the floating label twice.
|
|
1272
|
-
|
|
1273
|
-
### Change detection: frozen action entries, no zone.js (Phase 3.7)
|
|
1274
|
-
|
|
1275
|
-
7.0 runs without zone.js. Every component of the library is `OnPush` — nothing is checked
|
|
1276
|
-
just because something, somewhere, ticked — and the library says when its own state changed.
|
|
1277
|
-
Two consequences reach a host.
|
|
1278
|
-
|
|
1279
|
-
#### A `UiActionModel` is frozen: build an entry, never edit one
|
|
1280
|
-
|
|
1281
|
-
Every field is `readonly`, and `[uiActionModels]` takes `readonly UiActionModel[]`.
|
|
1282
|
-
|
|
1283
|
-
```ts
|
|
1284
|
-
// before — compiles on 6.x, and on 7.0 changes nothing on screen
|
|
1285
|
-
entry.cssClass = 'active-nav-action';
|
|
1286
|
-
|
|
1287
|
-
// after
|
|
1288
|
-
this.actions = this.actions.map((a) =>
|
|
1289
|
-
a.uiAction.code === code ? { ...a, cssClass: 'active-nav-action' } : a
|
|
1290
|
-
);
|
|
1291
|
-
```
|
|
1292
|
-
|
|
1293
|
-
Measured across the 12 hosts in scope (2026-07-28): **72 sites in 7 hosts**, all of them
|
|
1294
|
-
`cssClass` — p009-angular alone has 38, p014 and p043 12 each. The codemod prints every one
|
|
1295
|
-
of them with its line number; it cannot rewrite them, because only you know which array the
|
|
1296
|
-
entry belongs to.
|
|
1297
|
-
|
|
1298
|
-
The compile error and the fix are the same thing. Writing into an entry that a toolbar is
|
|
1299
|
-
already rendering never reached the screen under zone.js either — it worked only because
|
|
1300
|
-
some other event happened to tick the application. Rebuilding the array is what actually
|
|
1301
|
-
re-renders.
|
|
1302
|
-
|
|
1303
|
-
**The gap this does not close.** `this.actions[0] = { ...this.actions[0], cssClass: 'x' }`
|
|
1304
|
-
compiles: `actions` is your field, and the freeze only covers the entries. It keeps the same
|
|
1305
|
-
array reference, so the toolbar does not re-render. Reassign the array — or, if you hold it
|
|
1306
|
-
in a `signal`, `update()` it.
|
|
1307
|
-
|
|
1308
|
-
The descriptor a toolbar resolves is no longer written back into the entry either. Nothing
|
|
1309
|
-
outside the toolbar read it; if you did, resolve it yourself through
|
|
1310
|
-
`UiActionDescriptorService.getActionDescriptor(uiAction)` — which is
|
|
1311
|
-
[synchronous in 7.0](#getactiondescriptor-is-synchronous-phase-39).
|
|
1312
|
-
|
|
1313
|
-
#### Client-side translation is gone
|
|
1314
|
-
|
|
1315
|
-
`SmartTranslateService`, `viewContext.translateService` and the `translateServiceChanged`
|
|
1316
|
-
subject are removed, together with `originalLabel` / `originalPlaceholder` on the widget
|
|
1317
|
-
interfaces. No measured host assigned any of them — the three references in p014/p043 were
|
|
1318
|
-
unused imports — and the backend localizes server-side.
|
|
1319
|
-
|
|
1320
|
-
What the frontend authors itself is now configuration:
|
|
1321
|
-
|
|
1322
|
-
```ts
|
|
1323
|
-
provideSmartNgClient({
|
|
1324
|
-
errorDialog: { title: 'Hiba', message: 'Váratlan hiba történt.', buttonLabel: 'Rendben' },
|
|
1325
|
-
});
|
|
1326
|
-
```
|
|
1327
|
-
|
|
1328
|
-
`SmartViewContextService.getSmartViewContextApiErrorByCode()` is no longer `async`.
|
|
1329
|
-
|
|
1330
|
-
#### If your host still bootstraps with zone.js
|
|
1331
|
-
|
|
1332
|
-
Nothing forces you to drop it — the library works either way, because it no longer depends
|
|
1333
|
-
on an application-wide tick. The playground runs `provideZonelessChangeDetection()` with no
|
|
1334
|
-
`zone.js` polyfill, and that is the configuration 7.0 is tested in.
|
|
1335
|
-
|
|
1336
|
-
If you do go zoneless, the same rule applies to your own components: state written from a
|
|
1337
|
-
subscription, a promise or a timer needs a signal or a `markForCheck()`; state written from a
|
|
1338
|
-
template event or an input does not.
|
|
1339
|
-
|
|
1340
|
-
### Dates: moment → date-fns, and the timezone contract (Phase 3.8)
|
|
1341
|
-
|
|
1342
|
-
**This section needs action at install time.** The reasoning is ADR-0008 in the
|
|
1343
|
-
`platform-angular2` working copy; the contract itself is stated in §4 below.
|
|
1344
|
-
|
|
1345
|
-
#### 1. Swap the peer dependency
|
|
1346
|
-
|
|
1347
|
-
`@angular/material-moment-adapter` is out of the library's `peerDependencies`;
|
|
1348
|
-
`@angular/material-date-fns-adapter` and `date-fns` are in. `date-fns` is listed explicitly
|
|
1349
|
-
rather than left transitive, because the library imports `date-fns/locale` itself.
|
|
1350
|
-
|
|
1351
|
-
```bash
|
|
1352
|
-
npm uninstall @angular/material-moment-adapter moment
|
|
1353
|
-
npm install @angular/material-date-fns-adapter@^22.0.0 date-fns@^4
|
|
1354
|
-
```
|
|
1355
|
-
|
|
1356
|
-
If your own code imports moment for something unrelated, keep it — nothing here forces its
|
|
1357
|
-
removal, only the adapter's.
|
|
1358
|
-
|
|
1359
|
-
**But check *how* it imports moment.** `import * as moment from 'moment'` stops being callable
|
|
1360
|
-
under `moduleResolution: "bundler"`, which the Angular 20 hop turns on: moment is an
|
|
1361
|
-
`export =` module, and a namespace import of one is not a callable value. The symptom is
|
|
1362
|
-
**`TS2349: This expression is not callable`** on every `moment(...)` call, and it arrives at the
|
|
1363
|
-
Angular 20 hop rather than at this step. Two ways out:
|
|
1364
|
-
|
|
1365
|
-
- `esModuleInterop: true` plus `import moment from 'moment'` — correct, but it changes how every
|
|
1366
|
-
CommonJS import in the app resolves, which is a wide blast radius for one file.
|
|
1367
|
-
- Replace the calls. Worth checking first how much moment is actually doing: in p014 the three
|
|
1368
|
-
surviving calls were `moment(new Date()).toDate()` (i.e. `new Date()`) and two moments compared
|
|
1369
|
-
with `<=` (`Date` compares through `valueOf()` identically). All three became plain `Date`s, and
|
|
1370
|
-
**nothing under `src/` imported moment any more** — so the `npm uninstall … moment` above was
|
|
1371
|
-
right for that host after all, just for a reason nobody predicted.
|
|
1372
|
-
|
|
1373
|
-
#### 2. Leave your `MAT_DATE_LOCALE` provider alone
|
|
1374
|
-
|
|
1375
|
-
If you have this line — p014 and p043 both do — **keep it as it is**:
|
|
1376
|
-
|
|
1377
|
-
```ts
|
|
1378
|
-
{ provide: MAT_DATE_LOCALE, useValue: 'hu-HU' },
|
|
1379
|
-
```
|
|
1380
|
-
|
|
1381
|
-
The strict reading of the date-fns adapter would require a `Locale` **object** here, and a bare
|
|
1382
|
-
string would throw inside date-fns the first time a user opened a date field. The library's
|
|
1383
|
-
`SmartDateFnsAdapter` accepts both: it resolves `'hu'` / `'hu-HU'` / `'en'` / `'en-US'`
|
|
1384
|
-
(case-insensitively) to a date-fns locale, passes a real `Locale` object through untouched, and
|
|
1385
|
-
falls back to Hungarian with a `console.warn` for anything else.
|
|
1386
|
-
|
|
1387
|
-
So: **hosts on Hungarian or English need no change.** For any other language, provide a real
|
|
1388
|
-
date-fns locale:
|
|
1389
|
-
|
|
1390
|
-
```ts
|
|
1391
|
-
import { de } from 'date-fns/locale';
|
|
1392
|
-
…
|
|
1393
|
-
{ provide: MAT_DATE_LOCALE, useValue: de },
|
|
1394
|
-
```
|
|
1395
|
-
|
|
1396
|
-
`SmartNgClientConfig.dateLocale` is unchanged — still a `string`, still defaulting to `'hu-HU'`.
|
|
1397
|
-
|
|
1398
|
-
#### 3. If you override `MAT_DATE_FORMATS`, your format strings will throw — and you probably want to delete the override
|
|
1399
|
-
|
|
1400
|
-
Measured across 12 hosts on 2026-07-28: three of them (**p014**, **p043**, **p009-angular**)
|
|
1401
|
-
provide their own formats next to the locale, and two more (`app-vlab`, `app-tournament`) have the
|
|
1402
|
-
same line commented out:
|
|
1403
|
-
|
|
1404
|
-
```ts
|
|
1405
|
-
{ provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
|
|
1406
|
-
```
|
|
1407
|
-
|
|
1408
|
-
with
|
|
1409
|
-
|
|
1410
|
-
```ts
|
|
1411
|
-
export const MY_FORMATS = {
|
|
1412
|
-
parse: { dateInput: 'YYYY.MM.DD' },
|
|
1413
|
-
display: { dateInput: 'YYYY.MM.DD', monthYearLabel: 'YYYY',
|
|
1414
|
-
dateA11yLabel: 'LL', monthYearA11yLabel: 'YYYY' },
|
|
1415
|
-
};
|
|
1416
|
-
```
|
|
1417
|
-
|
|
1418
|
-
Those are **moment tokens**. date-fns treats `YYYY`, `DD`, `YY` and `D` as *protected* and throws a
|
|
1419
|
-
`RangeError` on them, and its `LL` means something else entirely (stand-alone month, not a
|
|
1420
|
-
localized long date). So this override **crashes at runtime, on the first screen with a date
|
|
1421
|
-
field** — the same failure shape as the string locale above, and just as invisible to the compiler.
|
|
1422
|
-
|
|
1423
|
-
**The fix is almost certainly deletion.** That `MY_FORMATS` exists to render `YYYY.MM.DD`, which is
|
|
1424
|
-
exactly what the library's own `SMART_DATE_FORMATS` now produces (`2026.07.28.`). Drop the
|
|
1425
|
-
provider and the constant and you get the format you wanted, plus the more forgiving parsing.
|
|
1426
|
-
|
|
1427
|
-
If you genuinely need a different format, translate the tokens rather than copying them:
|
|
1428
|
-
|
|
1429
|
-
| moment | date-fns |
|
|
1430
|
-
|---|---|
|
|
1431
|
-
| `YYYY` | `yyyy` |
|
|
1432
|
-
| `DD` | `dd` |
|
|
1433
|
-
| `LL` | `PP` (or spell it out: `yyyy. MMMM d.`) |
|
|
1434
|
-
| `MMM YYYY` | `yyyy. LLL` |
|
|
1435
|
-
|
|
1436
|
-
Note that the library's `parse.dateInput` is an **array** — `DateFnsAdapter._parse` tries each entry
|
|
1437
|
-
in turn (and ISO-8601 first), so you can list several accepted spellings instead of one.
|
|
1438
|
-
|
|
1439
|
-
#### 4. `SmartNgClientConfig.useUtcDates` is removed
|
|
1440
|
-
|
|
1441
|
-
Delete it if you set it. No known host does. It existed for a client-configurable timezone that
|
|
1442
|
-
was abandoned as a requirement; the contract is now fixed and explicit:
|
|
1443
|
-
|
|
1444
|
-
> **The browser's zone on screen. Zulu (`…Z`) on the wire. The server converts.**
|
|
1445
|
-
|
|
1446
|
-
This is unchanged behaviour in practice — `useUtcDates` defaulted to `false`, and the moment
|
|
1447
|
-
adapter with `useUtc: false` behaves exactly like a plain `Date`.
|
|
1448
|
-
|
|
1449
|
-
#### 5. What a date widget's value now *is*
|
|
1450
|
-
|
|
1451
|
-
Form control values from the date pickers are plain **`Date`** objects, where they were
|
|
1452
|
-
`Moment` objects. The values reach your backend through a `UiActionRequest` param, so the wire
|
|
1453
|
-
format is `JSON.stringify` of that value — and `Date.toJSON()` emits the same ISO-8601 UTC string
|
|
1454
|
-
`Moment.toJSON()` did. **Nothing changes on the wire.** Only host code that read a value out of a
|
|
1455
|
-
form control and called a moment method on it (`.format()`, `.add()`, `.startOf()`) needs
|
|
1456
|
-
rewriting; the platform APIs never exposed a `Moment` in a signature.
|
|
1457
|
-
|
|
1458
|
-
#### 6. The one visible difference
|
|
1459
|
-
|
|
1460
|
-
Date inputs render **`2026.07.28.`** where 6.x rendered `2026.7.28.` — the documented `YYYY.MM.DD`
|
|
1461
|
-
convention. The old rendering was not a decision: `MAT_MOMENT_DATE_FORMATS` used moment's `'l'`,
|
|
1462
|
-
which strips the zero padding off the Hungarian `L`. Typed input is **more** forgiving than 6.x,
|
|
1463
|
-
not less: `2026.07.28.`, `2026.07.28`, `2026.7.28.`, `2026.7.28` and ISO-8601 are all accepted.
|
|
1464
|
-
|
|
1465
|
-
The month picker is unchanged (`07/2026`), and grid/table date columns were never affected —
|
|
1466
|
-
`SmartDatePipe` and friends extend Angular's own `DatePipe` and never used moment.
|
|
1467
|
-
|
|
1468
|
-
**Verified on a running host** (p014, 2026-07-29, live backend, `Europe/Budapest`). Picking
|
|
1469
|
-
2026-07-15 in a `Kezdődátum` field gave, end to end:
|
|
1470
|
-
|
|
1471
|
-
| | |
|
|
1472
|
-
|---|---|
|
|
1473
|
-
| input | `2026.07.15.` |
|
|
1474
|
-
| widget value | `instanceof Date` — not a moment, not a luxon `DateTime` |
|
|
1475
|
-
| `JSON.stringify` of that value | `"2026-07-14T22:00:00.000Z"` |
|
|
1476
|
-
| `getHours()` | `0` |
|
|
1477
|
-
|
|
1478
|
-
That third row is the whole contract in one line: the value's `toJSON()` **is** the wire format
|
|
1479
|
-
(see §4), and it is zulu, while the screen and `getHours()` stay local — 15 July 00:00 CEST is
|
|
1480
|
-
14 July 22:00 UTC. A grid column on the same host rendered `2026.04.20 0:03`, confirming the
|
|
1481
|
-
untouched pipe path.
|
|
1482
|
-
|
|
1483
|
-
### HTTP: the library no longer forces the XHR backend (Phase 3.8)
|
|
1484
|
-
|
|
1485
|
-
`provideSmartNgClient()` used to pass `withXhr()` to its internal `provideHttpClient()` call,
|
|
1486
|
-
overriding the Angular 22 default on your behalf. It no longer does — the application runs on the
|
|
1487
|
-
framework default **fetch** backend.
|
|
1488
|
-
|
|
1489
|
-
For almost every host this is invisible. The one behavioural difference that matters: **the fetch
|
|
1490
|
-
backend does not support upload progress.** A request with `reportProgress: true` and a body
|
|
1491
|
-
throws `NG2824` instead of emitting `HttpUploadProgress` events.
|
|
1492
|
-
|
|
1493
|
-
Measured 2026-07-28 across 12 hosts: **`reportProgress: true` and `observe: 'events'` appear
|
|
1494
|
-
nowhere** outside the generated API services' unused parameters. So this is a theoretical risk for
|
|
1495
|
-
the current host set, not a live one. If *your* code does ask for upload progress, pass the
|
|
1496
|
-
feature back in:
|
|
1497
|
-
|
|
1498
|
-
```ts
|
|
1499
|
-
import { withXhr } from '@angular/common/http';
|
|
1500
|
-
|
|
1501
|
-
provideSmartNgClient({ httpFeatures: [withXhr()] })
|
|
1502
|
-
```
|
|
1503
|
-
|
|
1504
|
-
The upside is that the "**do not call `provideHttpClient()` twice**" trap this guide warned about
|
|
1505
|
-
is gone rather than merely documented: a second call now re-provides the same backend the library
|
|
1506
|
-
already uses. The library's interceptors were never at risk from it — they are `multi` providers.
|
|
1507
|
-
|
|
1508
|
-
### `getActionDescriptor()` is synchronous (Phase 3.9)
|
|
1509
|
-
|
|
1510
|
-
`UiActionDescriptorService.getActionDescriptor(uiAction)` returns a `UiActionDescriptor`.
|
|
1511
|
-
It used to return a `Promise<UiActionDescriptor>`, and it was `async` for exactly one
|
|
1512
|
-
reason: the client-side translation layer that [Phase 3.7](#client-side-translation-is-gone)
|
|
1513
|
-
retired. Nothing it reads has been asynchronous since, so every caller was awaiting a value
|
|
1514
|
-
that was already there.
|
|
1515
|
-
|
|
1516
|
-
**If you only `await` the call, you are already fine.** `await` on a non-promise is legal
|
|
1517
|
-
TypeScript, so this compiles and behaves identically before and after. The codemod drops the
|
|
1518
|
-
`await` anyway, so the code stops claiming an asynchrony that is not there:
|
|
1519
|
-
|
|
1520
|
-
```ts
|
|
1521
|
-
const descriptor = await this.uiActionDescriptor.getActionDescriptor(uiAction); // 6.x
|
|
1522
|
-
const descriptor = this.uiActionDescriptor.getActionDescriptor(uiAction); // 7.0
|
|
1523
|
-
```
|
|
1524
|
-
|
|
1525
|
-
Two shapes genuinely break. Measured 2026-07-28 across the 12 hosts in scope: **5 hosts,
|
|
1526
|
-
7 sites** — and none of them in a host's own `getActionDescriptor**s**()` map helper, which
|
|
1527
|
-
is an unrelated API and is left alone.
|
|
1528
|
-
|
|
1529
|
-
**1. A subclass that overrides it** — `TS2416`, because `Promise<UiActionDescriptor>` is not
|
|
1530
|
-
assignable to `UiActionDescriptor`. Found in three hosts, as a byte-identical
|
|
1531
|
-
`MdmUiActionDescriptorService`. **The codemod rewrites this one.**
|
|
1532
|
-
|
|
1533
|
-
```ts
|
|
1534
|
-
// 6.x
|
|
1535
|
-
override async getActionDescriptor(uiAction: UiAction): Promise<UiActionDescriptor> {
|
|
1536
|
-
let d: UiActionDescriptor = await super.getActionDescriptor(basicUiAction);
|
|
1537
|
-
…
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
// 7.0
|
|
1541
|
-
override getActionDescriptor(uiAction: UiAction): UiActionDescriptor {
|
|
1542
|
-
let d: UiActionDescriptor = super.getActionDescriptor(basicUiAction);
|
|
1543
|
-
…
|
|
1544
|
-
}
|
|
1545
|
-
```
|
|
1546
|
-
|
|
1547
|
-
This is also the service you hand to a toolbar through `[uiActionDescriptorService]`, so a
|
|
1548
|
-
host with a custom one hits this at compile time rather than on screen.
|
|
1549
|
-
|
|
1550
|
-
**2. `.then(…)` on the result** — `TS2339`, because a descriptor has no `.then`. Found in
|
|
1551
|
-
two hosts, four sites. **The codemod reports this one and does not rewrite it**: turning a
|
|
1552
|
-
callback into straight-line code means moving its body, which is a judgment call. The fix is
|
|
1553
|
-
mechanical anyway — assign, then inline:
|
|
1554
|
-
|
|
1555
|
-
```ts
|
|
1556
|
-
// 6.x
|
|
1557
|
-
constructForm() {
|
|
1558
|
-
this.actionDescriptorService.getActionDescriptor(this.uiAction).then((d) => {
|
|
1559
|
-
this.buttonTitle = d.title;
|
|
1560
|
-
this.buttonColor = d.color;
|
|
1561
|
-
});
|
|
1562
|
-
…
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
// 7.0
|
|
1566
|
-
constructForm() {
|
|
1567
|
-
const d = this.actionDescriptorService.getActionDescriptor(this.uiAction);
|
|
1568
|
-
this.buttonTitle = d.title;
|
|
1569
|
-
this.buttonColor = d.color;
|
|
1570
|
-
…
|
|
1571
|
-
}
|
|
1572
|
-
```
|
|
1573
|
-
|
|
1574
|
-
If the callback was the reason a field lived in a `signal` — because the value arrived after
|
|
1575
|
-
the first render — it no longer needs to. The library made that same simplification in its
|
|
1576
|
-
own three action dialogs and its snack bar.
|
|
1577
|
-
|
|
1578
|
-
**What went synchronous with it, inside the library:** the toolbar's descriptor resolution,
|
|
1579
|
-
`SmartGridComponent`'s header construction, and `SmartTreeGenericService.cacheActionDesciptors()`
|
|
1580
|
-
and **`syncTree()`**. `syncTree()` now returns `void`, which is what
|
|
1581
|
-
`SmartTreeServiceInterface` always declared; a host that calls it and discards the result —
|
|
1582
|
-
the only shape measured — needs no change, and a host that `await`s it still compiles.
|
|
1583
|
-
|
|
1584
|
-
#### The same cleanup, elsewhere: `SmartdialogService.closeDialog()` returns `void`
|
|
1585
|
-
|
|
1586
|
-
A sweep for the same shape found eight more library functions declared `async` with nothing
|
|
1587
|
-
asynchronous in them. Only one of them is on a surface a host touches:
|
|
1588
|
-
**`SmartdialogService.closeDialog(stopPropagate?)` returns `void`** instead of
|
|
1589
|
-
`Promise<void>`.
|
|
1590
|
-
|
|
1591
|
-
**Nothing to do.** Both measured hosts that extend `SmartdialogService` override this method
|
|
1592
|
-
as `override async closeDialog(): Promise<void>`, and that **still compiles**: TypeScript
|
|
1593
|
-
accepts any return type where the base declares `void`. `await this.closeDialog()` keeps
|
|
1594
|
-
compiling too. This is the difference from `getActionDescriptor` above — that one returns a
|
|
1595
|
-
*value*, which is why the same override shape fails there with `TS2416` and not here.
|
|
1596
|
-
|
|
1597
|
-
You may of course drop the `async` from your own override; nothing requires it.
|
|
1598
|
-
|
|
1599
|
-
### Upload actions now wait for the server, and report their failures (Phase 3.9)
|
|
1600
|
-
|
|
1601
|
-
**This one changes behaviour, on purpose.** `SmartViewContextService.performUploadAction()`
|
|
1602
|
-
and `performUploadMultipleAction()` used to start the upload and resolve immediately,
|
|
1603
|
-
without waiting for it and without passing on its failure. Every other action in the family
|
|
1604
|
-
(`performAction`, `performWidgetAction`, `performWidgetMainAction`, `dataChanged`) always
|
|
1605
|
-
waited; the two upload ones were an omission.
|
|
1606
|
-
|
|
1607
|
-
Two things follow, and both are visible in a running app:
|
|
1608
|
-
|
|
1609
|
-
1. **An upload action now blocks until the server has answered.** Before, the action was
|
|
1610
|
-
"done" the moment the request went out — so `UiActionService` showed its success snackbar
|
|
1611
|
-
over an upload that was still in flight, and anything the action was sequenced before ran
|
|
1612
|
-
against a view the server had not updated yet. Now the snackbar appears when the upload
|
|
1613
|
-
really has succeeded. On a large file that is a longer wait than 6.x showed you.
|
|
1614
|
-
|
|
1615
|
-
2. **A failed upload now reaches your error handling.** The rejection used to be dropped —
|
|
1616
|
-
it became an unhandled promise rejection, `UiActionService`'s `catch` never ran, and the
|
|
1617
|
-
user was told the upload had worked. It now propagates, which means
|
|
1618
|
-
`setActionErrorHandler()` (or the default error dialog) will fire on upload failures it
|
|
1619
|
-
never fired on before. **If your host reports "new" upload errors after upgrading, they
|
|
1620
|
-
are not new** — they were happening silently.
|
|
1621
|
-
|
|
1622
|
-
Multi-file uploads also go out **one batch at a time** now. The batches were already being
|
|
1623
|
-
computed from the descriptor's `maxSize` and `maxBatchSize`, but they were then all sent at
|
|
1624
|
-
once, which put the whole set on the server simultaneously and defeated the split. If your
|
|
1625
|
-
backend measured upload concurrency, this is why it drops.
|
|
1626
|
-
|
|
1627
|
-
Nothing to change in host code.
|
|
1628
|
-
|
|
1629
|
-
### New in 7.0: a grid row can render a backend layout (#29717)
|
|
1630
|
-
|
|
1631
|
-
A card-mode grid row that carries `layoutDescriptor.componentLayouts['GRID_ROW_LAYOUT']`
|
|
1632
|
-
renders that layout instead of the card component the host registered under
|
|
1633
|
-
`'<GRID_ID>Card'`. Both branches stay: a row without the descriptor behaves exactly as
|
|
1634
|
-
before, and this needs no host change.
|
|
1635
|
-
|
|
1636
|
-
Two consequences worth knowing if you style or extend it:
|
|
1637
|
-
|
|
1638
|
-
- The rendered layout's root element gets the `gridCardLayout` class, applied by the card to
|
|
1639
|
-
its own child rather than pushed into the `style.classesToAdd` of the object the backend
|
|
1640
|
-
sent. Nothing mutates the server model.
|
|
1641
|
-
- **The row's actions reach the layout's toolbars by a pull, not a walk.** The card provides
|
|
1642
|
-
a `SmartActionHost` for its row subtree and writes the row's action list into it; a toolbar
|
|
1643
|
-
inside the layout resolves that host before falling back to the page client. If you build
|
|
1644
|
-
a component that owns such a subtree, do the same — `WIDGETS.md`, *Toolbars*.
|
|
1645
|
-
|
|
1646
|
-
This shipped in 7.0.0 rather than as a 6.0.3x patch because it is built on the inverted
|
|
1647
|
-
widget contract that 7.0 introduces.
|
|
1648
|
-
|
|
1649
|
-
### Writing your own widget
|
|
1650
|
-
|
|
1651
|
-
`WIDGETS.md`, next to this file, is the protocol: how a widget finds its screen component
|
|
1652
|
-
through DI and registers itself, when to opt out with `[smartComponentDetached]`, what has to
|
|
1653
|
-
be a signal now that there is no zone, how a toolbar resolves its actions, and the three
|
|
1654
|
-
traps that have actually bitten (imperatively-fed inputs cannot be signal inputs; `@for`
|
|
1655
|
-
tracking over backend objects; clear-then-apply styling).
|
|
1656
|
-
|
|
1657
|
-
In 6.x this was not possible at all — `SmartComponentApiClient` collected its children with
|
|
1658
|
-
eight `@ViewChildren` over eight concrete widget classes.
|
|
1659
|
-
|
|
1660
|
-
### Imperatively created components are given their inputs properly
|
|
1661
|
-
|
|
1662
|
-
`ComponentFactoryService` creates the components the library instantiates by hand — grid
|
|
1663
|
-
cards, expanded rows, table cell components, the expandable section's content, the form's
|
|
1664
|
-
`COMPONENT` widget, the filter editor's field editors — and most of those classes come from a
|
|
1665
|
-
host. It used to write their inputs by assigning the field. Two things follow, and both are
|
|
1666
|
-
fixed:
|
|
1667
|
-
|
|
1668
|
-
- **A declared input is now written with `ref.setInput()`**, i.e. the same way a template
|
|
1669
|
-
binding writes it. So a **signal input** works (assignment used to overwrite the input
|
|
1670
|
-
function with the value, and the component's next `this.x()` threw `x is not a function`),
|
|
1671
|
-
an **aliased** input is addressable under either name, and **`ngOnChanges` runs**.
|
|
1672
|
-
- **Falsy values arrive.** The old guard was `if (value)`, so `false`, `0` and `''` never
|
|
1673
|
-
reached the component. Only `undefined` is skipped now, which keeps meaning "not passed".
|
|
1674
|
-
|
|
1675
|
-
If your component takes something from the library this way and the key is **not** a declared
|
|
1676
|
-
input, the field is still assigned — nothing breaks — but you get one `console.warn` per
|
|
1677
|
-
component type and key. Declaring it with `@Input()` (or as a signal input) is the fix.
|
|
1678
|
-
|
|
1679
|
-
**Check your falsy defaults.** If a host component relied on the old guard — expecting to
|
|
1680
|
-
keep its own default when the library passed `false` or `''` — it now receives the value.
|
|
1681
|
-
This is the only part of 7.0 where a bugfix can change what you see on screen without any
|
|
1682
|
-
code of yours changing.
|
|
1683
|
-
|
|
1684
|
-
### Bugfixes shipped with 7.0
|
|
1685
|
-
|
|
1686
|
-
- `SmartformwidgetComponent.ngAfterViewInit` no longer crashes with
|
|
1687
|
-
`Cannot read properties of undefined (reading 'valueChanges')` when the form
|
|
1688
|
-
contains a `MONTH_PICKER` widget (it wrongly subscribed to the non-existent
|
|
1689
|
-
`<key>-time` control; only `DATE_TIME_PICKER` has one).
|
|
1690
|
-
- A widget nested in a CONTAINER receives `blurSophisticatedValueChange`; the recursion never
|
|
1691
|
-
passed it down, so a BLUR-mode child would have thrown on blur.
|
|
1692
|
-
- `SmartformComponent` reaches widgets nested in a CONTAINER when it changes them in place
|
|
1693
|
-
(values, constraints, the touched state after a failed submit). The view query it used
|
|
1694
|
-
could not see them.
|
|
1695
|
-
- `UiActionDescriptorService` no longer shares one dialog object between actions. Any action
|
|
1696
|
-
whose descriptor had no `dialog` of its own was given the service's single placeholder — the
|
|
1697
|
-
same object every time — and had its title and button caption written into it, so resolving
|
|
1698
|
-
one action renamed the dialog every other one was holding. The service is
|
|
1699
|
-
`providedIn: 'root'`, so this outlasted the view.
|
|
1700
|
-
|
|
1701
|
-
What you saw on screen, measured on both versions:
|
|
1702
|
-
|
|
1703
|
-
| The action | 6.x, when the dialog opened | 7.0 |
|
|
1704
|
-
|---|---|---|
|
|
1705
|
-
| described by the backend, no dialog of its own | **another action's code** | its own code |
|
|
1706
|
-
| described by nothing at all | **another action's code**, or empty | empty |
|
|
1707
|
-
| registered client-side, no dialog of its own | its own code | unchanged |
|
|
1708
|
-
|
|
1709
|
-
The middle column is not a typo. An action the backend describes is resolved twice in the
|
|
1710
|
-
normal flow — once when the toolbar renders it, again when the dialog it opens resolves it
|
|
1711
|
-
for itself — and the first resolution wrote a dialog onto the action's own descriptor, so the
|
|
1712
|
-
second one skipped the step that fills in the title. What the dialog then showed was whatever
|
|
1713
|
-
code had last passed through the shared object. An **already open** dialog could change too,
|
|
1714
|
-
since its title is re-read on every change-detection pass.
|
|
1715
|
-
|
|
1716
|
-
An action nothing describes still gets an untitled placeholder dialog: its *button* carries
|
|
1717
|
-
the code, the dialog does not. That is unchanged and deliberate — such an action is a gap in
|
|
1718
|
-
the host's descriptor map, and this is the honest rendering of it.
|
|
1719
|
-
|
|
1720
|
-
A resolved descriptor is now the caller's own object throughout, which also means
|
|
1721
|
-
`getActionDescriptor()` no longer writes a placeholder dialog and a `SNACKBAR` feedbackType
|
|
1722
|
-
into the `descriptor` the backend put on the action itself.
|
|
1
|
+
# @smartbit4all/ng-client 6.x → 7.0 migration guide
|
|
2
|
+
|
|
3
|
+
> 7.0.0 targets **Angular 22.0.8 / TypeScript 6.0.3 / Material 22.0.6**, with PrimeNG fully
|
|
4
|
+
> removed (Material/CDK only), every library component `OnPush`, and **no zone.js** — the
|
|
5
|
+
> configuration 7.0 is tested in. Angular's `moment` date adapter is replaced by `date-fns`.
|
|
6
|
+
> It is a major because a host has to act; nothing here is a silent behaviour change that
|
|
7
|
+
> you could ignore.
|
|
8
|
+
|
|
9
|
+
## Where to start
|
|
10
|
+
|
|
11
|
+
1. **Run the codemod** — [Migrating a host: the codemod](#migrating-a-host-the-codemod). It
|
|
12
|
+
does the mechanical part (the module imports, the provider configuration, the dead legacy
|
|
13
|
+
calls) and prints `file:line` for everything that needs a decision.
|
|
14
|
+
2. Read [Full standalone + `provideSmartNgClient()`](#full-standalone--providesmartngclient-phase-32--the-big-one).
|
|
15
|
+
That section rewrites your `app.module.ts`; everything else is small next to it.
|
|
16
|
+
3. Read [Dates](#dates-moment--date-fns-and-the-timezone-contract-phase-38). It is the one
|
|
17
|
+
change that needs action **at install time**, and the one that breaks at *runtime* on the
|
|
18
|
+
first screen with a date field rather than at build time.
|
|
19
|
+
4. Then the rest in order. The **PrimeNG removal** sections (1.1–1.10) are API changes; the
|
|
20
|
+
**hop sections** carry the framework defaults you inherit (OnPush, fetch vs XHR, `?.`).
|
|
21
|
+
5. If you write your own widget, `WIDGETS.md` next to this file is the protocol.
|
|
22
|
+
|
|
23
|
+
Measured across the 12 hosts in scope on 2026-07-28, so you know what to expect: 12–17
|
|
24
|
+
`Smart*Module` imports per host, 1–20 string-keyed providers, `.cssClass =` on an action
|
|
25
|
+
entry at 72 sites in 7 hosts, `this.uiActionModels =` in 5 hosts, `UseUiAction` in 6.
|
|
26
|
+
|
|
27
|
+
**How big is this for *your* host?** Codemod dry-run counts for the five hosts in scope
|
|
28
|
+
(2026-07-30). The first column the codemod applies for you; the second is the list it refuses to
|
|
29
|
+
guess at, which is the real work:
|
|
30
|
+
|
|
31
|
+
| host | mechanical | needs a decision |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| p014 *(done — the reference)* | 121 | ~100 |
|
|
34
|
+
| p043 | 121 | 96 |
|
|
35
|
+
| app-tournament | 86 | 34 |
|
|
36
|
+
| app-finance-ai | 62 | 18 |
|
|
37
|
+
| app-fitnessmirror | 61 | 14 |
|
|
38
|
+
|
|
39
|
+
The spread is not about host size — all five are within 274–841 source files — but about which
|
|
40
|
+
APIs they happened to use. p043 is p014's twin and repeats its whole decision list; the other
|
|
41
|
+
three have **no `.cssClass =` sites at all**, and their lists are dominated by `smart-modules`,
|
|
42
|
+
the 3-line `moment-adapter` swap, and `zone-js` (which is **optional** — dropping zone.js is not
|
|
43
|
+
required). Take a small one first if you want the process shaken out cheaply.
|
|
44
|
+
|
|
45
|
+
**One number that is now zero: the library install.** p014 was migrated against an unpublished
|
|
46
|
+
build and paid for it in hand-copies, tarballs and `ERESOLVE`. 7.0.0 is on npm, so a host today
|
|
47
|
+
writes `"@smartbit4all/ng-client": "^7.0.0"` and runs `npm i`. None of that thread applies to you.
|
|
48
|
+
|
|
49
|
+
## Migrating a host: the codemod
|
|
50
|
+
|
|
51
|
+
It lives in the `platform-angular2` repository — `tools/ng-client-7-codemod/` — and you run
|
|
52
|
+
it with plain Node, from a checkout, against your own source directory. Nothing to install.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
node tools/ng-client-7-codemod/migrate.mjs ../my-host/ui-angular/src --dry-run # look first
|
|
56
|
+
node tools/ng-client-7-codemod/migrate.mjs ../my-host/ui-angular/src # then write
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
It **refuses to run on a dirty git tree** (its diff is how you review it, and `git checkout .`
|
|
60
|
+
is how you undo it), it is **idempotent**, and it prints a verdict for every rule it knows —
|
|
61
|
+
including `no sites found`, because a codemod that silently matched nothing looks exactly
|
|
62
|
+
like one that had nothing to do.
|
|
63
|
+
|
|
64
|
+
**What it rewrites:** the `Smart*Module` imports (into the standalone declarables of the
|
|
65
|
+
mapping table below), the string-keyed providers and both `forRoot()` payloads (into one
|
|
66
|
+
`provideSmartNgClient(config, ...features)` call), the redundant core services in the root
|
|
67
|
+
providers array, `this.useQueryLists = true` / `handleDataChangeSubscriptions()`,
|
|
68
|
+
`[parentSmartComponent]` and the dead `[parent]` inputs,
|
|
69
|
+
`execute(x.uiAction, x)` → `execute(x)`, the `async` / `await` around
|
|
70
|
+
`getActionDescriptor`, the `SmartdialogService` `super(…)` call and its constructor
|
|
71
|
+
parameters, and PrimeNG module imports **no template in your repository renders**.
|
|
72
|
+
|
|
73
|
+
**What it refuses to guess at**, printing `file:line` and the section here that explains it:
|
|
74
|
+
`UseUiAction` → `UiActionExecutor`, `this.uiActionModels =`, `entry.cssClass =`, `addForm()`,
|
|
75
|
+
the 4th argument of `addGrid()`, `getActionDescriptor(…).then(…)`, the moment adapter,
|
|
76
|
+
component-level providers, every remaining `primeng/*` import, and `::ng-deep` selectors that
|
|
77
|
+
reach into the form widget.
|
|
78
|
+
|
|
79
|
+
**What it deliberately does not do:** convert *your* components to standalone. That is
|
|
80
|
+
Angular's own schematic (`ng generate @angular/core:standalone`) and it is not required — an
|
|
81
|
+
NgModule's `imports:` array takes standalone components, which is exactly what the codemod
|
|
82
|
+
puts there. Do the library migration first, get the app running, then decide about standalone
|
|
83
|
+
separately.
|
|
84
|
+
|
|
85
|
+
Afterwards: run your formatter, swap the date packages ([Dates §1](#1-swap-the-peer-dependency)),
|
|
86
|
+
build (expect one `NG8001` per template element whose component is not imported yet — that
|
|
87
|
+
loop terminates), and **smoke the app**, because the DI changes are invisible to the compiler.
|
|
88
|
+
|
|
89
|
+
## Breaking changes
|
|
90
|
+
|
|
91
|
+
### Dialog stack: PrimeNG dynamicdialog → MatDialog (Phase 1.1)
|
|
92
|
+
|
|
93
|
+
- `SmartdialogService` and `SmartViewContextDialogService` constructors no longer
|
|
94
|
+
take a PrimeNG `DialogService` parameter. If your host extends or instantiates
|
|
95
|
+
these services, drop the argument (MatDialog is injected internally).
|
|
96
|
+
|
|
97
|
+
Both arguments PrimeNG contributed are gone, so the base constructor lost two of
|
|
98
|
+
its four parameters:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// 6.x
|
|
102
|
+
constructor(dialog: MatDialog, dialogService: DialogService, inject: Injector,
|
|
103
|
+
@Inject(COMPONENT_LIBRARY) compLib: ComponentLibrary) {
|
|
104
|
+
super(dialog, dialogService, inject, compLib);
|
|
105
|
+
}
|
|
106
|
+
// 7.0
|
|
107
|
+
constructor(dialog: MatDialog, inject: Injector) {
|
|
108
|
+
super(dialog, inject);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**The codemod does this** (rule `smartdialog-super`), including the constructor
|
|
113
|
+
parameters and the now-unused `primeng/dynamicdialog` import — except where a
|
|
114
|
+
dropped parameter carries an accessibility modifier (`protected dialogService: …`),
|
|
115
|
+
which declares a class *property* the body may still read. Those are reported.
|
|
116
|
+
- The `ComponentLibrary` switch no longer has a PrimeNG branch in the dialog
|
|
117
|
+
layer — all dialogs open via `MatDialog` regardless of `COMPONENT_LIBRARY`.
|
|
118
|
+
|
|
119
|
+
### smart-form widgets: PrimeNG removed (Phase 1.2)
|
|
120
|
+
|
|
121
|
+
- `ComparableDropdownDirective` and `ComparableMultiselectDirective` are removed
|
|
122
|
+
(they decorated the PrimeNG `p-dropdown` / `p-multiSelect` elements, which no
|
|
123
|
+
longer exist). Use `SmartformwidgetComponent.compareItems` semantics via
|
|
124
|
+
`mat-select [compareWith]`, which the widget template already wires up.
|
|
125
|
+
- `SmartformwidgetComponent` constructor no longer takes the injected
|
|
126
|
+
`COMPONENT_LIBRARY` token; the widget renders Material-only.
|
|
127
|
+
- `SmartformwidgetComponent` removed PrimeNG-only public members:
|
|
128
|
+
`errorMessages`, `getControlMessages()`, `onPrimeRichTextEditorContentChanged()`,
|
|
129
|
+
`safeDropDownBlur()`/`onDropDownShow()`/`onDropDownInput()`,
|
|
130
|
+
`safeCalendarBlur()`/`onCalendarShow()`/`onCalendarInput()`, `defaultDate`.
|
|
131
|
+
- `SmartFormService.setValuesFromModel()` / `toFormGroup()` /
|
|
132
|
+
`createFormControls()` no longer take a `ComponentLibrary` parameter.
|
|
133
|
+
- `SmartViewContextModule` no longer imports the PrimeNG form modules
|
|
134
|
+
(Dropdown, MultiSelect, Calendar, Chips, InputSwitch, Checkbox, InputNumber,
|
|
135
|
+
InputText, InputTextarea, InputMask, FloatLabel, Messages) and no longer
|
|
136
|
+
provides `Dropdown` / `MultiSelect`.
|
|
137
|
+
|
|
138
|
+
### fileupload: PrimeNG removed (Phase 1.4)
|
|
139
|
+
|
|
140
|
+
- `SmartFileUploaderComponent` (selector `smart-file-uploader4sc`) is removed
|
|
141
|
+
from the public API. It was a PrimeNG `p-fileUpload` wrapper declared in
|
|
142
|
+
`SmartNgClientModule` but referenced nowhere; the Material-style
|
|
143
|
+
`SmartfileuploaderComponent` (selector `smartfileuploader`) is the uploader.
|
|
144
|
+
- The internal `PrimeFileUploaderComponent` (selector `prime-file-uploader`)
|
|
145
|
+
is removed; `smart-upload-widget` always renders `smartfileuploader`.
|
|
146
|
+
- `UploadWidgetComponent` constructor no longer takes the injected
|
|
147
|
+
`COMPONENT_LIBRARY` token, and its PrimeNG-only members are gone
|
|
148
|
+
(`fileUploadPrime`, `uploadFiles()`).
|
|
149
|
+
- `SmartNgClientModule` and `SmartViewContextModule` no longer import the
|
|
150
|
+
PrimeNG `FileUploadModule`; `SmartViewContextModule` also dropped the dead
|
|
151
|
+
`OverlayPanelModule` import (Phase 1.3).
|
|
152
|
+
|
|
153
|
+
### editor: Quill 1.3 → 2, quill-emoji removed (Phase 1.5)
|
|
154
|
+
|
|
155
|
+
- Peer dependencies changed: `quill` `^1.3.7` → `^2.0.3`, `ngx-quill`
|
|
156
|
+
`16.2.1 - 24.0.5` → `^25.3.3`. Hosts must bump both together (ngx-quill 25.x
|
|
157
|
+
is the Quill 2 line for Angular 17; later Angular hops will raise it further).
|
|
158
|
+
- `quill-emoji` peer dependency is **removed** (dead, Quill 1-only, unmaintained).
|
|
159
|
+
If a host needs emoji support, pick a Quill 2-compatible module on its own.
|
|
160
|
+
- `@types/quill` must be removed from host devDependencies — Quill 2 ships its
|
|
161
|
+
own TypeScript types, and the stale `@types/quill` 1.x conflicts with them.
|
|
162
|
+
- The bundled `quill.snow.css` (imported by the smart-form widget) is now the
|
|
163
|
+
Quill 2.0.3 stylesheet; hosts that import `quill/dist/quill.snow.css`
|
|
164
|
+
themselves must serve the Quill 2 version.
|
|
165
|
+
- `SmartViewContextModule` no longer imports the PrimeNG `EditorModule`
|
|
166
|
+
(`<p-editor>` died with Phase 1.2; RICH_TEXT renders via ngx-quill).
|
|
167
|
+
|
|
168
|
+
### smart-diagram: p-chart replaced by direct chart.js (Phase 1.6)
|
|
169
|
+
|
|
170
|
+
- `SmartDiagramComponent` renders its own `<canvas>` and instantiates chart.js
|
|
171
|
+
directly; the PrimeNG `<p-chart>` wrapper (and the `ChartModule` import in
|
|
172
|
+
`SmartDiagramModule`) is gone. Chart.js controllers are now registered by the
|
|
173
|
+
component itself (`Chart.register(...registerables)`), so no `chart.js/auto`
|
|
174
|
+
import is needed anywhere.
|
|
175
|
+
- The public `chart` property (and `getChart()`) is now the chart.js `Chart`
|
|
176
|
+
instance instead of the PrimeNG `UIChart` component. `getBase64Image()` and
|
|
177
|
+
`refresh()` keep their signatures (delegating to `toBase64Image()` /
|
|
178
|
+
`update()`).
|
|
179
|
+
- `SmartDiagramComponent` constructor no longer takes the injected
|
|
180
|
+
`COMPONENT_LIBRARY` token; the component has a single render path.
|
|
181
|
+
- DOM/CSS hooks changed: the internal structure is
|
|
182
|
+
`div.chart-host > div.chart-container > canvas`; host styles targeting
|
|
183
|
+
`::ng-deep p-chart` no longer match. The aspect-ratio classes
|
|
184
|
+
(`default-aspect-ratio` / `pie-aspect-ratio`) are now applied on
|
|
185
|
+
`.chart-host` and size `.chart-container`.
|
|
186
|
+
|
|
187
|
+
### smart-grid: PrimeNG branch removed (Phase 1.7)
|
|
188
|
+
|
|
189
|
+
- `SmartGridComponent` renders Material-only. The `@if (compLib === PRIMENG)`
|
|
190
|
+
template branch (the inline `p-table` / `p-paginator` / `p-menu` /
|
|
191
|
+
`p-multiSelect` grid) is gone; the grid always renders through the PrimeNG-free
|
|
192
|
+
`smart-table` (the `#table` slot), `mat-tree`, `app-smart-grid-card`, and
|
|
193
|
+
`mat-paginator`.
|
|
194
|
+
- `SmartGridComponent` constructor no longer takes the injected
|
|
195
|
+
`COMPONENT_LIBRARY` token, nor the `SmartDatePipe` / `SmartDateTimePipe` /
|
|
196
|
+
`SmartTimePipe` (they only fed the removed inline `p-table` cell renderer);
|
|
197
|
+
those pipes are dropped from the component `providers` too.
|
|
198
|
+
- Removed PrimeNG-only public members: `columns`, `menuButtons`, `menu`,
|
|
199
|
+
`onOptionsClick()`, `previousmultiSortMeta`, `gridSort()`, `lazyLoad()`,
|
|
200
|
+
`headerChange()`, `onColOrder()`, `getOrderColumNames()`, `getColValue()`,
|
|
201
|
+
`onPrimeChangePage()`, `onRowSelect()`, `onRowUnselect()`, `onSelectAllRow()`,
|
|
202
|
+
`getImageResourceIcons()`, `getImageResourceStyle()`, `getRowColumnAction()`,
|
|
203
|
+
`showCellToolbar()`, `shouldShowOptionsButton()`, `calculateMenuActions()`,
|
|
204
|
+
`createCellToActionMap()`, `getRowMenuActionModelArray()`, `rowTrackByFn()`,
|
|
205
|
+
`cellToActionMap`, `columnMetaByName`, and the `_headerToolbar`
|
|
206
|
+
(`#headerToolbar`) view child. The `headerToolbar` getter now returns the
|
|
207
|
+
smart-table's header toolbar unconditionally (its only remaining consumer,
|
|
208
|
+
`SmartComponentApiClient`, is unaffected).
|
|
209
|
+
- `SmartGridModule` no longer imports the PrimeNG `TableModule`, `ButtonModule`,
|
|
210
|
+
`MenuModule`, `PaginatorModule`, `MultiSelectModule`; `MatTooltipModule` is
|
|
211
|
+
added (the grid refresh button is now a Material `mat-icon-button` +
|
|
212
|
+
`matTooltip` instead of `pButton` + `pTooltip`).
|
|
213
|
+
- Note: this is the PrimeNG-branch deletion only. The `smart-grid` internals
|
|
214
|
+
rewrite to `mat-table` + `MatPaginator` and the `smart-grid`/`smart-table`
|
|
215
|
+
merge remain a Phase 3 (post-Angular-22) change.
|
|
216
|
+
|
|
217
|
+
### misc widgets: PrimeNG branches removed (Phase 1.8)
|
|
218
|
+
|
|
219
|
+
The remaining components that still carried a dead PrimeNG branch alongside an
|
|
220
|
+
already-active Material one are now Material-only. In every case the Material
|
|
221
|
+
branch was the rendered path under `COMPONENT_LIBRARY = MATERIAL` (the value all
|
|
222
|
+
7.0 hosts use), so this is render-neutral; the `COMPONENT_LIBRARY` token, where
|
|
223
|
+
these components still injected it, is now ignored by them.
|
|
224
|
+
|
|
225
|
+
- `ExpandableSectionComponent` renders Material-only (the `p-accordion` branch is
|
|
226
|
+
gone; always `mat-expansion-panel`). Its constructor no longer takes the
|
|
227
|
+
injected `COMPONENT_LIBRARY` token. `SmartExpandableSectionModule` no longer
|
|
228
|
+
imports the PrimeNG `AccordionModule`.
|
|
229
|
+
- `UiActionButtonComponent` renders Material-only (the `pButton`/`pRipple` branch
|
|
230
|
+
is gone; always `mat-button`). Its constructor no longer takes the optional
|
|
231
|
+
`COMPONENT_LIBRARY` token (it previously defaulted to `PRIMENG` when unset).
|
|
232
|
+
`getType()` no longer has a PrimeNG class-map branch — it returns the
|
|
233
|
+
`mat-mdc-*` class strings unconditionally. (The dynamic `getbtnClass()`'s
|
|
234
|
+
`p-button-<color>` class token was later dropped in Phase 1.9.)
|
|
235
|
+
- `UiActionToolbarComponent` constructor no longer takes the injected
|
|
236
|
+
`COMPONENT_LIBRARY` token. The dead `getType()` / `getbtnClass()` methods
|
|
237
|
+
(never referenced by the template) were removed, and the scroll-affordance
|
|
238
|
+
buttons use the Material icons `arrow_back` / `arrow_forward` unconditionally
|
|
239
|
+
(the PrimeNG `chevron-left` / `chevron-right` fallback is gone).
|
|
240
|
+
- `UiActionConfirmDialogComponent` and `UiActionInputDialogComponent` render
|
|
241
|
+
their close button as a Material `mat-icon-button` unconditionally (the `@else`
|
|
242
|
+
`p-button` branch is gone); both constructors no longer take the injected
|
|
243
|
+
`COMPONENT_LIBRARY` token.
|
|
244
|
+
- `SmartIconModule` no longer imports the PrimeNG `BadgeModule` (badges render
|
|
245
|
+
via the custom `ui-badge` component, unaffected).
|
|
246
|
+
- `SmartViewContextModule` no longer imports the PrimeNG `ButtonModule`,
|
|
247
|
+
`TooltipModule`, `ToastModule`, `ImageModule`, or `OrderListModule` (all
|
|
248
|
+
unused after the branch deletions). The `primeng/api` `SharedModule`
|
|
249
|
+
(`PrimeSharedModule`) import was later dropped in Phase 1.9's final sweep.
|
|
250
|
+
|
|
251
|
+
### PrimeNG package dependency removed (Phase 1.9)
|
|
252
|
+
|
|
253
|
+
The `primeng` npm package is gone. Most importantly it is dropped from
|
|
254
|
+
`@smartbit4all/ng-client`'s own **`peerDependencies`** (in
|
|
255
|
+
`projects/smart-ng-client/package.json`, which flows into the published
|
|
256
|
+
`npms/smart-ng-client/package.json`) — that entry is what previously forced every
|
|
257
|
+
consuming host to install PrimeNG. It is also removed from the dev workspace
|
|
258
|
+
(`package.json`, `package-lock.json`, `node_modules`), and both PrimeNG theme
|
|
259
|
+
stylesheets (`primeng/resources/themes/saga-blue/theme.css`,
|
|
260
|
+
`primeng/resources/primeng.min.css`) are removed from the app-playground `styles`
|
|
261
|
+
in `angular.json`. Hosts that still list `primeng` as a dependency or load its
|
|
262
|
+
theme CSS should remove both; 7.0 renders entirely on Material/CDK.
|
|
263
|
+
|
|
264
|
+
#### Your own PrimeNG usage
|
|
265
|
+
|
|
266
|
+
Removing the peer dependency does not remove PrimeNG from *your* code, and the measured
|
|
267
|
+
hosts carry more of it than their authors expect — mostly as **dead module imports**. Across
|
|
268
|
+
the 12 hosts, `DialogService` is imported 56 times, but 8 of those in p014 turned out to be
|
|
269
|
+
nothing but the base-constructor argument above, and four PrimeNG modules in its
|
|
270
|
+
`app.module.ts` rendered nothing at all.
|
|
271
|
+
|
|
272
|
+
So the codemod separates the two cases, and the separation is decidable rather than
|
|
273
|
+
guessed:
|
|
274
|
+
|
|
275
|
+
- **`primeng-modules`** — a PrimeNG NgModule in an `imports:` array is **deleted** when none
|
|
276
|
+
of its selectors appears in any template in the repository. The scan runs over `.html`
|
|
277
|
+
*and* `.ts` (for inline templates) before anything is rewritten, and it distinguishes an
|
|
278
|
+
element (`<p-tree>`) from an attribute directive (`pTooltip`) so that a leftover
|
|
279
|
+
`class="p-button"` is not read as a component still in use. What the scan found is printed
|
|
280
|
+
in the header, because it is what licenses the deletions.
|
|
281
|
+
- **`primeng-imports`** — everything else from `primeng/*` is reported with its replacement.
|
|
282
|
+
These are the real ports.
|
|
283
|
+
|
|
284
|
+
The replacements, for what the hosts actually import:
|
|
285
|
+
|
|
286
|
+
| PrimeNG | Material / CDK |
|
|
287
|
+
|---|---|
|
|
288
|
+
| `p-button` / `pButton` | `MatButtonModule` — `mat-button`, `mat-icon-button` |
|
|
289
|
+
| `p-tree` | the library's `<smart-tree>`, or `MatTreeModule` |
|
|
290
|
+
| `p-progressSpinner` | `MatProgressSpinnerModule` — `<mat-spinner>` |
|
|
291
|
+
| `pTooltip` | `MatTooltipModule` (`matTooltip`), or `SmartTooltipDirective` |
|
|
292
|
+
| `p-sidebar` | `MatSidenavModule` |
|
|
293
|
+
| `pInputText` | `MatInputModule` — `matInput` inside `<mat-form-field>` |
|
|
294
|
+
| `p-fieldset` | `MatCardModule`, or `<fieldset>` + `<mat-divider>` |
|
|
295
|
+
| `p-divider` | `MatDividerModule` |
|
|
296
|
+
| `p-accordion` | `MatExpansionModule`, or `<smart-expandable-section>` |
|
|
297
|
+
| `p-table` | the library's `<smart-grid>`, or `MatTableModule` |
|
|
298
|
+
| `p-editor` | ngx-quill's `<quill-editor>` (already a library dependency) |
|
|
299
|
+
| `p-badge` / `pBadge` | `MatBadgeModule`, or `UiBadgeComponent` / `UiBadgeDirective` |
|
|
300
|
+
| `p-menu`, `p-overlayPanel` | `MatMenuModule`, or the CDK Overlay |
|
|
301
|
+
| `p-inputSwitch` | `MatSlideToggleModule` |
|
|
302
|
+
| `p-dialog` | `MatDialog` — open a component instead of toggling `[visible]` |
|
|
303
|
+
| `DialogService` | `MatDialog`. There is **no `header` option**: put `<h2 mat-dialog-title>` in the component |
|
|
304
|
+
| `DynamicDialogRef` | `MatDialogRef` — `afterClosed()` replaces `onClose` |
|
|
305
|
+
| `DynamicDialogConfig` | `MAT_DIALOG_DATA` for the data, `MatDialogRef` for the rest |
|
|
306
|
+
| `PrimeNGConfig` | **delete it.** `setTranslation()` of day and month names is already covered by `MAT_DATE_LOCALE` + `SmartDateFnsAdapter` — the date-fns `hu` locale carries both the names and the Monday week start. **Confirmed on a running host** (p014, 2026-07-29, against a live backend): the datepicker opens on `2026. JÚL.` with `H K Sz Cs P Sz V`, i.e. Hungarian and Monday-first, exactly what the deleted `firstDayOfWeek: 1` + `dayNamesMin` used to buy. See [Dates](#dates-moment--date-fns-and-the-timezone-contract-phase-38) |
|
|
307
|
+
| `MessageService` | `MatSnackBar` |
|
|
308
|
+
| `ConfirmationService` | the library's `UiActionConfirmDialogService` |
|
|
309
|
+
| `MenuItem` | the library's `UiActionModel`, or a plain `<mat-menu>` item |
|
|
310
|
+
| `SharedModule` (`primeng/api`) / `PrimeTemplate` | a plain `<ng-template>` — PrimeNG needed `pTemplate`, Material does not |
|
|
311
|
+
|
|
312
|
+
`primeicons` is a separate package. Nothing in 7.0 needs it, but nothing breaks if you keep
|
|
313
|
+
it either — `pi pi-*` class names are just a font.
|
|
314
|
+
|
|
315
|
+
### `QuillModule.forRoot()` in your root module
|
|
316
|
+
|
|
317
|
+
`provideSmartNgClient()` calls `importProvidersFrom(QuillModule.forRoot())` itself — ngx-quill
|
|
318
|
+
has no provider function, so the module is the only way to reach its root configuration. A
|
|
319
|
+
root module that also calls it is configuring quill twice, so the codemod removes the call
|
|
320
|
+
and its import. In a *feature* module the same call is that module's own configuration and is
|
|
321
|
+
left alone.
|
|
322
|
+
|
|
323
|
+
**Keep `ngx-quill` and `quill` in your `package.json`.** They are peer dependencies of the
|
|
324
|
+
library — the host provides them. npm installs peers automatically, which is why hosts have
|
|
325
|
+
got away with not declaring them, but any install that runs with `--legacy-peer-deps` drops
|
|
326
|
+
them silently and the next build fails on an import that has always worked. Two of the twelve
|
|
327
|
+
measured hosts were in exactly that state.
|
|
328
|
+
|
|
329
|
+
**7.0 adds four peer dependencies 6.x did not have**, and they bite in a way the TypeScript
|
|
330
|
+
errors do not prepare you for:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
npm install ngx-mask@^22.0.0 fast-equals@^5.0.1 wavesurfer.js@^7.9.5 @angular/youtube-player@^22.0.0
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
If you install the library the normal way, npm brings these in for you. If you *swap a local
|
|
337
|
+
build into `node_modules` by hand* — which is how you would try the library before it is
|
|
338
|
+
published — npm never runs, so nothing installs them. The symptom arrives **after** the last
|
|
339
|
+
TypeScript error is fixed, as five `Module not found` errors reported against the library's
|
|
340
|
+
own `fesm2022` bundle rather than against any file of yours:
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
./node_modules/@smartbit4all/ng-client/fesm2022/smartbit4all-ng-client.mjs:7:0-60 -
|
|
344
|
+
Error: Module not found: Error: Can't resolve 'ngx-mask'
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Four packages, five errors: `wavesurfer.js` is missing twice, once for its `record` plugin.
|
|
348
|
+
|
|
349
|
+
- `UiActionButtonComponent.getbtnClass()` no longer emits the dead
|
|
350
|
+
`p-button-<color>` class token — it returns only `sb4-<color>` (nothing styled
|
|
351
|
+
`.p-button-*`; the platform button theme keys off `sb4-<color>` in
|
|
352
|
+
`custom-theme.scss`). Hosts with their own `.p-button-<color>` overrides for
|
|
353
|
+
sb4 action buttons should migrate them to `.sb4-<color>`.
|
|
354
|
+
- `SmartViewContextModule` no longer imports the `primeng/api` `SharedModule`
|
|
355
|
+
(`PrimeSharedModule`); no template in the module used `pTemplate`.
|
|
356
|
+
- `SmartComponentLayoutModule` no longer imports `InputGroupModule` /
|
|
357
|
+
`InputGroupAddonModule` (they were dead — no `p-inputGroup` template existed).
|
|
358
|
+
|
|
359
|
+
The `ComponentLibrary` enum still exists with only its `MATERIAL` member in
|
|
360
|
+
active use; its `PRIMENG` member and the remaining dead `ComponentLibrary.PRIMENG`
|
|
361
|
+
code branches (icon/menu/widget components, dead `.p-*`/`.pi` CSS) are removed in
|
|
362
|
+
a follow-up internal-cleanup step (1.10) — they are not a package dependency and
|
|
363
|
+
do not affect hosts. `deviceInfo.componentLibrary` continues to report
|
|
364
|
+
`material` to the backend.
|
|
365
|
+
|
|
366
|
+
### ComponentLibrary / COMPONENT_LIBRARY removed entirely (Phase 1.10)
|
|
367
|
+
|
|
368
|
+
7.0 is Material-only, so the `ComponentLibrary` abstraction (which had no second
|
|
369
|
+
implementation once PrimeNG was gone) is deleted outright.
|
|
370
|
+
|
|
371
|
+
- **Host action required:** `ComponentLibrary` (enum) and `COMPONENT_LIBRARY`
|
|
372
|
+
(injection token) are no longer exported from `@smartbit4all/ng-client`. Hosts
|
|
373
|
+
that provide `{ provide: COMPONENT_LIBRARY, useValue: ComponentLibrary.MATERIAL }`
|
|
374
|
+
in their root providers must **remove that provider and its import** — it now
|
|
375
|
+
fails to compile (no exported member). No replacement is needed; every component
|
|
376
|
+
renders Material-only.
|
|
377
|
+
- Every `@Inject(COMPONENT_LIBRARY)` constructor argument is gone from the library
|
|
378
|
+
(smart-icon, click/hover tiered menu, photo-capture / voice-record widgets,
|
|
379
|
+
smart-voice-recorder, smart-file-editor, smart-multi-file-editor, sortable
|
|
380
|
+
widget, smart-filter-editor-content, validation-result-page, message-dialog, the
|
|
381
|
+
dialog services `SmartdialogService` / `SmartViewContextDialogService` /
|
|
382
|
+
`SmartViewContextErrorDialogService` / the ui-action confirm/input/file-upload
|
|
383
|
+
dialog services, and `PdfViewerDialogService` in `@smartbit4all/document-explorer`).
|
|
384
|
+
The `SmartdialogService` base constructor is now `(dialog, injector)` — subclasses
|
|
385
|
+
that called `super(dialog, dialogService, injector, compLib)` keep the first and
|
|
386
|
+
third arguments only. See [Phase 1.1](#dialog-stack-primeng-dynamicdialog--matdialog-phase-11);
|
|
387
|
+
the codemod rewrites it.
|
|
388
|
+
- `SmartViewContextService` no longer exposes the public `componentLibrary` field
|
|
389
|
+
and no longer injects the token; it now writes the literal `'material'` into
|
|
390
|
+
`deviceInfo.componentLibrary` on every view-context update (the backend keys its
|
|
391
|
+
icon set on this string — `images-playground.properties` has `.material=` vs
|
|
392
|
+
`.primeng=` variants — so the value keeps flowing; the DTO field is unchanged).
|
|
393
|
+
- All dead `ComponentLibrary.PRIMENG` branches and their PrimeNG icon names
|
|
394
|
+
(`chevron-right`, `pi pi-*`, `power-off`, `trash`, `file`, `exclamation-circle`,
|
|
395
|
+
`play-circle`/`stop-circle`, etc.) are removed; the Material icon path is now
|
|
396
|
+
unconditional. Dead `.p-*` / `.pi` CSS rules were purged from the library
|
|
397
|
+
(smart-icon, smartform, smartformwidget, the file editors/uploader,
|
|
398
|
+
ui-action-file-upload-dialog) and from the app-playground `custom-theme.scss`;
|
|
399
|
+
the app-playground `index.html` no longer loads the `primeicons` CDN stylesheet.
|
|
400
|
+
- After 1.10, `grep -rin primeng projects` reports a single residual: the
|
|
401
|
+
auto-generated `deviceInfo.ts` DTO doc comment (`(Material, PrimeNg)`), which
|
|
402
|
+
mirrors the backend OpenAPI field description and is regenerated from it — left
|
|
403
|
+
untouched by design (the field itself is intentionally kept).
|
|
404
|
+
|
|
405
|
+
### Angular 18 hop (Phase 2, 17 → 18)
|
|
406
|
+
|
|
407
|
+
The library line now builds against Angular 18. Host-relevant changes:
|
|
408
|
+
|
|
409
|
+
- **Peer dependency bumps** (hosts must upgrade together with the lib):
|
|
410
|
+
`@angular/*` `^18.0.0`, `@angular/youtube-player` `^18.2.14` (was lagging on
|
|
411
|
+
`^16` — its `^16 || ^17` peer range breaks a strict `npm install` on Angular
|
|
412
|
+
18, so it can no longer lag), `ngx-quill` `^26.0.0` (the Angular 18 line;
|
|
413
|
+
quill stays `^2.0.3`), `ngx-mask` `^18.0.0`, and — for
|
|
414
|
+
`@smartbit4all/document-explorer` — `ngx-extended-pdf-viewer` `^21.0.0`
|
|
415
|
+
(the 19.x/20.x lines cap their Angular peer below 18).
|
|
416
|
+
- **`parchment` hoisting quirk (ngx-quill 26):** the ngx-quill 26 typings
|
|
417
|
+
reference `import("parchment")`, but npm may leave `parchment` nested under
|
|
418
|
+
`node_modules/quill/` (even after `npm dedupe`), which fails the host build
|
|
419
|
+
with `TS2307: Cannot find module 'parchment'`. Fix: add
|
|
420
|
+
`"parchment": "^3.0.0"` to the host devDependencies so it is hoisted to the
|
|
421
|
+
top level (quill 2 depends on parchment 3, so versions cannot conflict).
|
|
422
|
+
- **Material theming (M2 compat APIs):** the `ng update @angular/material@18`
|
|
423
|
+
schematic rewrites M2 theme SCSS to the prefixed compat API
|
|
424
|
+
(`mat.define-palette` → `mat.m2-define-palette`,
|
|
425
|
+
`mat.$indigo-palette` → `mat.$m2-indigo-palette`,
|
|
426
|
+
`mat.define-light-theme` → `mat.m2-define-light-theme`, …). The theme stays
|
|
427
|
+
visually **M2** — no M3 switch happens at this hop; hosts running the
|
|
428
|
+
schematic get this rewrite automatically.
|
|
429
|
+
- **`HttpClientModule` deprecation:** the `ng update @angular/core@18`
|
|
430
|
+
migration replaces `HttpClientModule` imports with
|
|
431
|
+
`provideHttpClient(withInterceptorsFromDi())`. Not a library breaking
|
|
432
|
+
change (the lib's generated API services were migrated internally), but
|
|
433
|
+
hosts will get the same automatic migration in their own modules.
|
|
434
|
+
- TypeScript requirement raised to `>=5.4` (the repo builds on 5.5).
|
|
435
|
+
|
|
436
|
+
### Angular 19 hop (Phase 2, 18 → 19)
|
|
437
|
+
|
|
438
|
+
The library line now builds against Angular 19. Host-relevant changes:
|
|
439
|
+
|
|
440
|
+
- **Peer dependency bumps** (hosts must upgrade together with the lib):
|
|
441
|
+
`@angular/*` `^19.0.0`, `@angular/youtube-player` `^19.0.0`,
|
|
442
|
+
`ngx-quill` `^27.0.0` (the Angular 19 line; quill stays `^2.0.3`),
|
|
443
|
+
`ngx-mask` `^19.0.0`, and — for `@smartbit4all/document-explorer` —
|
|
444
|
+
`ngx-extended-pdf-viewer` `^22.0.0` (the 21.x line caps its Angular peer
|
|
445
|
+
below 19).
|
|
446
|
+
- **ngx-extended-pdf-viewer 22 input rename:** the `[showScrollingButton]`
|
|
447
|
+
input no longer exists — it is `[showScrollingButtons]` (plural,
|
|
448
|
+
`ResponsiveVisibility`). Hosts that use `<ngx-extended-pdf-viewer>` directly
|
|
449
|
+
must rename the binding, otherwise the template fails with NG8002.
|
|
450
|
+
- **Standalone-by-default:** in Angular 19 components/directives/pipes are
|
|
451
|
+
standalone unless declared otherwise. The `ng update @angular/core@19`
|
|
452
|
+
migration automatically adds `standalone: false` to every NgModule-declared
|
|
453
|
+
declarable in the host codebase — run it and review the (large, mechanical)
|
|
454
|
+
diff. Declarables not referenced by any NgModule are skipped by the
|
|
455
|
+
schematic.
|
|
456
|
+
- **Material theming:** the `ng update @angular/material@19` schematic splits
|
|
457
|
+
`@include mat.core()` into `@include mat.elevation-classes()` +
|
|
458
|
+
`@include mat.app-background()`. No visual change; hosts running the
|
|
459
|
+
schematic get this rewrite automatically.
|
|
460
|
+
- **New NG8111 extended diagnostic** (warning): "Function in event binding
|
|
461
|
+
should be invoked". Uninvoked event bindings like
|
|
462
|
+
`(click)="(someCallback)"` — previously silently dead — are now flagged at
|
|
463
|
+
build time. Warning only, the build stays green.
|
|
464
|
+
- TypeScript requirement stays satisfied by 5.5 (Angular 19 supports
|
|
465
|
+
TS 5.5–5.8).
|
|
466
|
+
|
|
467
|
+
### Angular 20 hop (Phase 2, 19 → 20)
|
|
468
|
+
|
|
469
|
+
The library line now builds against Angular 20. Host-relevant changes:
|
|
470
|
+
|
|
471
|
+
- **Peer dependency bumps** (hosts must upgrade together with the lib):
|
|
472
|
+
`@angular/*` `^20.0.0`, `@angular/youtube-player` `^20.0.0`,
|
|
473
|
+
`ngx-quill` `^28.0.0` (the Angular 20 line; quill stays `^2.0.3`),
|
|
474
|
+
`ngx-mask` `^20.0.0`, and — for `@smartbit4all/document-explorer` —
|
|
475
|
+
`ngx-extended-pdf-viewer` `^23.0.0` (the 22.x line caps its Angular peer
|
|
476
|
+
below 20). No pdf-viewer API rename at this bump (unlike 22.x).
|
|
477
|
+
- **TypeScript 5.9 required** (Angular 20 supports 5.8–5.9); `ng update`
|
|
478
|
+
bumps it automatically. TS 5.9 adds **TS2872 "This kind of expression is
|
|
479
|
+
always truthy"**, which turns previously-silent dead `||` alternatives into
|
|
480
|
+
build errors — expect one or two in any older codebase.
|
|
481
|
+
- **Material 20 button labels no longer set `white-space: nowrap`.** Material
|
|
482
|
+
20 dropped the MDC stylesheet (`@material/button`), which used to carry it.
|
|
483
|
+
Multi-word button labels now wrap, and any rule that lets the button size to
|
|
484
|
+
its content (the lib's `.mdc-button { height: fit-content }`) makes the
|
|
485
|
+
button grow taller. The lib restores the pre-20 look inside
|
|
486
|
+
`ui-action-button` (`.mdc-button__label { white-space: nowrap }`); **hosts
|
|
487
|
+
that render Material buttons of their own must add the same rule** if they
|
|
488
|
+
relied on single-line labels (typical symptom: a navigation bar that
|
|
489
|
+
suddenly wraps and overflows into scroll arrows).
|
|
490
|
+
- **Material 20 renamed the MDC-era CSS custom properties**: e.g.
|
|
491
|
+
`--mdc-text-button-container-height` → `--mat-button-text-container-height`.
|
|
492
|
+
The `ng update @angular/material@20` schematic rewrites the ones it finds in
|
|
493
|
+
`.css`/`.scss` files; token names embedded in TS strings or host templates
|
|
494
|
+
must be renamed by hand.
|
|
495
|
+
- **`DOCUMENT` moved from `@angular/common` to `@angular/core`.** Automatic
|
|
496
|
+
migration; `@angular/common`'s re-export is deprecated.
|
|
497
|
+
- **`moduleResolution: "node"` → `"bundler"`** in `tsconfig.json` (and any
|
|
498
|
+
`tsconfig.lib.json` that sets it explicitly). Automatic migration; it changes
|
|
499
|
+
how sub-path exports resolve, so a host with hand-written `paths` entries
|
|
500
|
+
should re-verify its build.
|
|
501
|
+
- **Workspace generation defaults**: the CLI adds a `schematics` block to
|
|
502
|
+
`angular.json` pinning the pre-20 file-naming style (`type: "component"`,
|
|
503
|
+
`typeSeparator: "."`). Cosmetic — it only affects newly generated files.
|
|
504
|
+
- Optional migrations **not** run here (deferred to the modernization phase):
|
|
505
|
+
`use-application-builder` (esbuild), `control-flow-migration`,
|
|
506
|
+
`router-current-navigation`.
|
|
507
|
+
- The `InjectFlags`, `TestBed.get` → `TestBed.inject` and
|
|
508
|
+
`provideServerRendering` migrations were no-ops for this codebase.
|
|
509
|
+
- The NG8111 warnings introduced at the 19 hop are no longer emitted by the
|
|
510
|
+
Angular 20 compiler (the dead bindings themselves are unchanged).
|
|
511
|
+
|
|
512
|
+
### Angular 21 hop (Phase 2, 20 → 21)
|
|
513
|
+
|
|
514
|
+
The library line now builds against Angular 21. Host-relevant changes:
|
|
515
|
+
|
|
516
|
+
- **Peer dependency bumps** (hosts must upgrade together with the lib):
|
|
517
|
+
`@angular/*` `^21.0.0`, `@angular/youtube-player` `^21.0.0`,
|
|
518
|
+
`ngx-quill` `^29.0.0` (the Angular 21 line; quill stays `^2.0.3`),
|
|
519
|
+
`ngx-mask` `^21.0.0`, and — for `@smartbit4all/document-explorer` —
|
|
520
|
+
`ngx-extended-pdf-viewer` `^25.0.0` (the 24.x line still caps its Angular
|
|
521
|
+
peer below 21). No pdf-viewer API rename at this bump.
|
|
522
|
+
- **`ngx-quill` 30.x is intentionally *not* used.** 30.x is the "zoneless"
|
|
523
|
+
line and drops the `zone.js` peer dependency; the 7.0 lib is still
|
|
524
|
+
zone-based. Hosts that have already gone zoneless may use 30.x, but the
|
|
525
|
+
lib is only validated against 29.x.
|
|
526
|
+
- **TypeScript stays 5.9** and **zone.js stays 0.15** (Angular 21 accepts
|
|
527
|
+
`~0.15.0 || ~0.16.0`). **Node** must satisfy
|
|
528
|
+
`^20.19.0 || ^22.12.0 || >=24.0.0`.
|
|
529
|
+
- **`MatCommonModule` is removed from `@angular/material/core` (breaking).**
|
|
530
|
+
It was already deprecated in Material 20 (`@breaking-change 21.0.0`). Any
|
|
531
|
+
host NgModule that imports it will fail to compile with
|
|
532
|
+
`Unknown reference` — and because a broken NgModule cascades, the real
|
|
533
|
+
error usually shows up as a flood of `NG8001: '<some-component>' is not a
|
|
534
|
+
known element` / `NG6002: … does not appear to be an NgModule class`
|
|
535
|
+
further down the build log. **Fix: just delete the import and the array
|
|
536
|
+
entry.** `MatCommonModule` had no template surface; it only
|
|
537
|
+
(a) applied the `cdk-high-contrast-*` body classes — `A11yModule` still
|
|
538
|
+
does this, and Material components that need it import `A11yModule`
|
|
539
|
+
themselves — and (b) re-exported `BidiModule`. If your host actually uses
|
|
540
|
+
the `Dir` directive or injects `Directionality`, import
|
|
541
|
+
`BidiModule` from `@angular/cdk/bidi` explicitly.
|
|
542
|
+
The lib dropped it from all 10 of its own modules.
|
|
543
|
+
- **`provideZoneChangeDetection()` is now added at bootstrap** by the
|
|
544
|
+
`ng update` migration, e.g.
|
|
545
|
+
`bootstrapModule(AppModule, { applicationProviders: [provideZoneChangeDetection()] })`.
|
|
546
|
+
This keeps zone-based change detection explicit; behaviour is unchanged.
|
|
547
|
+
- **The control-flow migration (`*ngIf` → `@if`) now runs as a *mandatory*
|
|
548
|
+
`ng update` migration**, not an optional one. It rewrites every structural
|
|
549
|
+
directive in the workspace. The lib **reverted** it to keep the version hop
|
|
550
|
+
reviewable — `NgIf`/`NgForOf`/`NgSwitch` still ship in Angular 21, so
|
|
551
|
+
`*ngIf`/`*ngFor` keep working. Hosts may keep or revert it as they prefer;
|
|
552
|
+
if you want it as a separate change, run
|
|
553
|
+
`npx ng generate @angular/core:control-flow` on its own commit.
|
|
554
|
+
- **New `NG8107` extended diagnostic** (warning): an optional chain `?.`
|
|
555
|
+
whose left side can no longer be `null`/`undefined` under Angular 21's
|
|
556
|
+
sharpened template inference. Warning only — expect a batch of them in any
|
|
557
|
+
older template set.
|
|
558
|
+
- **`tsconfig.json` loses its explicit `lib` array** (the CLI derives it from
|
|
559
|
+
`target`). Automatic migration.
|
|
560
|
+
- The Material and CDK v21 migration schematics made no source changes, and
|
|
561
|
+
the `Router.lastSuccessfulNavigation` migration was a no-op. The optional
|
|
562
|
+
`router-current-navigation` migration was not run.
|
|
563
|
+
- The `.mdc-button__label { white-space: nowrap }` workaround introduced at
|
|
564
|
+
the Angular 20 hop is **still required on Material 21** — the class still
|
|
565
|
+
exists in the button template and Material still ships no `white-space`
|
|
566
|
+
rule for it.
|
|
567
|
+
|
|
568
|
+
### Angular 22 hop (Phase 2, 21 → 22) — final hop
|
|
569
|
+
|
|
570
|
+
The library line now builds against **Angular 22**. This is the biggest
|
|
571
|
+
host-facing hop of the series: it changes the **change-detection default**, the
|
|
572
|
+
**HTTP backend default** and the **meaning of `?.` in templates**. All three are
|
|
573
|
+
handled by `ng update` migrations, but you must let them run.
|
|
574
|
+
|
|
575
|
+
- **Peer dependency bumps** (hosts must upgrade together with the lib):
|
|
576
|
+
`@angular/*` `^22.0.0`, `@angular/youtube-player` `^22.0.0`,
|
|
577
|
+
`ngx-quill` `^31.0.0` (the Angular 22 line; quill stays `^2.0.3`),
|
|
578
|
+
`ngx-mask` `^22.0.0`, and — for `@smartbit4all/document-explorer` —
|
|
579
|
+
`ngx-extended-pdf-viewer` `^28.0.0` (25/26/27 all cap their Angular peer
|
|
580
|
+
below 22). **No pdf-viewer API rename at this bump.**
|
|
581
|
+
`@ngx-translate/core` `^14` still works (its peer is `>=13`).
|
|
582
|
+
- **TypeScript must be 6.0** (`@angular/compiler-cli` peers `>=6.0 <6.1`);
|
|
583
|
+
**zone.js stays 0.15** (`~0.15.0 || ~0.16.0`) and **rxjs is unchanged**.
|
|
584
|
+
**Node** must satisfy `^22.22.3 || ^24.15.0 || >=26.0.0` — note the
|
|
585
|
+
**22.22.3** floor, which is stricter than Angular 21's `^22.12.0`.
|
|
586
|
+
|
|
587
|
+
#### 1. `ChangeDetectionStrategy.OnPush` is the new default
|
|
588
|
+
|
|
589
|
+
In Angular 22 the enum is redefined: `OnPush = 0` (default), `Eager = 1`, and
|
|
590
|
+
`Default = 1` is kept as a **deprecated alias of `Eager`**. Any component
|
|
591
|
+
without an explicit `changeDetection` therefore switches from CheckAlways to
|
|
592
|
+
OnPush — a real behaviour change for components that mutate their own state
|
|
593
|
+
without signals or `markForCheck()`.
|
|
594
|
+
|
|
595
|
+
An `ng update` schematic **automatically adds
|
|
596
|
+
`changeDetection: ChangeDetectionStrategy.Eager` to every component that had
|
|
597
|
+
none**, which keeps behaviour bit-identical. The lib took exactly this route
|
|
598
|
+
(114 components) and did **not** opt into OnPush at this hop. Recommended host
|
|
599
|
+
approach: let the schematic run, verify your app, and move components to OnPush
|
|
600
|
+
deliberately afterwards.
|
|
601
|
+
|
|
602
|
+
Watch out for components the schematic can skip — it only touches components it
|
|
603
|
+
can resolve. After updating, grep for `@Component` files with no
|
|
604
|
+
`changeDetection` and decide about each one; a component that is dynamically
|
|
605
|
+
instantiated but never statically declared can slip through.
|
|
606
|
+
|
|
607
|
+
#### 2. `provideHttpClient` now defaults to the fetch backend
|
|
608
|
+
|
|
609
|
+
Angular 22 switches `HttpClient` from `XMLHttpRequest` to `fetch`. An
|
|
610
|
+
`ng update` migration inserts **`withXhr()`** into your `provideHttpClient(...)`
|
|
611
|
+
calls to preserve the old backend:
|
|
612
|
+
|
|
613
|
+
```ts
|
|
614
|
+
provideHttpClient(withXhr(), withInterceptorsFromDi())
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
The lib does this in `SmartViewContextModule`. **Hosts that call
|
|
618
|
+
`provideHttpClient` themselves must do the same** (or consciously move to
|
|
619
|
+
fetch). This matters most for **upload progress**: `reportProgress` events
|
|
620
|
+
behave differently on the fetch backend, so if your host reports upload
|
|
621
|
+
progress, keep `withXhr()` until you have retested it.
|
|
622
|
+
|
|
623
|
+
#### 3. `?.` in templates now yields `undefined`, not `null`
|
|
624
|
+
|
|
625
|
+
Angular 22's safe-navigation operator returns `undefined` where it used to
|
|
626
|
+
return `null`. Where that difference is observable, an `ng update` migration
|
|
627
|
+
wraps the expression in the **`$safeNavigationMigration()` compiler builtin**:
|
|
628
|
+
|
|
629
|
+
```html
|
|
630
|
+
[ngStyle]="calcStyle($safeNavigationMigration(diagramModel?.descriptor?.style))"
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
This is an official builtin (handled like `$any()` — the compiler unwraps it and
|
|
634
|
+
the type-checker sees straight through it), not a temporary marker, so wrapped
|
|
635
|
+
templates are safe to keep. The same `ng update` run also sets the
|
|
636
|
+
`nullishCoalescingNotNullable` and `optionalChainNotNullable` extended
|
|
637
|
+
diagnostics to `suppress` in your tsconfigs, which silences the `NG8107`
|
|
638
|
+
warnings the Angular 21 hop introduced.
|
|
639
|
+
|
|
640
|
+
#### 4. `ComponentFactoryResolver` is removed (breaking)
|
|
641
|
+
|
|
642
|
+
`ComponentFactoryResolver` is gone and `ComponentFactory` is no longer public
|
|
643
|
+
(it only survives as `ɵRender3ComponentFactory`). Code like this stops
|
|
644
|
+
compiling with `TS2305` / `NG2003`:
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
constructor(private resolver: ComponentFactoryResolver) {}
|
|
648
|
+
ngAfterViewInit() {
|
|
649
|
+
const factory = this.resolver.resolveComponentFactory(MyComponent);
|
|
650
|
+
this.vcRef.createComponent(factory);
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Replace it with the type-based overload — behaviour is identical (v22 builds the
|
|
655
|
+
same factory internally and resolves the injector from the parent):
|
|
656
|
+
|
|
657
|
+
```ts
|
|
658
|
+
ngAfterViewInit() {
|
|
659
|
+
this.vcRef.createComponent(MyComponent);
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
**Public-API change in the lib:** `ComponentFactoryService` no longer exposes
|
|
664
|
+
its `factory` field. `createComponent()` / `destroyComponent()` keep their
|
|
665
|
+
signatures, so hosts that only call those need no change.
|
|
666
|
+
|
|
667
|
+
#### 5. TypeScript 6.0 deprecates `baseUrl` and `downlevelIteration`
|
|
668
|
+
|
|
669
|
+
TS 6.0 raises **`TS5101`** for both options ("deprecated and will stop
|
|
670
|
+
functioning in TypeScript 7.0"). Two ways out:
|
|
671
|
+
|
|
672
|
+
- Remove them. `downlevelIteration` is a no-op at `target: ES2022`. `baseUrl`
|
|
673
|
+
can only go once every non-relative first-party import (`from 'projects/…'`,
|
|
674
|
+
`from 'src/…'`) is relative or covered by a `paths` entry.
|
|
675
|
+
- Or silence them for now with `"ignoreDeprecations": "6.0"` in
|
|
676
|
+
`compilerOptions`. This is what the lib workspace does, because `baseUrl` is
|
|
677
|
+
still load-bearing there. It only buys time until TS 7.
|
|
678
|
+
|
|
679
|
+
#### 6. Other notes
|
|
680
|
+
|
|
681
|
+
- **Material 22 removed no further modules or tokens** — the CDK and Material
|
|
682
|
+
v22 migration schematics made no source changes. CDK 22 moves to a
|
|
683
|
+
package `exports` map (the per-entry-point sub-directories are gone from
|
|
684
|
+
`node_modules/@angular/cdk`), but every `@angular/cdk/*` import still
|
|
685
|
+
resolves; this only matters if you referenced those paths on disk.
|
|
686
|
+
- The `.mdc-button__label { white-space: nowrap }` workaround introduced at the
|
|
687
|
+
Angular 20 hop is **still required on Material 22** — the class still appears
|
|
688
|
+
in Material's button template and Material still ships no `white-space` rule
|
|
689
|
+
for it.
|
|
690
|
+
- **The control-flow migration did not re-run at this hop** (it was mandatory at
|
|
691
|
+
21). The lib still ships `*ngIf`/`*ngFor` templates; `NgIf`/`NgForOf`/
|
|
692
|
+
`NgSwitch` continue to work in Angular 22.
|
|
693
|
+
- **The webpack-based builders are deprecated.** `@angular-devkit/build-angular`
|
|
694
|
+
now prints a notice recommending `@angular/build`. Nothing breaks yet, but
|
|
695
|
+
plan the switch.
|
|
696
|
+
- `strictTemplates` is on by default in v22; the lib workspace was already
|
|
697
|
+
strict, so nothing changed. If your host was not strict, the `ng update`
|
|
698
|
+
migration adds `"strictTemplates": false` to keep it that way.
|
|
699
|
+
|
|
700
|
+
### The third-party packages the library does *not* own (Phase 2)
|
|
701
|
+
|
|
702
|
+
Every hop section above lists the library's own peer dependencies. Your host almost
|
|
703
|
+
certainly carries packages the library never sees, and those have their own Angular
|
|
704
|
+
peer ceilings — each one can stop a hop dead. Measured on p014 (2026-07-29), which
|
|
705
|
+
went 17 → 22 with the library still installed:
|
|
706
|
+
|
|
707
|
+
- **`@swimlane/ngx-charts` needs a ladder of its own**: `20 → 22 → 22 → 23 → 24 → 25`
|
|
708
|
+
across the five hops. At the Angular 21 hop it peer-caps the CDK, and because
|
|
709
|
+
`ng update`'s own `npm` step is **not** `--force`d (even when you passed `--force`
|
|
710
|
+
to `ng update`), the install fails there while `package.json` has already been
|
|
711
|
+
written correctly. The fix both times was to redo the install by hand, not to
|
|
712
|
+
re-run the update. `p043` and `app-tournament` both carry ngx-charts `^20.4.1`
|
|
713
|
+
and will hit this; `app-fitnessmirror` and `app-finance-ai` do not.
|
|
714
|
+
- **`ngx-extended-pdf-viewer` moved to `.mjs`** at the 21 hop and stopped shipping
|
|
715
|
+
`pdf-3.10.560-es5.min.js`. If your `angular.json` names that file under `scripts`
|
|
716
|
+
or `assets`, the build fails on a missing file rather than on anything Angular.
|
|
717
|
+
- **`karma` and `@angular/build`**: `@angular/build` 22 declares
|
|
718
|
+
`peerOptional karma@^6.4.0`. A host pinned to `~6.3.0` gets a wall of
|
|
719
|
+
`npm warn ERESOLVE overriding peer dependency` — **warnings, exit code 0**, so it
|
|
720
|
+
is not blocking. It becomes a *hard* install failure the moment `@angular/build`
|
|
721
|
+
is a direct devDependency rather than a nested one, which is what happened in the
|
|
722
|
+
library workspace. `karma@~6.4.4` clears it.
|
|
723
|
+
|
|
724
|
+
The general shape: **`ng update` needs `--force` at every hop** (the library and
|
|
725
|
+
PrimeNG both peer-cap at Angular 17 until the very end), and `--force` covers the
|
|
726
|
+
Angular schematics but not the `npm install` they trigger. When that install fails,
|
|
727
|
+
check `package.json` before assuming the hop did nothing — it is usually already
|
|
728
|
+
correct.
|
|
729
|
+
|
|
730
|
+
### Housekeeping (Phase 3.0)
|
|
731
|
+
|
|
732
|
+
#### The dialog's built-in Ok/Cancel buttons are gone
|
|
733
|
+
|
|
734
|
+
`smartdialog.component.html` no longer renders an `Ok` and a `Cancel` button.
|
|
735
|
+
They were dead: their `(click)` handlers were the no-op expressions
|
|
736
|
+
`(click)="(data.okCallback)"` / `(click)="(data.cancelCallback)"` — a bare
|
|
737
|
+
property read, not a call — so pressing them did nothing, and the whole block
|
|
738
|
+
was behind `*ngIf="!data.customComponent"`, which is never true for a host
|
|
739
|
+
dialog (`SmartDialog.ngAfterViewInit` unconditionally instantiates
|
|
740
|
+
`data.customComponent`).
|
|
741
|
+
|
|
742
|
+
**The `okCallback` / `cancelCallback` model fields stay.** Hosts set and invoke
|
|
743
|
+
them from their own `customComponent` dialogs, and that keeps working
|
|
744
|
+
unchanged. Only the library-rendered buttons are gone. The `actionCallback` /
|
|
745
|
+
`actionLabel` button — the one with a real handler (`onActionClick()`) — is
|
|
746
|
+
untouched.
|
|
747
|
+
|
|
748
|
+
Action needed: none, unless you relied on the library rendering those two
|
|
749
|
+
buttons — in which case they never worked, and you should render them in your
|
|
750
|
+
own dialog component.
|
|
751
|
+
|
|
752
|
+
### Full standalone + `provideSmartNgClient()` (Phase 3.2) — **the big one**
|
|
753
|
+
|
|
754
|
+
This is the change that rewrites your `app.module.ts`. Everything else in this
|
|
755
|
+
guide is small next to it.
|
|
756
|
+
|
|
757
|
+
#### Every NgModule is gone
|
|
758
|
+
|
|
759
|
+
The library no longer ships a single `@NgModule`. All 58 are deleted —
|
|
760
|
+
`SmartNgClientModule`, `SmartViewContextModule`, `SmartSessionModule`,
|
|
761
|
+
`SmartGridModule`, `SmarttableModule`, `SmarttreeModule`, `SmartdialogModule`,
|
|
762
|
+
`SmartIconModule`, `SmartNavbarModule`, `SmartFilterModule`,
|
|
763
|
+
`SmartFilterEditorModule`, `SmartComponentLayoutModule`,
|
|
764
|
+
`SmartExpandableSectionModule`, `SmartMapModule`, `SmartDiagramModule`,
|
|
765
|
+
`SmartNavigationModule`, `SmartValidationModule`, `SmartGenericPagesModule`,
|
|
766
|
+
`ComponentFactoryServiceModule`, `SharedModule`, `SmartTabGroupModule`,
|
|
767
|
+
`SmartDocuStoreExplorerModule` and the generated OpenAPI `ApiModule`s.
|
|
768
|
+
|
|
769
|
+
Every component, directive and pipe is now **standalone**. Import the ones your
|
|
770
|
+
templates use directly, in the component that uses them:
|
|
771
|
+
|
|
772
|
+
```ts
|
|
773
|
+
@Component({
|
|
774
|
+
selector: 'app-my-page',
|
|
775
|
+
templateUrl: './my-page.component.html',
|
|
776
|
+
imports: [SmartGridComponent, UiActionToolbarComponent, SmartEmbeddedSlotDirective],
|
|
777
|
+
})
|
|
778
|
+
export class MyPageComponent extends SmartComponent<MyModel> { … }
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
**The compiler tells you what is missing** — one `NG8001: '<smart-grid>' is not
|
|
782
|
+
a known element` per unresolved element, per template. Work through them; there
|
|
783
|
+
is no guessing involved. In practice ~16 module imports become ~40 component
|
|
784
|
+
imports spread across the components that actually use them.
|
|
785
|
+
|
|
786
|
+
**The compiler does NOT tell you about the DI changes below.** Nothing fails at
|
|
787
|
+
build time if you get those wrong; you find out on the first page load. Smoke
|
|
788
|
+
your app.
|
|
789
|
+
|
|
790
|
+
#### `provideSmartNgClient(config, ...features)` replaces the module imports
|
|
791
|
+
|
|
792
|
+
```ts
|
|
793
|
+
bootstrapApplication(AppComponent, {
|
|
794
|
+
providers: [
|
|
795
|
+
provideAnimations(),
|
|
796
|
+
provideRouter(ROUTES),
|
|
797
|
+
provideSmartNgClient(
|
|
798
|
+
{
|
|
799
|
+
gridMenuIcon: 'more_horiz',
|
|
800
|
+
treeMenuIcon: 'more_horiz',
|
|
801
|
+
aclEditingViewName: Pages.ACL_MATRIX_PAGE,
|
|
802
|
+
invalidSmartlinkPageName: Pages.INVALID_SMARTLINK_PAGE_NAME,
|
|
803
|
+
namedValidators: [MY_VALIDATOR_FACTORY],
|
|
804
|
+
},
|
|
805
|
+
withSmartMap({ engine: MapEngine.LEAFLET }),
|
|
806
|
+
withSmartDiagram({ customOptions: [MY_CHART] })
|
|
807
|
+
),
|
|
808
|
+
{ provide: HTTP_INTERCEPTORS, useClass: MyLoadingInterceptor, multi: true },
|
|
809
|
+
],
|
|
810
|
+
});
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
The core is always wired and cannot be forgotten: session, view context, the
|
|
814
|
+
three BFF interceptors, layout, form, grid, table, tree, dialog, icon, navbar,
|
|
815
|
+
filter, filter editor, validation, expandable section, navigation, shared and
|
|
816
|
+
the generic pages. That is the point — the session and header interceptors used
|
|
817
|
+
to be easy to leave out, and leaving them out fails *silently* (no
|
|
818
|
+
`Authorization` header).
|
|
819
|
+
|
|
820
|
+
Only two features are opt-in, because they pull heavy third-party code that not
|
|
821
|
+
every host renders: `withSmartMap()` (leaflet / Google Maps) and
|
|
822
|
+
`withSmartDiagram()` (chart.js). There is **no `withSmartTabGroup()`** —
|
|
823
|
+
`smart-tab-group` had no component left, only an empty module, and is deleted.
|
|
824
|
+
|
|
825
|
+
`provideSmartNgClient()` calls `provideHttpClient()` itself, with
|
|
826
|
+
`withInterceptorsFromDi()` and its own three functional interceptors, and leaves the backend
|
|
827
|
+
on the framework default (fetch). Your own class-based `HTTP_INTERCEPTORS` keep working and
|
|
828
|
+
still run after the library's three. Pass extra `HttpClient` features in
|
|
829
|
+
`config.httpFeatures` rather than making a second `provideHttpClient()` call — see
|
|
830
|
+
[HTTP](#http-the-library-no-longer-forces-the-xhr-backend-phase-38), which is also where the
|
|
831
|
+
6.x "do not call it twice" trap went.
|
|
832
|
+
|
|
833
|
+
#### String-keyed providers become typed config fields
|
|
834
|
+
|
|
835
|
+
Delete these from your providers array and pass them to `provideSmartNgClient`:
|
|
836
|
+
|
|
837
|
+
| 6.x provider | 7.0 config field |
|
|
838
|
+
|---|---|
|
|
839
|
+
| `{ provide: 'gridMenuIcon', useValue: … }` | `gridMenuIcon` |
|
|
840
|
+
| `{ provide: 'treeMenuIcon', useValue: … }` | `treeMenuIcon` |
|
|
841
|
+
| `{ provide: 'searchPageName' / 'searchComponentName', … }` | `searchPageName` / `searchComponentName` |
|
|
842
|
+
| `{ provide: 'genericPageName' / 'genericComponentName', … }` | `genericPageName` / `genericComponentName` |
|
|
843
|
+
| `{ provide: 'subjectSelectorPageName' / 'subjectSelectorComponentName', … }` | `subjectSelectorPageName` / `subjectSelectorComponentName` |
|
|
844
|
+
| `{ provide: 'validationResultPageName' / 'validationResultComponentName', … }` | `validationResultPageName` / `validationResultComponentName` |
|
|
845
|
+
| `{ provide: 'noPermissionPageName' / 'noPermissionComponentName', … }` | `noPermissionPageName` / `noPermissionComponentName` |
|
|
846
|
+
| `{ provide: 'invalidSmartlinkPageName' / 'invalidSmartlinkComponentName', … }` | `invalidSmartlinkPageName` / `invalidSmartlinkComponentName` |
|
|
847
|
+
| `{ provide: 'aclEditingViewName', useValue: … }` | `aclEditingViewName` |
|
|
848
|
+
| `{ provide: DIALOG_DISABLE_CLOSE, useValue: … }` | `dialogDisableClose` |
|
|
849
|
+
| `{ provide: MAT_DATE_LOCALE, useValue: 'hu-HU' }` | `dateLocale` — **or keep the provider as it is**, which is what the codemod does; see [Dates §2](#2-leave-your-mat_date_locale-provider-alone) |
|
|
850
|
+
| `SmartWidgetSettings.useUtc = true` (a static, not a provider) | **delete it** — the flag is gone with the moment adapter; see [Dates §4](#4-smartngclientconfiguseutcdates-is-removed) |
|
|
851
|
+
| `{ provide: NAMED_VALIDATOR, useValue: F, multi: true }` | `namedValidators: [F]` |
|
|
852
|
+
| `{ provide: SMART_DEFAULT_VIEW_COMPONENTS, useValue: E, multi: true }` | `defaultViewComponents: [E]` |
|
|
853
|
+
| `SmartValidationModule.forRoot([...])` | `namedValidators` — **the element type changes**, see below |
|
|
854
|
+
| `SmartDiagramModule.forRoot([...])` | `withSmartDiagram({ customOptions })` |
|
|
855
|
+
| `{ provide: MAP_ENGINE, useValue: … }` | `withSmartMap({ engine })` |
|
|
856
|
+
|
|
857
|
+
`'pageName'`, `'componentName'` and `'treeId'` stay as string tokens on purpose:
|
|
858
|
+
they identify one component instance rather than configuring the library, and
|
|
859
|
+
hosts also provide them at component level.
|
|
860
|
+
|
|
861
|
+
You can also delete these from your **root** providers array — the core provides them:
|
|
862
|
+
`SmartSessionService`, `SmartViewContextService`, `SmartNavigationService`,
|
|
863
|
+
`SmartIconService`, `SmartFilterEditorService`, `SmartFormService`,
|
|
864
|
+
`SmartCookieService`, `NamedValidatorService`, `ComponentFactoryService`, and the date
|
|
865
|
+
adapter wiring (`MAT_DATE_FORMATS`, `DateAdapter`).
|
|
866
|
+
|
|
867
|
+
⚠️ **`namedValidators` takes factories, not providers.** `SmartValidationModule.forRoot()`
|
|
868
|
+
took `Provider[]`, so hosts wrote the wrapper by hand:
|
|
869
|
+
|
|
870
|
+
```ts
|
|
871
|
+
// 6.x — validator.factories.ts
|
|
872
|
+
export const VALIDATOR_PROVIDER_EMPTY_INPUT: Provider = {
|
|
873
|
+
provide: NAMED_VALIDATOR, useValue: VALIDATOR_FACTORY_EMPTY_INPUT, multi: true,
|
|
874
|
+
};
|
|
875
|
+
// app.module.ts
|
|
876
|
+
imports: [SmartValidationModule.forRoot([VALIDATOR_PROVIDER_EMPTY_INPUT])]
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
`provideSmartNgClient()` adds that wrapper itself, so the config takes the bare
|
|
880
|
+
`ValidatorFactory[]`. Export the factories and pass those:
|
|
881
|
+
|
|
882
|
+
```ts
|
|
883
|
+
// 7.0
|
|
884
|
+
export const VALIDATOR_FACTORY_EMPTY_INPUT: ValidatorFactory = { … };
|
|
885
|
+
provideSmartNgClient({ namedValidators: [VALIDATOR_FACTORY_EMPTY_INPUT] })
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
The codemod moves the argument into the config and **reports it**, because the entries are
|
|
889
|
+
normally named constants declared in another file and only you know what each one wraps. If
|
|
890
|
+
you miss the note, `TS2322: Type 'Provider' is not assignable to type 'ValidatorFactory'`
|
|
891
|
+
lands on every element.
|
|
892
|
+
|
|
893
|
+
⚠️ **The same line on a `@Component` is not the same thing.**
|
|
894
|
+
`providers: [SmartFilterEditorService]` on a page component asks for one instance *per page*,
|
|
895
|
+
which is usually deliberate — p014 does it in five components. Leave those alone; the codemod
|
|
896
|
+
does.
|
|
897
|
+
|
|
898
|
+
#### The module → standalone mapping
|
|
899
|
+
|
|
900
|
+
What each `Smart*Module` used to export, reduced to what 7.0 still exports publicly. An
|
|
901
|
+
NgModule's `imports:` array accepts standalone components, so this substitution is all a host
|
|
902
|
+
that is still NgModule-based needs to keep compiling — the codemod performs it.
|
|
903
|
+
|
|
904
|
+
| 6.x module | 7.0 imports |
|
|
905
|
+
|---|---|
|
|
906
|
+
| `SmartNgClientModule` | the union of the fourteen it re-exported (everything below except the generic pages, the filter editor, the map and the diagram) — trim it to what your templates use |
|
|
907
|
+
| `SmartComponentLayoutModule` | `SmartComponentLayoutComponent` |
|
|
908
|
+
| `SmartViewContextModule` | `UiActionToolbarComponent`, `UiActionButtonComponent`, `UiActionDialogButtonComponent`, `SmartformComponent`, `SmartfileuploaderComponent`, `SmartFileEditorComponent`, `SmartMultiFileEditorComponent`, `SmartVoiceRecorderComponent`, `SmartEmbeddedSlotDirective`, `HighlightPipe` |
|
|
909
|
+
| `SmartGridModule` | `SmartGridComponent` |
|
|
910
|
+
| `SmarttreeModule` | `SmartTreeComponent` |
|
|
911
|
+
| `SmartdialogModule` | `SmartDialog` |
|
|
912
|
+
| `SmartIconModule` | `SmartIconComponent`, `UiBadgeComponent`, `UiBadgeDirective` |
|
|
913
|
+
| `SmartNavbarModule` | `SmartNavbarComponent` |
|
|
914
|
+
| `SmartFilterModule` | `SmartFilterComponent` |
|
|
915
|
+
| `SmartFilterEditorModule` | `SmartFilterEditorContentComponent` |
|
|
916
|
+
| `SmartExpandableSectionModule` | `ExpandableSectionComponent` |
|
|
917
|
+
| `SmartSessionModule` | `SmartSessionTimerComponent` |
|
|
918
|
+
| `SmartGenericPagesModule` | `SearchPageComponent`, `GenericPageComponent`, `SubjectSelectorComponent`, `InvalidSmartlinkComponent` |
|
|
919
|
+
| `SmartMapModule` | `SmartMapComponent` + `withSmartMap({ engine })` |
|
|
920
|
+
| `SmartDiagramModule` | `SmartDiagramComponent` + `withSmartDiagram({ customOptions })` |
|
|
921
|
+
| `SharedModule` | `SmartTooltipDirective`, `SmartDatePipe`, `SmartDateTimePipe`, `SmartTimePipe` |
|
|
922
|
+
| `SmartValidationModule` | nothing — `forRoot()`'s validators become `namedValidators` |
|
|
923
|
+
| `SmarttableModule` | nothing — `SmarttableComponent` is no longer public; render a `<smart-grid>` |
|
|
924
|
+
| `SmartNavigationModule`, `ComponentFactoryServiceModule` | nothing — they only carried services, which the core provides |
|
|
925
|
+
| `SmartTabGroupModule` | nothing — deleted; it had no component left |
|
|
926
|
+
| `SmartDocuStoreExplorerModule` (`@smartbit4all/document-explorer`) | `SmartDocuStoreExplorerComponent`, `FolderContentComponent`, `PdfViewerDialogPageComponent` |
|
|
927
|
+
|
|
928
|
+
Two things this table will not tell you, and the compiler will not either: an import you no
|
|
929
|
+
longer need is invisible (unused entries in an NgModule `imports:` array are not reported),
|
|
930
|
+
and a name you already bind from elsewhere — a `SmartDatePipe` of your own — must not be
|
|
931
|
+
imported twice. The codemod reports both cases rather than guessing.
|
|
932
|
+
|
|
933
|
+
#### Your `SmartComponent` subclasses silently become OnPush
|
|
934
|
+
|
|
935
|
+
This is an **Angular 22 fact rather than something 7.0.0 introduces**, but it
|
|
936
|
+
lands on you at the same moment, so it is worth stating plainly: `SmartComponent`
|
|
937
|
+
is an abstract `@Component`, and each of your subclasses carries its own
|
|
938
|
+
`@Component` decorator. On Angular 22 a component without an explicit
|
|
939
|
+
`changeDetection` gets the new `OnPush` default. p014 has 75 such subclasses,
|
|
940
|
+
p043 has 70.
|
|
941
|
+
|
|
942
|
+
Let the `ng update` schematic add `changeDetection: ChangeDetectionStrategy.Eager`
|
|
943
|
+
to all of them (that is what the library did), verify your app, and move to
|
|
944
|
+
OnPush deliberately afterwards. A subclass that mutates its own state outside a
|
|
945
|
+
signal and without `markForCheck()` will stop repainting under OnPush.
|
|
946
|
+
|
|
947
|
+
#### Removed from the public API
|
|
948
|
+
|
|
949
|
+
A major is the only chance to narrow the surface, so these are no longer
|
|
950
|
+
exported. All of them are library internals; none is used by p014, p043,
|
|
951
|
+
app-formmate or `@smartbit4all/document-explorer`:
|
|
952
|
+
|
|
953
|
+
- `SmarttableComponent`, `SmarttableService` — use `<smart-grid>`, which renders
|
|
954
|
+
the table internally. **All `SmartTable*` model types stay** (`SmartTableType`,
|
|
955
|
+
`SmartTableInterfaceTypeEnum`, `SmartTableHeader`, …). p043 imports
|
|
956
|
+
`SmarttableComponent` in `ad-groups.component.ts` but never uses it — delete
|
|
957
|
+
that one import line.
|
|
958
|
+
- `GoogleMap`, `LeafletMap`, `AbstractMap` — select an engine with
|
|
959
|
+
`withSmartMap({ engine })`.
|
|
960
|
+
- `SmartFilterParamComponent`, `SmartFilterParamsComponent`,
|
|
961
|
+
`SmartFilterExpressionItemComponent`, `SmartFilterExpressionItemsComponent` —
|
|
962
|
+
internals of `SmartFilterEditorContentComponent`, which stays public.
|
|
963
|
+
- `HoverMenuComponent`, `ClickMenuComponent` — rendered by the ui-action toolbar.
|
|
964
|
+
- `UiActionConfirmDialogComponent`, `UiActionInputDialogComponent` — opened by
|
|
965
|
+
their services, which stay public.
|
|
966
|
+
- `UploadWidgetComponent`, `PhotoCaptureWidgetComponent`,
|
|
967
|
+
`VoiceRecordWidgetComponent` — contents of the ui-action upload dialog.
|
|
968
|
+
- `SmartNgClientService` — deleted; it was an empty stub with no members.
|
|
969
|
+
- The generated `ApiModule`s — they had no usages and their `forRoot()` told you
|
|
970
|
+
to import `HttpClientModule`, which no longer applies.
|
|
971
|
+
|
|
972
|
+
Two internal string tokens are gone as well, replaced by real `InjectionToken`s
|
|
973
|
+
(`'confirmDialogService'`, `'textFieldDialogService'`, `'fileUploadDialogService'`).
|
|
974
|
+
No host provided them, so no action is needed.
|
|
975
|
+
|
|
976
|
+
#### Behaviour notes
|
|
977
|
+
|
|
978
|
+
- `<mat-slide-toggle>` in the form widget no longer carries a `value` binding,
|
|
979
|
+
`<mat-nested-tree-node>` no longer carries an interpolated `matTreeNodeToggle`,
|
|
980
|
+
and the ui-action menus no longer carry `[subActions]`. All four were dead
|
|
981
|
+
bindings that the NgModules' `CUSTOM_ELEMENTS_SCHEMA` had been hiding — they
|
|
982
|
+
wrote a DOM property nobody read. Rendering is unchanged.
|
|
983
|
+
- The dead `ExpandableGridComponent` is deleted (it was declared in
|
|
984
|
+
`SmartGridModule` but its selector was used nowhere).
|
|
985
|
+
|
|
986
|
+
### Widget ↔ SmartComponent: the model routing is inverted (Phase 3.4)
|
|
987
|
+
|
|
988
|
+
A widget no longer waits to be collected by the screen component it lives in: it finds its
|
|
989
|
+
`SmartComponentApiClient` through DI and registers itself. `SmartComponent` carries the
|
|
990
|
+
channel as an inherited host directive, so **no host component has to be changed for the
|
|
991
|
+
routing itself** — everything below is the *legacy surface* that went with the old
|
|
992
|
+
collection path. The protocol is written up in `WIDGETS.md` next to this file; the decision
|
|
993
|
+
record behind it is ADR-0005 in the `platform-angular2` working copy.
|
|
994
|
+
|
|
995
|
+
#### What a host must delete (all of it mechanical, all reported by the compiler)
|
|
996
|
+
|
|
997
|
+
| Delete | Why |
|
|
998
|
+
|---|---|
|
|
999
|
+
| `this.useQueryLists = true;` | The property is gone. Its setter rejected anything but `true`, so every branch behind it was unreachable — this was a no-op that logged a warning. |
|
|
1000
|
+
| `[parentSmartComponent]="this"` on `<smart-component-layout>` | The input is gone; the layout injects the client. `strictTemplates` reports leftovers as **NG8002**. |
|
|
1001
|
+
| `this.addForm(id, smartForm, formComponent)` | Gone. A `<smartform>` that displays the client's model registers itself. Two call styles existed; where the return value was used, it was the `smartForm` argument — use that (which makes those lines self-assignments, so they just go). **Delete the scaffolding around the call too**: in p014 the 21 calls left behind 9 empty `if (this.formX) { }` guards and 3 `new Promise((r) => setTimeout(r, 500)).then(() => { })` wrappers whose only content had been the registration. The compiler does not complain about either. |
|
|
1002
|
+
| `this.widgets.delete(…)` / `this.widgets.set(…)` / `this.formWidgets…` | The maps are gone (they were only written by the legacy path and read by nothing). |
|
|
1003
|
+
| the 4th argument of `addGrid(smartGrid, options, useAsDefaultGrid, componentReference)` | `addGrid()` keeps the options and the default-grid role; the services it used to copy into the grid model (`uiActionService`, `uiActionDescriptorService`, `serviceToUse`, `viewContextService`) are the grid's own DI job now. |
|
|
1004
|
+
| `override getSmartXxxQL()` (e.g. the two `throw new Error` overrides in a `FilterPageService`) | The eight abstract getters are gone from `SmartComponentApiClient`. |
|
|
1005
|
+
| `this.getSmartGridsQL()` and friends | If a component really wants its own widgets, it declares the view query itself: `@ViewChildren(SmartGridComponent) grids!: QueryList<SmartGridComponent>` — same reach as the removed getter had. |
|
|
1006
|
+
| `handleDataChangeSubscriptions()` | Gone. A form publishes its own value-change keys when it constructs. |
|
|
1007
|
+
|
|
1008
|
+
Removed from the public API in the same step, in case a host imported them directly:
|
|
1009
|
+
`SmartComponentLayoutUtility` and its `EmbeddedSlotInfo` interface (the whole file — its
|
|
1010
|
+
collectors walked the layout tree for widgets, which nothing does now), and the `[parent]`
|
|
1011
|
+
inputs of `<smart-map>` / `<smart-diagram>` (they were bound to `parentSmartComponent` and read
|
|
1012
|
+
by nothing; both widgets resolve the client themselves). `initActions()` also runs less often:
|
|
1013
|
+
only when the model or its action list changes, no longer after every layout render, grid view
|
|
1014
|
+
init and filter model change — a host override that relied on one of those extra calls has to
|
|
1015
|
+
key off `dataChanged` instead.
|
|
1016
|
+
|
|
1017
|
+
The [codemod](#migrating-a-host-the-codemod) deletes the first two rows and the last one, and
|
|
1018
|
+
lists the rest — `addForm()`, the 4th `addGrid()` argument, the getter overrides — with their
|
|
1019
|
+
line numbers, because each of those is a small decision rather than a substitution.
|
|
1020
|
+
|
|
1021
|
+
#### What stays
|
|
1022
|
+
|
|
1023
|
+
- `SmartService` — a view-less client. Its `@ViewChildren` never populated anyway; they are
|
|
1024
|
+
simply gone now.
|
|
1025
|
+
- `initActions()` — still the hook a host overrides to build its own action list. The base
|
|
1026
|
+
implementation only asks for a render; toolbars pull their actions themselves.
|
|
1027
|
+
- `setUpDefaultTree()` / `createTreeService()`, `addGrid()`, `submitForms()`,
|
|
1028
|
+
`getInvalidFields()`, `uiActionModels` (now a getter over the model's actions).
|
|
1029
|
+
|
|
1030
|
+
#### `uiActionModels` is read-only now — assigning to it no longer compiles
|
|
1031
|
+
|
|
1032
|
+
It became a getter over the `actionModels` computed, so a host that *replaced* the list in
|
|
1033
|
+
place gets `TS2540: Cannot assign to 'uiActionModels' because it is a read-only property`.
|
|
1034
|
+
Measured in **p014 and p043**, `document-storage-editor.component.ts`:
|
|
1035
|
+
|
|
1036
|
+
```ts
|
|
1037
|
+
// before — no longer compiles
|
|
1038
|
+
this.uiActionModels = this.uiActionModels.filter((a) => a.uiAction.code !== 'CANCEL');
|
|
1039
|
+
|
|
1040
|
+
// after — filter into a field of your own and bind it
|
|
1041
|
+
protected readonly visibleActions = signal<UiActionModel[]>([]);
|
|
1042
|
+
// …
|
|
1043
|
+
this.visibleActions.set(this.uiActionModels.filter((a) => a.uiAction.code !== 'CANCEL'));
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
```html
|
|
1047
|
+
<smart-ui-action-toolbar [uiActionModels]="visibleActions()"></smart-ui-action-toolbar>
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
A plain field works just as well; what matters is that the **array reference changes**, which
|
|
1051
|
+
is what tells the toolbar to re-render. See also the entry-freeze note in the Phase 3.7
|
|
1052
|
+
section.
|
|
1053
|
+
|
|
1054
|
+
#### Behavioural deltas, accepted
|
|
1055
|
+
|
|
1056
|
+
- **Wider reach.** A widget in a host's *own* sub-component now participates, where a view
|
|
1057
|
+
query could not see it (measured on p014/p043: 5 toolbars and 3–4 forms per host). A form
|
|
1058
|
+
takes part only if it displays the client's component model, which is what the forms the
|
|
1059
|
+
queries used to find have in common; a form that renders something else of its own (a
|
|
1060
|
+
search box, a field editor) does not.
|
|
1061
|
+
- Two widgets sharing one identifier both reload; the old map kept only the last.
|
|
1062
|
+
- `getInvalidFields()` collects in subscription order rather than view order — visible only
|
|
1063
|
+
in the order of the names in the validation dialog.
|
|
1064
|
+
- A toolbar entry is tracked by a synthesized key (action code + ordinal among entries with
|
|
1065
|
+
the same code), so a button's DOM node — with its focus and ripple — follows its action
|
|
1066
|
+
across reordering. Rendering is otherwise identical.
|
|
1067
|
+
|
|
1068
|
+
#### `[smartComponentDetached]`
|
|
1069
|
+
|
|
1070
|
+
The one brake on the automatic registration. Put it on an element whose subtree deliberately
|
|
1071
|
+
renders something other than the client's model; widgets below it resolve an empty slot and
|
|
1072
|
+
neither reload nor take part in submit/validation.
|
|
1073
|
+
|
|
1074
|
+
```html
|
|
1075
|
+
<div smartComponentDetached>
|
|
1076
|
+
<smart-grid [smartGrid]="ownGrid" [uuid]="ownUuid"></smart-grid>
|
|
1077
|
+
</div>
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
#### `SmartSubject` is deleted
|
|
1081
|
+
|
|
1082
|
+
It was a no-op wrapper over `Subject`: `subscription.add(this.unsubscribe$.subscribe())`
|
|
1083
|
+
registers the teardown in the wrong direction, so after `destroy$.next()`, `.complete()` or
|
|
1084
|
+
`.unsubscribe()` subscribers still received values. Replace `new SmartSubject(destroy$)`
|
|
1085
|
+
with `new Subject()`; where a subscription really must die with a component, use
|
|
1086
|
+
`takeUntil(this._destroy$)` (which is what actually stopped delivery all along) or
|
|
1087
|
+
`takeUntilDestroyed()`. No host used it.
|
|
1088
|
+
|
|
1089
|
+
### Toolbars resolve their own actions and own the execution context (Phase 3.5)
|
|
1090
|
+
|
|
1091
|
+
One rule decides which actions a toolbar shows, and the toolbar — not each list entry —
|
|
1092
|
+
says who performs them. `WIDGETS.md`, *Toolbars*, is the short version; the decision record
|
|
1093
|
+
behind it is ADR-0006 in the `platform-angular2` working copy.
|
|
1094
|
+
|
|
1095
|
+
> A toolbar renders the actions **addressed to its `id`** (`uiAction.toolbar == id`). The
|
|
1096
|
+
> list comes from an explicit `[uiActionModels]` binding if there is one, otherwise from the
|
|
1097
|
+
> screen component above it in the DOM. **Without an `id` it never pulls** — "unaddressed" is
|
|
1098
|
+
> not an address, so a toolbar with neither an id nor a binding shows nothing.
|
|
1099
|
+
|
|
1100
|
+
`[uiActionModels]="uiActionModels"` therefore keeps meaning "put my page's unaddressed
|
|
1101
|
+
actions here" and needs no change.
|
|
1102
|
+
|
|
1103
|
+
#### `UseUiAction` is deleted; `UseUiAction2` is now `UiActionExecutor`
|
|
1104
|
+
|
|
1105
|
+
There is one executor interface. If a host class implements `UseUiAction` — a `submit` /
|
|
1106
|
+
`reSubscribeToChange` subject handshake — replace the two subjects with the two methods:
|
|
1107
|
+
|
|
1108
|
+
```ts
|
|
1109
|
+
// before
|
|
1110
|
+
export class DelegateInboxDialogService implements UseUiAction {
|
|
1111
|
+
submit: Subject<void> = new Subject();
|
|
1112
|
+
reSubscribeToChange: Subject<void> = new Subject();
|
|
1113
|
+
…
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// after
|
|
1117
|
+
export class DelegateInboxDialogService implements UiActionExecutor {
|
|
1118
|
+
submitForm(validate: boolean): void {
|
|
1119
|
+
this.submit.next(); // keep the subject if a component subscribes to it
|
|
1120
|
+
}
|
|
1121
|
+
getInvalidFields(): SmartFormInvalidFields {
|
|
1122
|
+
return { invalidFieldKeys: [], invalidFieldNames: [] };
|
|
1123
|
+
}
|
|
1124
|
+
…
|
|
1125
|
+
}
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
`Subject.next()` is synchronous, so a component that subscribed to `submit` has already run
|
|
1129
|
+
by the time `submitForm()` returns — which is exactly what the old handshake tried to
|
|
1130
|
+
express. Keep `reSubscribeToChange` only if something subscribes to it; the library no longer
|
|
1131
|
+
fires it.
|
|
1132
|
+
|
|
1133
|
+
This also **fixes a silent hang**: the old branch `await`ed `submit.toPromise()`, which only
|
|
1134
|
+
settles when somebody `complete()`s the subject. Three of the five implementors never did, so
|
|
1135
|
+
a `submit`/`model` action on those services waited forever.
|
|
1136
|
+
|
|
1137
|
+
#### `UiActionService.execute()` lost its first argument
|
|
1138
|
+
|
|
1139
|
+
It was always `uiActionModel.uiAction`:
|
|
1140
|
+
|
|
1141
|
+
```ts
|
|
1142
|
+
- this.uiActionService.execute(uiActionModel.uiAction, uiActionModel);
|
|
1143
|
+
+ this.uiActionService.execute(uiActionModel);
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
#### The execution context moved from the entry to the toolbar
|
|
1147
|
+
|
|
1148
|
+
| what | bind on the toolbar | entry field (still honoured) |
|
|
1149
|
+
|---|---|---|
|
|
1150
|
+
| who performs the action | `[executor]` | `serviceToUse` — **deprecated** |
|
|
1151
|
+
| which widget it runs against | `[widgetId]` | `widgetId` — **deprecated**, wins if set |
|
|
1152
|
+
| which row inside it | `[nodeId]` | `nodeId` — **deprecated**, wins if set |
|
|
1153
|
+
| extra params for every action | `[actionParams]` | (clone the `UiAction` — no longer necessary) |
|
|
1154
|
+
|
|
1155
|
+
`[executor]` defaults to the screen component the toolbar sits under, which is what almost
|
|
1156
|
+
every entry named. Bind it only when the actions belong to another API — a tree service, a
|
|
1157
|
+
filter editor, a dialog service of your own:
|
|
1158
|
+
|
|
1159
|
+
```html
|
|
1160
|
+
<smart-ui-action-toolbar [uiActionModels]="actions" [executor]="myService"></smart-ui-action-toolbar>
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
`[actionParams]` replaces cloning a `UiAction` per row just to inject a model:
|
|
1164
|
+
|
|
1165
|
+
```html
|
|
1166
|
+
<smart-ui-action-toolbar
|
|
1167
|
+
[uiActionModels]="rowActions"
|
|
1168
|
+
[widgetId]="gridId"
|
|
1169
|
+
[nodeId]="row.id"
|
|
1170
|
+
[actionParams]="{ model: row }"
|
|
1171
|
+
></smart-ui-action-toolbar>
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
#### Removed from the public API
|
|
1175
|
+
|
|
1176
|
+
| Removed | Replacement |
|
|
1177
|
+
|---|---|
|
|
1178
|
+
| `UseUiAction` | `UiActionExecutor` (see above) |
|
|
1179
|
+
| the third `SmartTable` constructor parameter and `getServiceToUse()` | nothing — the executor no longer travels through the table (0 host call sites) |
|
|
1180
|
+
| `SmartformComponent.getToolbars()` | nothing collects toolbars any more; each one resolves its own actions |
|
|
1181
|
+
| `ISmartFilterEditorService.get/setComplexToolbars()` and the `submit` / `reSubscribeToChange` members | as above |
|
|
1182
|
+
| `FileEditorToolbarComponent` | a plain `<smart-ui-action-toolbar>` with `[actionParams]` |
|
|
1183
|
+
| `SmartGridComponent.setupToolbar()` / `.toolbar` / `.headerToolbar` | the id is a binding; there is nothing to reach into |
|
|
1184
|
+
|
|
1185
|
+
#### Behavioural deltas, accepted
|
|
1186
|
+
|
|
1187
|
+
- A widget renders a toolbar exactly when its model carries a `toolbarId`, with no type list.
|
|
1188
|
+
A `toolbarId` on a type whose template has no place for a toolbar (`RECORDING_UPLOADER`)
|
|
1189
|
+
now logs a warning instead of being silently dropped.
|
|
1190
|
+
- Only the actions a toolbar actually shows are scheduled. Since 3.4 a toolbar armed every
|
|
1191
|
+
scheduled action of the view, so one action could fire from several toolbars at once.
|
|
1192
|
+
- A grid row's actions are no longer cloned per row; the row model travels as
|
|
1193
|
+
`[actionParams]`. The multi-file editor's per-file toolbars appear when their own list
|
|
1194
|
+
resolves rather than when a hidden sibling's did, and the tree's `doAction` clones instead
|
|
1195
|
+
of mutating the caller's `UiAction`.
|
|
1196
|
+
|
|
1197
|
+
### The form widget's rendering decisions (Phase 3.6)
|
|
1198
|
+
|
|
1199
|
+
Nothing here needs a code change in a host — there is no API to adapt to. What
|
|
1200
|
+
changes is what the widget puts on the page, so read this before you compare
|
|
1201
|
+
screenshots.
|
|
1202
|
+
|
|
1203
|
+
#### Every widget carried a spare empty wrapper; it is gone
|
|
1204
|
+
|
|
1205
|
+
The `COMPONENT` branch's wrapper had no type guard, so
|
|
1206
|
+
`<div class="widgetContainer"><div class="widgetContent"><ng-template
|
|
1207
|
+
#customComponent>` rendered after **every** widget of every type. Measured on the
|
|
1208
|
+
playground: 14 widgets, 28 `.widgetContainer` — one spare, empty, per widget.
|
|
1209
|
+
|
|
1210
|
+
The library's own CSS puts no box properties on `.widgetContainer`, so most pages
|
|
1211
|
+
will look identical. **Yours may not**, if you style that class with anything that
|
|
1212
|
+
occupies space. Both in-house hosts do, on one page:
|
|
1213
|
+
|
|
1214
|
+
```css
|
|
1215
|
+
/* p014 + p043, dossier-publication.component.css */
|
|
1216
|
+
:host ::ng-deep .widgetContainer { margin-bottom: 0.5rem !important; }
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
There, every widget loses half a rem of trailing space. Grep your stylesheets for
|
|
1220
|
+
`.widgetContainer` and check the pages the matching selectors reach. Rules that
|
|
1221
|
+
need content still match what they always matched — an empty wrapper never
|
|
1222
|
+
satisfied `.label.widgetContainer` or `.widgetContainer:has(…)`.
|
|
1223
|
+
|
|
1224
|
+
#### `MATRIX` and `YOUTUBE_PLAYER` now obey `isVisible` and get their `cssClass`
|
|
1225
|
+
|
|
1226
|
+
Those two branches sat outside the `@if (isVisible)` guard and outside the
|
|
1227
|
+
`<div [ngClass]="cssClass" class="container">` every other type lives in, so
|
|
1228
|
+
`isVisible: false` did not hide them and `cssClass` never reached them. Both are
|
|
1229
|
+
inside now. If a backend view relies on a matrix rendering while marked invisible,
|
|
1230
|
+
it will stop rendering.
|
|
1231
|
+
|
|
1232
|
+
#### A container keeps its invisible children
|
|
1233
|
+
|
|
1234
|
+
A `CONTAINER` widget's `ngOnInit` used to overwrite `widgetInstance.valueList`
|
|
1235
|
+
with the visible subset, destroying the invisible children in the model the
|
|
1236
|
+
backend sent. The visible subset is derived per check now, so the model stays
|
|
1237
|
+
whole and a later `applyConstraints()` that flips a child's `isVisible` brings it
|
|
1238
|
+
back — which it could not before. The DOM is unchanged: the same children render.
|
|
1239
|
+
|
|
1240
|
+
Side effect worth knowing: the model walks that recurse into a container's
|
|
1241
|
+
`valueList` (`applyComponentConstraints`, `copyValueFromFormToWidgetBeans`,
|
|
1242
|
+
`translateWidgets`) now see the invisible children too. Their form controls always
|
|
1243
|
+
existed — `createFormControls` runs over the whole model before any widget renders
|
|
1244
|
+
— so this only makes the widget beans agree with the controls.
|
|
1245
|
+
|
|
1246
|
+
#### A YouTube shorts link resolves
|
|
1247
|
+
|
|
1248
|
+
`parseYoutubeUrl` threw away its own `replace('/shorts/', '/watch?v=')`, so a
|
|
1249
|
+
shorts link produced no player. It plays now.
|
|
1250
|
+
|
|
1251
|
+
#### Removed from the public API
|
|
1252
|
+
|
|
1253
|
+
| Removed | Replacement |
|
|
1254
|
+
|---|---|
|
|
1255
|
+
| `SmartformwidgetComponent` | nothing — no host imported the class; `<smartform>` still renders it. 7.1 splits it into per-type components |
|
|
1256
|
+
| `SmartWidgetSettings` (its only member was `static useUtc`) | nothing — **delete the assignment** |
|
|
1257
|
+
|
|
1258
|
+
3.6 moved the `useUtc` static into `SmartNgClientConfig.useUtcDates`; **3.8 then removed the
|
|
1259
|
+
field altogether**, together with the moment adapter it fed. If you set
|
|
1260
|
+
`SmartWidgetSettings.useUtc = true` before bootstrap, delete that line and read
|
|
1261
|
+
[Dates §4](#4-smartngclientconfiguseutcdates-is-removed) — the timezone contract is fixed now,
|
|
1262
|
+
and the flag defaulted to `false` everywhere anyway.
|
|
1263
|
+
|
|
1264
|
+
#### What deliberately did not change
|
|
1265
|
+
|
|
1266
|
+
A required field is marked in both label positions, by two different mechanisms:
|
|
1267
|
+
a shown label (`showLabel: true`) is the widget's own `<h4>` and carries a literal
|
|
1268
|
+
` *`; a `mat-label` sits inside a `mat-form-field`, which renders
|
|
1269
|
+
`.mat-mdc-form-field-required-marker` from the control's own validators. That is
|
|
1270
|
+
why `getWidgetLabel` ties its asterisk to `showLabel` — dropping the condition
|
|
1271
|
+
would mark the floating label twice.
|
|
1272
|
+
|
|
1273
|
+
### Change detection: frozen action entries, no zone.js (Phase 3.7)
|
|
1274
|
+
|
|
1275
|
+
7.0 runs without zone.js. Every component of the library is `OnPush` — nothing is checked
|
|
1276
|
+
just because something, somewhere, ticked — and the library says when its own state changed.
|
|
1277
|
+
Two consequences reach a host.
|
|
1278
|
+
|
|
1279
|
+
#### A `UiActionModel` is frozen: build an entry, never edit one
|
|
1280
|
+
|
|
1281
|
+
Every field is `readonly`, and `[uiActionModels]` takes `readonly UiActionModel[]`.
|
|
1282
|
+
|
|
1283
|
+
```ts
|
|
1284
|
+
// before — compiles on 6.x, and on 7.0 changes nothing on screen
|
|
1285
|
+
entry.cssClass = 'active-nav-action';
|
|
1286
|
+
|
|
1287
|
+
// after
|
|
1288
|
+
this.actions = this.actions.map((a) =>
|
|
1289
|
+
a.uiAction.code === code ? { ...a, cssClass: 'active-nav-action' } : a
|
|
1290
|
+
);
|
|
1291
|
+
```
|
|
1292
|
+
|
|
1293
|
+
Measured across the 12 hosts in scope (2026-07-28): **72 sites in 7 hosts**, all of them
|
|
1294
|
+
`cssClass` — p009-angular alone has 38, p014 and p043 12 each. The codemod prints every one
|
|
1295
|
+
of them with its line number; it cannot rewrite them, because only you know which array the
|
|
1296
|
+
entry belongs to.
|
|
1297
|
+
|
|
1298
|
+
The compile error and the fix are the same thing. Writing into an entry that a toolbar is
|
|
1299
|
+
already rendering never reached the screen under zone.js either — it worked only because
|
|
1300
|
+
some other event happened to tick the application. Rebuilding the array is what actually
|
|
1301
|
+
re-renders.
|
|
1302
|
+
|
|
1303
|
+
**The gap this does not close.** `this.actions[0] = { ...this.actions[0], cssClass: 'x' }`
|
|
1304
|
+
compiles: `actions` is your field, and the freeze only covers the entries. It keeps the same
|
|
1305
|
+
array reference, so the toolbar does not re-render. Reassign the array — or, if you hold it
|
|
1306
|
+
in a `signal`, `update()` it.
|
|
1307
|
+
|
|
1308
|
+
The descriptor a toolbar resolves is no longer written back into the entry either. Nothing
|
|
1309
|
+
outside the toolbar read it; if you did, resolve it yourself through
|
|
1310
|
+
`UiActionDescriptorService.getActionDescriptor(uiAction)` — which is
|
|
1311
|
+
[synchronous in 7.0](#getactiondescriptor-is-synchronous-phase-39).
|
|
1312
|
+
|
|
1313
|
+
#### Client-side translation is gone
|
|
1314
|
+
|
|
1315
|
+
`SmartTranslateService`, `viewContext.translateService` and the `translateServiceChanged`
|
|
1316
|
+
subject are removed, together with `originalLabel` / `originalPlaceholder` on the widget
|
|
1317
|
+
interfaces. No measured host assigned any of them — the three references in p014/p043 were
|
|
1318
|
+
unused imports — and the backend localizes server-side.
|
|
1319
|
+
|
|
1320
|
+
What the frontend authors itself is now configuration:
|
|
1321
|
+
|
|
1322
|
+
```ts
|
|
1323
|
+
provideSmartNgClient({
|
|
1324
|
+
errorDialog: { title: 'Hiba', message: 'Váratlan hiba történt.', buttonLabel: 'Rendben' },
|
|
1325
|
+
});
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1328
|
+
`SmartViewContextService.getSmartViewContextApiErrorByCode()` is no longer `async`.
|
|
1329
|
+
|
|
1330
|
+
#### If your host still bootstraps with zone.js
|
|
1331
|
+
|
|
1332
|
+
Nothing forces you to drop it — the library works either way, because it no longer depends
|
|
1333
|
+
on an application-wide tick. The playground runs `provideZonelessChangeDetection()` with no
|
|
1334
|
+
`zone.js` polyfill, and that is the configuration 7.0 is tested in.
|
|
1335
|
+
|
|
1336
|
+
If you do go zoneless, the same rule applies to your own components: state written from a
|
|
1337
|
+
subscription, a promise or a timer needs a signal or a `markForCheck()`; state written from a
|
|
1338
|
+
template event or an input does not.
|
|
1339
|
+
|
|
1340
|
+
### Dates: moment → date-fns, and the timezone contract (Phase 3.8)
|
|
1341
|
+
|
|
1342
|
+
**This section needs action at install time.** The reasoning is ADR-0008 in the
|
|
1343
|
+
`platform-angular2` working copy; the contract itself is stated in §4 below.
|
|
1344
|
+
|
|
1345
|
+
#### 1. Swap the peer dependency
|
|
1346
|
+
|
|
1347
|
+
`@angular/material-moment-adapter` is out of the library's `peerDependencies`;
|
|
1348
|
+
`@angular/material-date-fns-adapter` and `date-fns` are in. `date-fns` is listed explicitly
|
|
1349
|
+
rather than left transitive, because the library imports `date-fns/locale` itself.
|
|
1350
|
+
|
|
1351
|
+
```bash
|
|
1352
|
+
npm uninstall @angular/material-moment-adapter moment
|
|
1353
|
+
npm install @angular/material-date-fns-adapter@^22.0.0 date-fns@^4
|
|
1354
|
+
```
|
|
1355
|
+
|
|
1356
|
+
If your own code imports moment for something unrelated, keep it — nothing here forces its
|
|
1357
|
+
removal, only the adapter's.
|
|
1358
|
+
|
|
1359
|
+
**But check *how* it imports moment.** `import * as moment from 'moment'` stops being callable
|
|
1360
|
+
under `moduleResolution: "bundler"`, which the Angular 20 hop turns on: moment is an
|
|
1361
|
+
`export =` module, and a namespace import of one is not a callable value. The symptom is
|
|
1362
|
+
**`TS2349: This expression is not callable`** on every `moment(...)` call, and it arrives at the
|
|
1363
|
+
Angular 20 hop rather than at this step. Two ways out:
|
|
1364
|
+
|
|
1365
|
+
- `esModuleInterop: true` plus `import moment from 'moment'` — correct, but it changes how every
|
|
1366
|
+
CommonJS import in the app resolves, which is a wide blast radius for one file.
|
|
1367
|
+
- Replace the calls. Worth checking first how much moment is actually doing: in p014 the three
|
|
1368
|
+
surviving calls were `moment(new Date()).toDate()` (i.e. `new Date()`) and two moments compared
|
|
1369
|
+
with `<=` (`Date` compares through `valueOf()` identically). All three became plain `Date`s, and
|
|
1370
|
+
**nothing under `src/` imported moment any more** — so the `npm uninstall … moment` above was
|
|
1371
|
+
right for that host after all, just for a reason nobody predicted.
|
|
1372
|
+
|
|
1373
|
+
#### 2. Leave your `MAT_DATE_LOCALE` provider alone
|
|
1374
|
+
|
|
1375
|
+
If you have this line — p014 and p043 both do — **keep it as it is**:
|
|
1376
|
+
|
|
1377
|
+
```ts
|
|
1378
|
+
{ provide: MAT_DATE_LOCALE, useValue: 'hu-HU' },
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
The strict reading of the date-fns adapter would require a `Locale` **object** here, and a bare
|
|
1382
|
+
string would throw inside date-fns the first time a user opened a date field. The library's
|
|
1383
|
+
`SmartDateFnsAdapter` accepts both: it resolves `'hu'` / `'hu-HU'` / `'en'` / `'en-US'`
|
|
1384
|
+
(case-insensitively) to a date-fns locale, passes a real `Locale` object through untouched, and
|
|
1385
|
+
falls back to Hungarian with a `console.warn` for anything else.
|
|
1386
|
+
|
|
1387
|
+
So: **hosts on Hungarian or English need no change.** For any other language, provide a real
|
|
1388
|
+
date-fns locale:
|
|
1389
|
+
|
|
1390
|
+
```ts
|
|
1391
|
+
import { de } from 'date-fns/locale';
|
|
1392
|
+
…
|
|
1393
|
+
{ provide: MAT_DATE_LOCALE, useValue: de },
|
|
1394
|
+
```
|
|
1395
|
+
|
|
1396
|
+
`SmartNgClientConfig.dateLocale` is unchanged — still a `string`, still defaulting to `'hu-HU'`.
|
|
1397
|
+
|
|
1398
|
+
#### 3. If you override `MAT_DATE_FORMATS`, your format strings will throw — and you probably want to delete the override
|
|
1399
|
+
|
|
1400
|
+
Measured across 12 hosts on 2026-07-28: three of them (**p014**, **p043**, **p009-angular**)
|
|
1401
|
+
provide their own formats next to the locale, and two more (`app-vlab`, `app-tournament`) have the
|
|
1402
|
+
same line commented out:
|
|
1403
|
+
|
|
1404
|
+
```ts
|
|
1405
|
+
{ provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
|
|
1406
|
+
```
|
|
1407
|
+
|
|
1408
|
+
with
|
|
1409
|
+
|
|
1410
|
+
```ts
|
|
1411
|
+
export const MY_FORMATS = {
|
|
1412
|
+
parse: { dateInput: 'YYYY.MM.DD' },
|
|
1413
|
+
display: { dateInput: 'YYYY.MM.DD', monthYearLabel: 'YYYY',
|
|
1414
|
+
dateA11yLabel: 'LL', monthYearA11yLabel: 'YYYY' },
|
|
1415
|
+
};
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
Those are **moment tokens**. date-fns treats `YYYY`, `DD`, `YY` and `D` as *protected* and throws a
|
|
1419
|
+
`RangeError` on them, and its `LL` means something else entirely (stand-alone month, not a
|
|
1420
|
+
localized long date). So this override **crashes at runtime, on the first screen with a date
|
|
1421
|
+
field** — the same failure shape as the string locale above, and just as invisible to the compiler.
|
|
1422
|
+
|
|
1423
|
+
**The fix is almost certainly deletion.** That `MY_FORMATS` exists to render `YYYY.MM.DD`, which is
|
|
1424
|
+
exactly what the library's own `SMART_DATE_FORMATS` now produces (`2026.07.28.`). Drop the
|
|
1425
|
+
provider and the constant and you get the format you wanted, plus the more forgiving parsing.
|
|
1426
|
+
|
|
1427
|
+
If you genuinely need a different format, translate the tokens rather than copying them:
|
|
1428
|
+
|
|
1429
|
+
| moment | date-fns |
|
|
1430
|
+
|---|---|
|
|
1431
|
+
| `YYYY` | `yyyy` |
|
|
1432
|
+
| `DD` | `dd` |
|
|
1433
|
+
| `LL` | `PP` (or spell it out: `yyyy. MMMM d.`) |
|
|
1434
|
+
| `MMM YYYY` | `yyyy. LLL` |
|
|
1435
|
+
|
|
1436
|
+
Note that the library's `parse.dateInput` is an **array** — `DateFnsAdapter._parse` tries each entry
|
|
1437
|
+
in turn (and ISO-8601 first), so you can list several accepted spellings instead of one.
|
|
1438
|
+
|
|
1439
|
+
#### 4. `SmartNgClientConfig.useUtcDates` is removed
|
|
1440
|
+
|
|
1441
|
+
Delete it if you set it. No known host does. It existed for a client-configurable timezone that
|
|
1442
|
+
was abandoned as a requirement; the contract is now fixed and explicit:
|
|
1443
|
+
|
|
1444
|
+
> **The browser's zone on screen. Zulu (`…Z`) on the wire. The server converts.**
|
|
1445
|
+
|
|
1446
|
+
This is unchanged behaviour in practice — `useUtcDates` defaulted to `false`, and the moment
|
|
1447
|
+
adapter with `useUtc: false` behaves exactly like a plain `Date`.
|
|
1448
|
+
|
|
1449
|
+
#### 5. What a date widget's value now *is*
|
|
1450
|
+
|
|
1451
|
+
Form control values from the date pickers are plain **`Date`** objects, where they were
|
|
1452
|
+
`Moment` objects. The values reach your backend through a `UiActionRequest` param, so the wire
|
|
1453
|
+
format is `JSON.stringify` of that value — and `Date.toJSON()` emits the same ISO-8601 UTC string
|
|
1454
|
+
`Moment.toJSON()` did. **Nothing changes on the wire.** Only host code that read a value out of a
|
|
1455
|
+
form control and called a moment method on it (`.format()`, `.add()`, `.startOf()`) needs
|
|
1456
|
+
rewriting; the platform APIs never exposed a `Moment` in a signature.
|
|
1457
|
+
|
|
1458
|
+
#### 6. The one visible difference
|
|
1459
|
+
|
|
1460
|
+
Date inputs render **`2026.07.28.`** where 6.x rendered `2026.7.28.` — the documented `YYYY.MM.DD`
|
|
1461
|
+
convention. The old rendering was not a decision: `MAT_MOMENT_DATE_FORMATS` used moment's `'l'`,
|
|
1462
|
+
which strips the zero padding off the Hungarian `L`. Typed input is **more** forgiving than 6.x,
|
|
1463
|
+
not less: `2026.07.28.`, `2026.07.28`, `2026.7.28.`, `2026.7.28` and ISO-8601 are all accepted.
|
|
1464
|
+
|
|
1465
|
+
The month picker is unchanged (`07/2026`), and grid/table date columns were never affected —
|
|
1466
|
+
`SmartDatePipe` and friends extend Angular's own `DatePipe` and never used moment.
|
|
1467
|
+
|
|
1468
|
+
**Verified on a running host** (p014, 2026-07-29, live backend, `Europe/Budapest`). Picking
|
|
1469
|
+
2026-07-15 in a `Kezdődátum` field gave, end to end:
|
|
1470
|
+
|
|
1471
|
+
| | |
|
|
1472
|
+
|---|---|
|
|
1473
|
+
| input | `2026.07.15.` |
|
|
1474
|
+
| widget value | `instanceof Date` — not a moment, not a luxon `DateTime` |
|
|
1475
|
+
| `JSON.stringify` of that value | `"2026-07-14T22:00:00.000Z"` |
|
|
1476
|
+
| `getHours()` | `0` |
|
|
1477
|
+
|
|
1478
|
+
That third row is the whole contract in one line: the value's `toJSON()` **is** the wire format
|
|
1479
|
+
(see §4), and it is zulu, while the screen and `getHours()` stay local — 15 July 00:00 CEST is
|
|
1480
|
+
14 July 22:00 UTC. A grid column on the same host rendered `2026.04.20 0:03`, confirming the
|
|
1481
|
+
untouched pipe path.
|
|
1482
|
+
|
|
1483
|
+
### HTTP: the library no longer forces the XHR backend (Phase 3.8)
|
|
1484
|
+
|
|
1485
|
+
`provideSmartNgClient()` used to pass `withXhr()` to its internal `provideHttpClient()` call,
|
|
1486
|
+
overriding the Angular 22 default on your behalf. It no longer does — the application runs on the
|
|
1487
|
+
framework default **fetch** backend.
|
|
1488
|
+
|
|
1489
|
+
For almost every host this is invisible. The one behavioural difference that matters: **the fetch
|
|
1490
|
+
backend does not support upload progress.** A request with `reportProgress: true` and a body
|
|
1491
|
+
throws `NG2824` instead of emitting `HttpUploadProgress` events.
|
|
1492
|
+
|
|
1493
|
+
Measured 2026-07-28 across 12 hosts: **`reportProgress: true` and `observe: 'events'` appear
|
|
1494
|
+
nowhere** outside the generated API services' unused parameters. So this is a theoretical risk for
|
|
1495
|
+
the current host set, not a live one. If *your* code does ask for upload progress, pass the
|
|
1496
|
+
feature back in:
|
|
1497
|
+
|
|
1498
|
+
```ts
|
|
1499
|
+
import { withXhr } from '@angular/common/http';
|
|
1500
|
+
|
|
1501
|
+
provideSmartNgClient({ httpFeatures: [withXhr()] })
|
|
1502
|
+
```
|
|
1503
|
+
|
|
1504
|
+
The upside is that the "**do not call `provideHttpClient()` twice**" trap this guide warned about
|
|
1505
|
+
is gone rather than merely documented: a second call now re-provides the same backend the library
|
|
1506
|
+
already uses. The library's interceptors were never at risk from it — they are `multi` providers.
|
|
1507
|
+
|
|
1508
|
+
### `getActionDescriptor()` is synchronous (Phase 3.9)
|
|
1509
|
+
|
|
1510
|
+
`UiActionDescriptorService.getActionDescriptor(uiAction)` returns a `UiActionDescriptor`.
|
|
1511
|
+
It used to return a `Promise<UiActionDescriptor>`, and it was `async` for exactly one
|
|
1512
|
+
reason: the client-side translation layer that [Phase 3.7](#client-side-translation-is-gone)
|
|
1513
|
+
retired. Nothing it reads has been asynchronous since, so every caller was awaiting a value
|
|
1514
|
+
that was already there.
|
|
1515
|
+
|
|
1516
|
+
**If you only `await` the call, you are already fine.** `await` on a non-promise is legal
|
|
1517
|
+
TypeScript, so this compiles and behaves identically before and after. The codemod drops the
|
|
1518
|
+
`await` anyway, so the code stops claiming an asynchrony that is not there:
|
|
1519
|
+
|
|
1520
|
+
```ts
|
|
1521
|
+
const descriptor = await this.uiActionDescriptor.getActionDescriptor(uiAction); // 6.x
|
|
1522
|
+
const descriptor = this.uiActionDescriptor.getActionDescriptor(uiAction); // 7.0
|
|
1523
|
+
```
|
|
1524
|
+
|
|
1525
|
+
Two shapes genuinely break. Measured 2026-07-28 across the 12 hosts in scope: **5 hosts,
|
|
1526
|
+
7 sites** — and none of them in a host's own `getActionDescriptor**s**()` map helper, which
|
|
1527
|
+
is an unrelated API and is left alone.
|
|
1528
|
+
|
|
1529
|
+
**1. A subclass that overrides it** — `TS2416`, because `Promise<UiActionDescriptor>` is not
|
|
1530
|
+
assignable to `UiActionDescriptor`. Found in three hosts, as a byte-identical
|
|
1531
|
+
`MdmUiActionDescriptorService`. **The codemod rewrites this one.**
|
|
1532
|
+
|
|
1533
|
+
```ts
|
|
1534
|
+
// 6.x
|
|
1535
|
+
override async getActionDescriptor(uiAction: UiAction): Promise<UiActionDescriptor> {
|
|
1536
|
+
let d: UiActionDescriptor = await super.getActionDescriptor(basicUiAction);
|
|
1537
|
+
…
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
// 7.0
|
|
1541
|
+
override getActionDescriptor(uiAction: UiAction): UiActionDescriptor {
|
|
1542
|
+
let d: UiActionDescriptor = super.getActionDescriptor(basicUiAction);
|
|
1543
|
+
…
|
|
1544
|
+
}
|
|
1545
|
+
```
|
|
1546
|
+
|
|
1547
|
+
This is also the service you hand to a toolbar through `[uiActionDescriptorService]`, so a
|
|
1548
|
+
host with a custom one hits this at compile time rather than on screen.
|
|
1549
|
+
|
|
1550
|
+
**2. `.then(…)` on the result** — `TS2339`, because a descriptor has no `.then`. Found in
|
|
1551
|
+
two hosts, four sites. **The codemod reports this one and does not rewrite it**: turning a
|
|
1552
|
+
callback into straight-line code means moving its body, which is a judgment call. The fix is
|
|
1553
|
+
mechanical anyway — assign, then inline:
|
|
1554
|
+
|
|
1555
|
+
```ts
|
|
1556
|
+
// 6.x
|
|
1557
|
+
constructForm() {
|
|
1558
|
+
this.actionDescriptorService.getActionDescriptor(this.uiAction).then((d) => {
|
|
1559
|
+
this.buttonTitle = d.title;
|
|
1560
|
+
this.buttonColor = d.color;
|
|
1561
|
+
});
|
|
1562
|
+
…
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// 7.0
|
|
1566
|
+
constructForm() {
|
|
1567
|
+
const d = this.actionDescriptorService.getActionDescriptor(this.uiAction);
|
|
1568
|
+
this.buttonTitle = d.title;
|
|
1569
|
+
this.buttonColor = d.color;
|
|
1570
|
+
…
|
|
1571
|
+
}
|
|
1572
|
+
```
|
|
1573
|
+
|
|
1574
|
+
If the callback was the reason a field lived in a `signal` — because the value arrived after
|
|
1575
|
+
the first render — it no longer needs to. The library made that same simplification in its
|
|
1576
|
+
own three action dialogs and its snack bar.
|
|
1577
|
+
|
|
1578
|
+
**What went synchronous with it, inside the library:** the toolbar's descriptor resolution,
|
|
1579
|
+
`SmartGridComponent`'s header construction, and `SmartTreeGenericService.cacheActionDesciptors()`
|
|
1580
|
+
and **`syncTree()`**. `syncTree()` now returns `void`, which is what
|
|
1581
|
+
`SmartTreeServiceInterface` always declared; a host that calls it and discards the result —
|
|
1582
|
+
the only shape measured — needs no change, and a host that `await`s it still compiles.
|
|
1583
|
+
|
|
1584
|
+
#### The same cleanup, elsewhere: `SmartdialogService.closeDialog()` returns `void`
|
|
1585
|
+
|
|
1586
|
+
A sweep for the same shape found eight more library functions declared `async` with nothing
|
|
1587
|
+
asynchronous in them. Only one of them is on a surface a host touches:
|
|
1588
|
+
**`SmartdialogService.closeDialog(stopPropagate?)` returns `void`** instead of
|
|
1589
|
+
`Promise<void>`.
|
|
1590
|
+
|
|
1591
|
+
**Nothing to do.** Both measured hosts that extend `SmartdialogService` override this method
|
|
1592
|
+
as `override async closeDialog(): Promise<void>`, and that **still compiles**: TypeScript
|
|
1593
|
+
accepts any return type where the base declares `void`. `await this.closeDialog()` keeps
|
|
1594
|
+
compiling too. This is the difference from `getActionDescriptor` above — that one returns a
|
|
1595
|
+
*value*, which is why the same override shape fails there with `TS2416` and not here.
|
|
1596
|
+
|
|
1597
|
+
You may of course drop the `async` from your own override; nothing requires it.
|
|
1598
|
+
|
|
1599
|
+
### Upload actions now wait for the server, and report their failures (Phase 3.9)
|
|
1600
|
+
|
|
1601
|
+
**This one changes behaviour, on purpose.** `SmartViewContextService.performUploadAction()`
|
|
1602
|
+
and `performUploadMultipleAction()` used to start the upload and resolve immediately,
|
|
1603
|
+
without waiting for it and without passing on its failure. Every other action in the family
|
|
1604
|
+
(`performAction`, `performWidgetAction`, `performWidgetMainAction`, `dataChanged`) always
|
|
1605
|
+
waited; the two upload ones were an omission.
|
|
1606
|
+
|
|
1607
|
+
Two things follow, and both are visible in a running app:
|
|
1608
|
+
|
|
1609
|
+
1. **An upload action now blocks until the server has answered.** Before, the action was
|
|
1610
|
+
"done" the moment the request went out — so `UiActionService` showed its success snackbar
|
|
1611
|
+
over an upload that was still in flight, and anything the action was sequenced before ran
|
|
1612
|
+
against a view the server had not updated yet. Now the snackbar appears when the upload
|
|
1613
|
+
really has succeeded. On a large file that is a longer wait than 6.x showed you.
|
|
1614
|
+
|
|
1615
|
+
2. **A failed upload now reaches your error handling.** The rejection used to be dropped —
|
|
1616
|
+
it became an unhandled promise rejection, `UiActionService`'s `catch` never ran, and the
|
|
1617
|
+
user was told the upload had worked. It now propagates, which means
|
|
1618
|
+
`setActionErrorHandler()` (or the default error dialog) will fire on upload failures it
|
|
1619
|
+
never fired on before. **If your host reports "new" upload errors after upgrading, they
|
|
1620
|
+
are not new** — they were happening silently.
|
|
1621
|
+
|
|
1622
|
+
Multi-file uploads also go out **one batch at a time** now. The batches were already being
|
|
1623
|
+
computed from the descriptor's `maxSize` and `maxBatchSize`, but they were then all sent at
|
|
1624
|
+
once, which put the whole set on the server simultaneously and defeated the split. If your
|
|
1625
|
+
backend measured upload concurrency, this is why it drops.
|
|
1626
|
+
|
|
1627
|
+
Nothing to change in host code.
|
|
1628
|
+
|
|
1629
|
+
### New in 7.0: a grid row can render a backend layout (#29717)
|
|
1630
|
+
|
|
1631
|
+
A card-mode grid row that carries `layoutDescriptor.componentLayouts['GRID_ROW_LAYOUT']`
|
|
1632
|
+
renders that layout instead of the card component the host registered under
|
|
1633
|
+
`'<GRID_ID>Card'`. Both branches stay: a row without the descriptor behaves exactly as
|
|
1634
|
+
before, and this needs no host change.
|
|
1635
|
+
|
|
1636
|
+
Two consequences worth knowing if you style or extend it:
|
|
1637
|
+
|
|
1638
|
+
- The rendered layout's root element gets the `gridCardLayout` class, applied by the card to
|
|
1639
|
+
its own child rather than pushed into the `style.classesToAdd` of the object the backend
|
|
1640
|
+
sent. Nothing mutates the server model.
|
|
1641
|
+
- **The row's actions reach the layout's toolbars by a pull, not a walk.** The card provides
|
|
1642
|
+
a `SmartActionHost` for its row subtree and writes the row's action list into it; a toolbar
|
|
1643
|
+
inside the layout resolves that host before falling back to the page client. If you build
|
|
1644
|
+
a component that owns such a subtree, do the same — `WIDGETS.md`, *Toolbars*.
|
|
1645
|
+
|
|
1646
|
+
This shipped in 7.0.0 rather than as a 6.0.3x patch because it is built on the inverted
|
|
1647
|
+
widget contract that 7.0 introduces.
|
|
1648
|
+
|
|
1649
|
+
### Writing your own widget
|
|
1650
|
+
|
|
1651
|
+
`WIDGETS.md`, next to this file, is the protocol: how a widget finds its screen component
|
|
1652
|
+
through DI and registers itself, when to opt out with `[smartComponentDetached]`, what has to
|
|
1653
|
+
be a signal now that there is no zone, how a toolbar resolves its actions, and the three
|
|
1654
|
+
traps that have actually bitten (imperatively-fed inputs cannot be signal inputs; `@for`
|
|
1655
|
+
tracking over backend objects; clear-then-apply styling).
|
|
1656
|
+
|
|
1657
|
+
In 6.x this was not possible at all — `SmartComponentApiClient` collected its children with
|
|
1658
|
+
eight `@ViewChildren` over eight concrete widget classes.
|
|
1659
|
+
|
|
1660
|
+
### Imperatively created components are given their inputs properly
|
|
1661
|
+
|
|
1662
|
+
`ComponentFactoryService` creates the components the library instantiates by hand — grid
|
|
1663
|
+
cards, expanded rows, table cell components, the expandable section's content, the form's
|
|
1664
|
+
`COMPONENT` widget, the filter editor's field editors — and most of those classes come from a
|
|
1665
|
+
host. It used to write their inputs by assigning the field. Two things follow, and both are
|
|
1666
|
+
fixed:
|
|
1667
|
+
|
|
1668
|
+
- **A declared input is now written with `ref.setInput()`**, i.e. the same way a template
|
|
1669
|
+
binding writes it. So a **signal input** works (assignment used to overwrite the input
|
|
1670
|
+
function with the value, and the component's next `this.x()` threw `x is not a function`),
|
|
1671
|
+
an **aliased** input is addressable under either name, and **`ngOnChanges` runs**.
|
|
1672
|
+
- **Falsy values arrive.** The old guard was `if (value)`, so `false`, `0` and `''` never
|
|
1673
|
+
reached the component. Only `undefined` is skipped now, which keeps meaning "not passed".
|
|
1674
|
+
|
|
1675
|
+
If your component takes something from the library this way and the key is **not** a declared
|
|
1676
|
+
input, the field is still assigned — nothing breaks — but you get one `console.warn` per
|
|
1677
|
+
component type and key. Declaring it with `@Input()` (or as a signal input) is the fix.
|
|
1678
|
+
|
|
1679
|
+
**Check your falsy defaults.** If a host component relied on the old guard — expecting to
|
|
1680
|
+
keep its own default when the library passed `false` or `''` — it now receives the value.
|
|
1681
|
+
This is the only part of 7.0 where a bugfix can change what you see on screen without any
|
|
1682
|
+
code of yours changing.
|
|
1683
|
+
|
|
1684
|
+
### Bugfixes shipped with 7.0
|
|
1685
|
+
|
|
1686
|
+
- `SmartformwidgetComponent.ngAfterViewInit` no longer crashes with
|
|
1687
|
+
`Cannot read properties of undefined (reading 'valueChanges')` when the form
|
|
1688
|
+
contains a `MONTH_PICKER` widget (it wrongly subscribed to the non-existent
|
|
1689
|
+
`<key>-time` control; only `DATE_TIME_PICKER` has one).
|
|
1690
|
+
- A widget nested in a CONTAINER receives `blurSophisticatedValueChange`; the recursion never
|
|
1691
|
+
passed it down, so a BLUR-mode child would have thrown on blur.
|
|
1692
|
+
- `SmartformComponent` reaches widgets nested in a CONTAINER when it changes them in place
|
|
1693
|
+
(values, constraints, the touched state after a failed submit). The view query it used
|
|
1694
|
+
could not see them.
|
|
1695
|
+
- `UiActionDescriptorService` no longer shares one dialog object between actions. Any action
|
|
1696
|
+
whose descriptor had no `dialog` of its own was given the service's single placeholder — the
|
|
1697
|
+
same object every time — and had its title and button caption written into it, so resolving
|
|
1698
|
+
one action renamed the dialog every other one was holding. The service is
|
|
1699
|
+
`providedIn: 'root'`, so this outlasted the view.
|
|
1700
|
+
|
|
1701
|
+
What you saw on screen, measured on both versions:
|
|
1702
|
+
|
|
1703
|
+
| The action | 6.x, when the dialog opened | 7.0 |
|
|
1704
|
+
|---|---|---|
|
|
1705
|
+
| described by the backend, no dialog of its own | **another action's code** | its own code |
|
|
1706
|
+
| described by nothing at all | **another action's code**, or empty | empty |
|
|
1707
|
+
| registered client-side, no dialog of its own | its own code | unchanged |
|
|
1708
|
+
|
|
1709
|
+
The middle column is not a typo. An action the backend describes is resolved twice in the
|
|
1710
|
+
normal flow — once when the toolbar renders it, again when the dialog it opens resolves it
|
|
1711
|
+
for itself — and the first resolution wrote a dialog onto the action's own descriptor, so the
|
|
1712
|
+
second one skipped the step that fills in the title. What the dialog then showed was whatever
|
|
1713
|
+
code had last passed through the shared object. An **already open** dialog could change too,
|
|
1714
|
+
since its title is re-read on every change-detection pass.
|
|
1715
|
+
|
|
1716
|
+
An action nothing describes still gets an untitled placeholder dialog: its *button* carries
|
|
1717
|
+
the code, the dialog does not. That is unchanged and deliberate — such an action is a gap in
|
|
1718
|
+
the host's descriptor map, and this is the honest rendering of it.
|
|
1719
|
+
|
|
1720
|
+
A resolved descriptor is now the caller's own object throughout, which also means
|
|
1721
|
+
`getActionDescriptor()` no longer writes a placeholder dialog and a `SNACKBAR` feedbackType
|
|
1722
|
+
into the `descriptor` the backend put on the action itself.
|