@dsivd/prestations-ng 19.2.0-beta.1 → 19.2.0-beta.10
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/CHANGELOG.md +182 -0
- package/CONTRIBUTING.md +43 -3
- package/GOOD_PRACTICES.md +186 -29
- package/UPGRADING_V19.md +181 -1
- package/dsivd-prestations-ng-19.2.0-beta.10.tgz +0 -0
- package/eslint/configs/spec-ts-recommended.mjs +34 -0
- package/eslint/configs/template-recommended.mjs +2 -0
- package/eslint/configs/template-rules.mjs +6 -2
- package/eslint/configs/ts-recommended.mjs +2 -0
- package/eslint/configs/ts-rules.mjs +6 -2
- package/eslint/index.mjs +17 -0
- package/eslint/rules/no-direct-signal-mutation.mjs +1 -1
- package/fesm2022/dsivd-prestations-ng.mjs +1626 -1142
- package/fesm2022/dsivd-prestations-ng.mjs.map +1 -1
- package/package.json +1 -2
- package/schematics/ng-add/index.js +2 -4
- package/schematics/ng-update/migrations/update-13_2_0/index.js +7 -2
- package/schematics/utility/add-allowed-common-js-dependencies.js +39 -29
- package/schematics/utility/remove-emit-decorator-metadata.js +8 -9
- package/src/eslint/configs/__tests__/configs.test.mjs +46 -2
- package/src/eslint/configs/spec-ts-recommended.mjs +34 -0
- package/src/eslint/configs/template-recommended.mjs +2 -0
- package/src/eslint/configs/template-rules.mjs +6 -2
- package/src/eslint/configs/ts-recommended.mjs +2 -0
- package/src/eslint/configs/ts-rules.mjs +6 -2
- package/src/eslint/index.mjs +17 -0
- package/src/eslint/rules/no-direct-signal-mutation.mjs +1 -1
- package/types/dsivd-prestations-ng.d.ts +303 -147
- package/dsivd-prestations-ng-19.2.0-beta.1.tgz +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,11 +8,193 @@
|
|
|
8
8
|
|
|
9
9
|
## [19.2.0] - should be aligned with prestations-be 19.2.x
|
|
10
10
|
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- [eslint/configs/spec-ts-recommended.mjs](projects/prestations-ng/src/eslint/configs/spec-ts-recommended.mjs)
|
|
14
|
+
- New `prestationsNg.configs.specTsRecommended` ESLint config for the Vitest specs, to use in the `extends` of a
|
|
15
|
+
`**/*.spec.ts` block: the `recommended` config of `@vitest/eslint-plugin`, which also counts the assertions of
|
|
16
|
+
the `HttpTestingController` and of the helpers of the specs. It needs `@vitest/eslint-plugin` in the
|
|
17
|
+
devDependencies of your project, only when you use it. See
|
|
18
|
+
[UPGRADING_V19.md](UPGRADING_V19.md#migrate-to-eslint-flat-configuration).
|
|
19
|
+
- [foehn-bo-multi-upload.component.ts](projects/prestations-ng/src/foehn-upload/foehn-bo-multi-upload/foehn-bo-multi-upload.component.ts)
|
|
20
|
+
- New `localUpload` input. When set to `true`, selected files are kept purely local
|
|
21
|
+
(as `DocumentReferenceWithFile`, no `uploadUrl()` call) so they can be cancelled before
|
|
22
|
+
being sent. Files show up immediately in the list with a "pending" indicator (dictionary
|
|
23
|
+
key `foehn-uploader.pending-label`, French default provided) since they're not uploaded
|
|
24
|
+
yet. It is your responsibility to upload/delete them at save time,
|
|
25
|
+
e.g. via the new
|
|
26
|
+
[document-changes.helper.ts](projects/prestations-ng/src/foehn-upload/document-changes.helper.ts)
|
|
27
|
+
(`splitDocumentChanges`) which computes `filesToAdd`/`documentsToDelete` from the model.
|
|
28
|
+
- The default (direct upload) mode is unchanged.
|
|
29
|
+
- New `maxFileSizeInMo` input: a client-side, per-file size limit that doesn't depend on
|
|
30
|
+
the `fileMaxSizeInBytesByFormKey` server configuration (useful for consumers that don't
|
|
31
|
+
expose that config yet). When both are set, the smallest limit applies. A file too big gets
|
|
32
|
+
the usual `413` error, which can be customized with `customErrors` (`{fileName}`,
|
|
33
|
+
`{fileMaxSize}`).
|
|
34
|
+
- New `chooseButtonStyleModifier` input, appended to the choose button's CSS classes
|
|
35
|
+
(e.g. `btn-sm`), mirroring the same input already available on other uploaders.
|
|
36
|
+
|
|
37
|
+
- [foehn-input-address.component.ts](projects/prestations-ng/src/foehn-address/foehn-input-address.component.ts)
|
|
38
|
+
- Added `streetAllowCustomValue` input to control whether the street field allows custom values (defaults to `true`).
|
|
39
|
+
- Added `swissNpaLocalityBaseUrl` input to control the base URL for Swiss NPA localities (defaults to `'api/refinf/locality'`).
|
|
40
|
+
|
|
11
41
|
### Changed
|
|
12
42
|
|
|
13
43
|
Library now runs as strict mode, which may cause some errors to appear in your project after updating.
|
|
14
44
|
Please check the [UPGRADING_V19.md](UPGRADING_V19.md) guide for more information.
|
|
15
45
|
|
|
46
|
+
- [foehn-list-summary.component.ts](projects/prestations-ng/src/foehn-menu-prestation/foehn-list-summary/foehn-list-summary.component.ts)
|
|
47
|
+
- `FoehnListSummaryComponent` is now generic: `FoehnListSummaryComponent<T extends FoehnListItem>`,
|
|
48
|
+
so the model, `getListItemTitle` and `listItemDescriptions` are typed with your own item class
|
|
49
|
+
instead of the base `FoehnListItem`
|
|
50
|
+
- [foehn-list-item-description.ts](projects/prestations-ng/src/foehn-menu-prestation/foehn-list-summary/foehn-list-item-description.ts)
|
|
51
|
+
- 🚨 `FoehnListItemDescription` now takes a type parameter: `FoehnListItemDescription<T>`.
|
|
52
|
+
`getFormattedValue(item: T)` and `hideIfEmpty(item: T)` receive your item type directly, no cast needed.
|
|
53
|
+
Replace `FoehnListItemDescription[]` with `FoehnListItemDescription<MyItem>[]` in your project.
|
|
54
|
+
- [uploader.helper.ts](projects/prestations-ng/src/foehn-upload/uploader.helper.ts)
|
|
55
|
+
- 🚨 `UploaderHelper.addCustomErrorOrDefault()` takes the limits of the uploader in an object (`UploaderErrorLimits`,
|
|
56
|
+
all optional) instead of its last three parameters:
|
|
57
|
+
`addCustomErrorOrDefault(name, errors, errorCode, fileName, customErrors, { overrideAcceptedExtensions, overrideMaxFileNameLength, overrideIllegalCharacters, maxFileSizeInMo })`.
|
|
58
|
+
The custom error 413 returned by the server now also displays the `maxFileSizeInMo` limit of
|
|
59
|
+
`foehn-bo-multi-upload`.
|
|
60
|
+
- 🚀 **prestations-ng is now zoneless**: its components no longer need zone.js to be refreshed. They keep
|
|
61
|
+
working in an application using zone.js, and your application can now drop zone.js, see
|
|
62
|
+
[UPGRADING_V19.md](UPGRADING_V19.md#zoneless-optional-but-recommended).
|
|
63
|
+
- the state displayed by the components is held by signals (`signal()`, `computed()`, `linkedSignal()`)
|
|
64
|
+
- what was waiting for `NgZone.onMicrotaskEmpty` or a `setTimeout()` to reach the rendered DOM (focus,
|
|
65
|
+
scroll, value displayed by the autocompletes, default values...) now uses `afterNextRender()`
|
|
66
|
+
- the short asynchronous work of the components is tracked with `PendingTasks`, so that
|
|
67
|
+
`ApplicationRef.whenStable()` (and `fixture.whenStable()` in your tests) waits for it: the debounced
|
|
68
|
+
`userInput` output of the `foehn-input-*` components, the debounced time of `foehn-input-date-time`,
|
|
69
|
+
the locale of `foehn-date-picker`, the default country and the sanitized model of `foehn-input-address`
|
|
70
|
+
- `providePrestationsNgCore()` now provides `provideBrowserGlobalErrorListeners()`: without zone.js, the
|
|
71
|
+
errors thrown in a timer or a promise still reach your `ErrorHandler`
|
|
72
|
+
- 🚨 **The inputs and outputs of the components are unchanged**, but the following public fields are now
|
|
73
|
+
signals. If your code reads them (e.g. through `viewChild()` or in your tests), call them (`isVisible()`
|
|
74
|
+
instead of `isVisible`) and write them with `.set()`. Those marked _read-only_ are `computed()` signals.
|
|
75
|
+
- `foehn-agenda`: `srAnnouncements`
|
|
76
|
+
- `foehn-agenda-timeslot-panel`: `selectedTimeSlot`, `timeInputValue`, `isClosed`, `timeSlots`
|
|
77
|
+
(_read-only_), `timeslotRemaining` (_read-only_), `canClose` (_read-only_)
|
|
78
|
+
- `foehn-autocomplete`: `searchValue`, `inputHasFocus`, `hideSuggestions`, `displayedElements`,
|
|
79
|
+
`notificationMessageUpdated`, `VIEW_PORT_HEIGHT` (_read-only_)
|
|
80
|
+
- `foehn-multiselect-autocomplete`: `autocompleteInputValue`, `showEmptyListMessage` (_read-only_), `srAnnouncements`
|
|
81
|
+
- `foehn-select`, `foehn-radio`, `foehn-checkbox` and the autocompletes (`FoehnCheckableGroupComponent`):
|
|
82
|
+
`groupedElements`
|
|
83
|
+
- `foehn-radio`: `inputValue`
|
|
84
|
+
- `foehn-confirm-modal`: `isVisible` (`content` is now a read-only field instead of a getter)
|
|
85
|
+
- `foehn-date-picker`: `hostId`, `weekDays`, `weeks`, `selectedDate`, `monthListItem`
|
|
86
|
+
- `foehn-dropdown-menu`: `isDropdownVisible`, `popupPosition`
|
|
87
|
+
- `foehn-header`: `connectedEsMenu` (_read-only_)
|
|
88
|
+
- `foehn-input-address`: `country`, `locality`, `street`, `houseNumber`, `postOfficeBoxText`,
|
|
89
|
+
`addressLine1`, `addressLine2` (linked to the model: they follow it and can still be set),
|
|
90
|
+
`streetCustomErrors`
|
|
91
|
+
- `foehn-input-foreign-locality`: `zipCode`, `longName`
|
|
92
|
+
- `foehn-input-foreign-street`: `longName`
|
|
93
|
+
- `foehn-input-date`: `day`, `month`, `year`, `datePickerModel`
|
|
94
|
+
- `foehn-input-date-time`: `date`, `time` (linked to the model: they follow it and can still be set)
|
|
95
|
+
- `foehn-input-time`: `hour`, `minute` (linked to the model: they follow it and can still be set)
|
|
96
|
+
- `foehn-input-nav13`: `part2`, `part3`, `part4`
|
|
97
|
+
- `foehn-input-textarea`: `isCharCountExceeded`, `charCountLeft`, `charCountLabel`, `ariaLiveStatus`
|
|
98
|
+
- `foehn-list` and `foehn-table`: `currentPage`, `filteredList`
|
|
99
|
+
- `foehn-list-summary`: `listCopyForTable`, `tableConfiguration` (_read-only_)
|
|
100
|
+
- `foehn-notfound`: `seconds`
|
|
101
|
+
- `foehn-page`: `pageTitle`
|
|
102
|
+
- `foehn-page-counter`: `isStable` (was an `Observable<boolean>`)
|
|
103
|
+
- `foehn-page-modal`: `wrongPublicModalVisible`, `formattedPublicsCibles`
|
|
104
|
+
- `foehn-picture-upload`: `pictureToCrop`, `croppedPictureAsBlob`, `loading`
|
|
105
|
+
- `foehn-status-progress-bar`: `isTransmitted`
|
|
106
|
+
- `gesdem-action-recovery-login`: `demandeNotFound`, `rateLimited`, `rateLimitCounter`, `otpRecipient`,
|
|
107
|
+
`otp`
|
|
108
|
+
- `gesdem-action-recovery-registration`: `mailSent`, `model` (replaced by the new `updateModel()` method,
|
|
109
|
+
never mutated)
|
|
110
|
+
- `epayment-form` (`SdkEpaymentComponent`): `failureModalDisplayed`, `redirectModalDisplayed`
|
|
111
|
+
- `captcha` (`SdkRecaptchaComponent`): `isLoaded`, `hasLoadingError`
|
|
112
|
+
- `redirector` (`RedirectComponent`): `counter`
|
|
113
|
+
- The lifecycle hooks that have nothing left to do are removed: `ngOnChanges()` of
|
|
114
|
+
`foehn-agenda-timeslot-panel`, `ngAfterViewInit()` of `foehn-list-summary`, `ngOnInit()` and `ngOnDestroy()`
|
|
115
|
+
of `captcha`.
|
|
116
|
+
- `SdkDictionaryService.getKeySync()` reads a signal: a `computed()` or a template calling it is refreshed
|
|
117
|
+
when the dictionary is (re)loaded, e.g. when the language changes.
|
|
118
|
+
- `foehn-autocomplete` and `foehn-multiselect-autocomplete`: the value to display, its reset, the default
|
|
119
|
+
values and the focus after a click on a suggestion are applied after the next rendering instead of in a
|
|
120
|
+
`setTimeout()`.
|
|
121
|
+
- Unit tests of the library: Karma and Jasmine are replaced by Vitest 4 in browser mode (Playwright,
|
|
122
|
+
headless Chromium), see [CONTRIBUTING.md](CONTRIBUTING.md#unit-testing).
|
|
123
|
+
- `core-js` is no longer a peer dependency: prestations-ng no longer imports it since 19.1.1, see
|
|
124
|
+
[UPGRADING_V19.md](UPGRADING_V19.md#core-js-is-no-longer-needed-since-1920).
|
|
125
|
+
- `ng add @dsivd/prestations-ng` adds `iban` and `dayjs` (the CommonJS dependencies of prestations-ng) to the
|
|
126
|
+
`allowedCommonJsDependencies` of `angular.json`, instead of `core-js/modules/es.array.includes`, `iban` and
|
|
127
|
+
`zone.js/dist/zone-error`, and no longer sets `allowSyntheticDefaultImports` (implied by the `tsconfig.json` of
|
|
128
|
+
Angular 21: `"module": "preserve"` or `"moduleResolution": "bundler"`).
|
|
129
|
+
|
|
130
|
+
### Deprecated
|
|
131
|
+
|
|
132
|
+
- `AbstractMenuPageComponent.ngZone` is no longer used and will be removed: `NgZone.onStable` never emits
|
|
133
|
+
without zone.js.
|
|
134
|
+
|
|
135
|
+
### Fixed
|
|
136
|
+
|
|
137
|
+
- `captcha`: no more `NG0953` warning ("Unexpected emit for destroyed `OutputRef`") when the captcha script fails to
|
|
138
|
+
load after the component has been destroyed (e.g. the user left the page meanwhile).
|
|
139
|
+
- `foehn-multiselect-autocomplete`: the suggestions and the message displayed once every element is selected follow a
|
|
140
|
+
model changed by the project, not only by the user. Once every element was selected, a model set back to some of
|
|
141
|
+
them kept the message and the hidden input; a model selecting every element did not display the message; the
|
|
142
|
+
suggestions still contained the elements selected by the project. An empty list of elements no longer displays
|
|
143
|
+
the message (every element of the list has already been added).
|
|
144
|
+
- `providePrestationsNgCore()` registers again the HTTP interceptors lost with `FoehnPageModule` in 19.0.0: the
|
|
145
|
+
`foehn-transmit-waiting-modal` is displayed again during a long transmission, the warning before the expiration of
|
|
146
|
+
the session receives its date again, and a new version of the application deployed meanwhile is detected again.
|
|
147
|
+
- `foehn-select`, `foehn-radio`, `foehn-checkbox` and the autocompletes:
|
|
148
|
+
- the model is no longer set back to its previous value when the parent changes `elements` and `model`
|
|
149
|
+
at the same time
|
|
150
|
+
- no more error when the component is destroyed while its `elementsUrl` is loading
|
|
151
|
+
- with a `defaultElementValue`, the model can be emptied again (e.g. set to `null` by the parent): the
|
|
152
|
+
default value was set back at once
|
|
153
|
+
- a change of the model or of `disabled` no longer processes the elements again (the default value, the
|
|
154
|
+
display of the model, the grouped elements and, for the autocompletes, the matching of the model)
|
|
155
|
+
- `foehn-autocomplete`:
|
|
156
|
+
- the model is subscribed only once, whatever the number of times the elements are retrieved: the value
|
|
157
|
+
to display was selected again for each retrieval
|
|
158
|
+
- the height of the suggestion list always follows `itemHeightInSuggestionListInPx`, even before the first
|
|
159
|
+
search returning less than 5 suggestions
|
|
160
|
+
- dynamic list (`isSuggestionListDynamic`): a model already set is no longer reset, even for a moment
|
|
161
|
+
(`modelChange` emitted `null`, then the value again), when the loaded elements do not contain it; it is
|
|
162
|
+
displayed once the elements loaded for it contain it, including when they are given at once with the model
|
|
163
|
+
- `isSuggestionListDynamic` can change at runtime: only the matching of the current kind of list is active,
|
|
164
|
+
the matching of the previous kind went on
|
|
165
|
+
- dynamic list: a model changed by the project (another value, `null`) is displayed again; once a value had been
|
|
166
|
+
displayed, the display no longer followed the model. The elements loaded for the search of the user still do
|
|
167
|
+
not replace what they type.
|
|
168
|
+
- `foehn-input-address` without zone.js: an address given by the parent right after the initialization of the
|
|
169
|
+
component (e.g. prefilled with `SessionInfoWithApplicationService.prefillForm()` for a connected user) is no
|
|
170
|
+
longer overwritten by the model with the default country only; the default country and the clean-up of the model
|
|
171
|
+
are applied after the next rendering, from the latest model.
|
|
172
|
+
- `foehn-input-address`: the deferred clean-up of the model (hidden address lines and post office box) applies
|
|
173
|
+
to the latest model, it could restore a previous value (e.g. remove the default country); nothing is
|
|
174
|
+
updated anymore once the component is destroyed.
|
|
175
|
+
- `foehn-multi-upload` and `foehn-bo-multi-upload`:
|
|
176
|
+
- after a failed upload (network, HTTP error), the next files are uploaded: nothing was uploaded anymore,
|
|
177
|
+
without any message, until the page was reloaded
|
|
178
|
+
- after a failed deletion (HTTP error, readonly document), the next files are deleted: nothing was deleted
|
|
179
|
+
anymore, and the error also reached the `ErrorHandler` as unhandled (its message is still displayed)
|
|
180
|
+
- `PendingUploadService.removePendingFile()`: a file which is not pending no longer removes the last pending file.
|
|
181
|
+
- `foehn-multi-upload` and `foehn-bo-multi-upload`: `customErrors` is used again for the errors which have a default
|
|
182
|
+
message (`413`, `415`, `500`, `FILENAME_TOO_LONG`, `FILENAME_SHOULD_NOT_CONTAIN`...): the default messages
|
|
183
|
+
overwrote the given object. Its messages can use `{fileName}` and `{fileMaxSize}`, `{fileExtensions}`,
|
|
184
|
+
`{maxFileNameLength}`, `{fileNameIllegalCharacters}` (as documented) as well as `%1$s` and `%2$s`.
|
|
185
|
+
- `UploaderHelper`: `getDefaultCustomErrors()` and `mergeCustomErrors()` are added, `createDefaultCustomErrors()` is
|
|
186
|
+
deprecated as it overwrites the given custom errors.
|
|
187
|
+
- `foehn-agenda-timeslot-panel`: when the time slots of the day change, the panel is closed again (only the
|
|
188
|
+
first time slots are displayed) unless the selected slot would be hidden; the list was cut while the panel
|
|
189
|
+
stayed open.
|
|
190
|
+
- `foehn-breadcrumb`: a breadcrumb updated in place is displayed again when it is emitted.
|
|
191
|
+
- `fromDictionary` pipe: its subscription is released when the pipe is destroyed.
|
|
192
|
+
- `SdkLogsService`: a log is not sent anymore once the application is destroyed (e.g. at the end of a test),
|
|
193
|
+
when it made the `HttpClient` throw `NG0205`.
|
|
194
|
+
- `ng add @dsivd/prestations-ng` works again on the Angular workspaces created since Angular 14: it failed because
|
|
195
|
+
`angular.json` no longer has a `defaultProject` (the dependencies are now added to every application), then
|
|
196
|
+
because of the comments of `tsconfig.json` (now kept, like its formatting).
|
|
197
|
+
|
|
16
198
|
## [19.1.1]
|
|
17
199
|
|
|
18
200
|
### Fixed
|
package/CONTRIBUTING.md
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
- Node 22.18+
|
|
6
6
|
- npm 10.2+
|
|
7
|
-
- Chrome or Chromium
|
|
8
7
|
- G++
|
|
9
8
|
|
|
9
|
+
Chromium is downloaded by Puppeteer during `npm install`: it is used by the unit tests (through Playwright)
|
|
10
|
+
and by the e2e tests (Cypress).
|
|
11
|
+
|
|
10
12
|
## Developing using the Dev Tool
|
|
11
13
|
|
|
12
14
|
Since version `8`, `prestations-ng` has a standalone application that allows
|
|
@@ -92,9 +94,10 @@ This project uses a set of scripts to lint and format the code, along with pre-c
|
|
|
92
94
|
|
|
93
95
|
Important scripts are :
|
|
94
96
|
|
|
95
|
-
- `npm run lint` : run ESLint and Prettier on all files
|
|
97
|
+
- `npm run lint` : run ESLint and Prettier on all files, and check the types of the TypeScript files compiled by no
|
|
98
|
+
build (`lint:types`: `cypress/`, which Cypress only transpiles, and the configuration files of `tsconfig.config.json`)
|
|
96
99
|
- `npm run format`: run ESLint and Prettier on all files and fix errors automatically
|
|
97
|
-
- `npm run validate`: clean and run all tests
|
|
100
|
+
- `npm run validate`: clean, lint and run all tests
|
|
98
101
|
|
|
99
102
|
**Husky** is used as a pre-commit hook to automatically output linting and formatting errors for staged files before committing.
|
|
100
103
|
In the end, this utilitary calls `npm run lint-staged` which you can also call yourself of course. To have this pre-commit hook,
|
|
@@ -218,6 +221,43 @@ export NODE_OPTIONS=--max_old_space_size=4096
|
|
|
218
221
|
|
|
219
222
|
Use at your own risks.
|
|
220
223
|
|
|
224
|
+
## Unit testing
|
|
225
|
+
|
|
226
|
+
The unit tests run with [Vitest](https://vitest.dev/) through the `@angular/build:unit-test` builder of the
|
|
227
|
+
Angular CLI, in the browser mode of Vitest: [Playwright](https://playwright.dev/) drives the headless Chromium
|
|
228
|
+
downloaded by Puppeteer (another browser can be used by setting the `CHROME_BIN` environment variable).
|
|
229
|
+
Neither the library nor the Dev Tool uses zone.js: the tests are **zoneless** too.
|
|
230
|
+
|
|
231
|
+
| Command | Tests |
|
|
232
|
+
| ------------------------- | ---------------------------------------------------------------- |
|
|
233
|
+
| `npm run test:lib` | library (`projects/prestations-ng/src/**/*.spec.ts`) |
|
|
234
|
+
| `npm run test:devtool` | Dev Tool (`src/**/*.spec.ts`) |
|
|
235
|
+
| `npm run test:schematics` | schematics, in Node.js (`projects/prestations-ng/schematics`) |
|
|
236
|
+
| `npm run test:eslint` | ESLint plugin, in Node.js (`projects/prestations-ng/src/eslint`) |
|
|
237
|
+
| `npm test` | builds the library, then runs all of the above |
|
|
238
|
+
|
|
239
|
+
- `ng test prestations-ng` without `--no-watch` runs the tests in watch mode.
|
|
240
|
+
- Only some specs: `ng test prestations-ng --no-watch --include=foehn-table` (folder or file, relative to
|
|
241
|
+
`projects/prestations-ng/src`), and `--filter="FoehnTableComponent"` to filter by test name.
|
|
242
|
+
- The configuration is in `angular.json` (`test` targets), in the `vitest-base.config.mts` file of each project and in
|
|
243
|
+
the `vitest-base.config.mts` file they share, at the root.
|
|
244
|
+
The coverage of the library is written in `projects/prestations-ng/coverage` (read by SonarQube).
|
|
245
|
+
- The configuration files of the tools (`.mjs`/`.mts`, e.g. `eslint.config.mjs`, `vitest-base.config.*`) are listed in
|
|
246
|
+
`tsconfig.config.json`, referenced by `tsconfig.json`: the IDE checks them with the options of the project instead of
|
|
247
|
+
its default ones. Add any new configuration file to its `files`.
|
|
248
|
+
|
|
249
|
+
### Writing zoneless tests
|
|
250
|
+
|
|
251
|
+
- Use `vi` (`vi.spyOn()`, `vi.fn()`...), the Jest-like API of Vitest. Unlike `spyOn()` in Jasmine,
|
|
252
|
+
`vi.spyOn()` calls the original method: add `.mockReturnValue(...)` or `.mockImplementation(...)` to stub it.
|
|
253
|
+
The spies are restored before each test.
|
|
254
|
+
- `fakeAsync()`, `tick()`, `flush()` and `waitForAsync()` require zone.js: use `async` tests with
|
|
255
|
+
`await fixture.whenStable()`, or `vi.useFakeTimers()` and `await vi.advanceTimersByTimeAsync(ms)`.
|
|
256
|
+
- `fixture.whenStable()` waits for the change detection and the Angular pending tasks (HTTP requests...), not
|
|
257
|
+
for the timers.
|
|
258
|
+
- Without zone.js, `fixture.detectChanges()` only refreshes the views that have been notified. A test (or a test
|
|
259
|
+
host component) changing the state of a component must use signals, as the components do.
|
|
260
|
+
|
|
221
261
|
## E2E testing
|
|
222
262
|
|
|
223
263
|
`Protractor` is no longer included in new Angular projects as of Angular 12.
|
package/GOOD_PRACTICES.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
> Ce guide décrit les bonnes pratiques pour développer une prestation avec `prestations-ng`.
|
|
4
4
|
> Il a été mis à jour pour la version **v19+** : composants **standalone**, **signals** et
|
|
5
|
-
> fonction **`inject`** (plus de modules, plus d'injection par constructeur).
|
|
5
|
+
> fonction **`inject`** (plus de modules, plus d'injection par constructeur). Depuis la **v19.2**,
|
|
6
|
+
> prestations-ng est **zoneless** : les exemples tiennent l'état de vos composants dans des signaux,
|
|
7
|
+
> ce qui fonctionne avec ou sans zone.js (voir [Application zoneless](good-practices#application_zoneless)).
|
|
6
8
|
>
|
|
7
9
|
> 📚 Pour aller plus loin :
|
|
8
10
|
> [ANGULAR_SIGNALS.md](ANGULAR_SIGNALS.md) · [COMPONENTS.md](COMPONENTS.md) · [UPGRADING_V19.md](UPGRADING_V19.md)
|
|
@@ -19,6 +21,10 @@
|
|
|
19
21
|
- [La balise `<fieldset>` et `<legend>`](good-practices#la_balise__code__lt_fieldset_gt___code__et__code__lt_legend_gt___code_)
|
|
20
22
|
- [Liseuse d'écran (afficher / masquer)](good-practices#liseuse_d__cran__afficher___masquer_)
|
|
21
23
|
- [Une page prestations-ng](good-practices#une_page_prestations-ng)
|
|
24
|
+
- [Application zoneless](good-practices#application_zoneless)
|
|
25
|
+
- [Afficher un état asynchrone](good-practices#afficher_un__tat_asynchrone)
|
|
26
|
+
- [Agir sur le DOM après le rendu](good-practices#agir_sur_le_dom_apr_s_le_rendu)
|
|
27
|
+
- [Tester un composant zoneless](good-practices#tester_un_composant_zoneless)
|
|
22
28
|
- [Le routage de vos pages](good-practices#le_routage_de_vos_pages)
|
|
23
29
|
- [Formulaire simple](good-practices#formulaire_simple)
|
|
24
30
|
- [Formulaire complexe (sous-routes)](good-practices#formulaire_complexe__sous-routes_)
|
|
@@ -151,8 +157,7 @@ Exemple :
|
|
|
151
157
|
<foehn-modal
|
|
152
158
|
modalHeaderText="Modal dialog"
|
|
153
159
|
modalSize="modal-lg"
|
|
154
|
-
[isModalVisible]="isModalVisible"
|
|
155
|
-
(isModalVisibleChange)="updateVisibilityStatus($event)"
|
|
160
|
+
[(isModalVisible)]="isModalVisible"
|
|
156
161
|
[modalTriggerHtmlElement]="modalTrigger"
|
|
157
162
|
>
|
|
158
163
|
<!-- on utilise le déclencheur -->
|
|
@@ -160,6 +165,15 @@ Exemple :
|
|
|
160
165
|
</foehn-modal>
|
|
161
166
|
```
|
|
162
167
|
|
|
168
|
+
```ts
|
|
169
|
+
// Un signal : la modale s'affiche et se masque aussi sans zone.js
|
|
170
|
+
readonly isModalVisible = signal(false);
|
|
171
|
+
|
|
172
|
+
openModal(): void {
|
|
173
|
+
this.isModalVisible.set(true);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
163
177
|
### La balise `<label>`
|
|
164
178
|
|
|
165
179
|
> ⚠️ Tous nos composants `foehn-input-*` disposent d'une balise label ([`<label>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/label))
|
|
@@ -383,6 +397,126 @@ export class PageOneComponent
|
|
|
383
397
|
</div>
|
|
384
398
|
```
|
|
385
399
|
|
|
400
|
+
## Application zoneless
|
|
401
|
+
|
|
402
|
+
> ℹ️ Depuis la v19.2, prestations-ng n'a plus besoin de zone.js et nous vous recommandons de rendre
|
|
403
|
+
> votre application zoneless (`provideZonelessChangeDetection()`, voir
|
|
404
|
+
> [UPGRADING_V19.md](UPGRADING_V19.md#zoneless-optional-but-recommended)).
|
|
405
|
+
>
|
|
406
|
+
> Sans zone.js, Angular ne rafraîchit un composant que lorsqu'il est prévenu d'un changement :
|
|
407
|
+
>
|
|
408
|
+
> - un listener de son template a été appelé (`(click)`, `(modelChange)`, `(userInput)`, ...), y
|
|
409
|
+
> compris les double-bindings `[(model)]`
|
|
410
|
+
> - un signal lu par son template a changé (`signal()`, `computed()`, `input()`, `model()`,
|
|
411
|
+
> `toSignal()`, ...)
|
|
412
|
+
> - `ChangeDetectorRef.markForCheck()` a été appelé, ce que fait le pipe `async` lorsqu'un observable
|
|
413
|
+
> émet
|
|
414
|
+
|
|
415
|
+
### Afficher un état asynchrone
|
|
416
|
+
|
|
417
|
+
> ⚠️ Un champ modifié ailleurs (dans un `subscribe()`, un `setTimeout()`, une `Promise`, un
|
|
418
|
+
> callback, ...) n'est plus affiché tant que rien d'autre ne rafraîchit le composant. Tenez-le dans
|
|
419
|
+
> un **signal**.
|
|
420
|
+
|
|
421
|
+
Ce qu'il ne faut **pas** faire :
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
isLoading = true;
|
|
425
|
+
|
|
426
|
+
ngOnInit(): void {
|
|
427
|
+
this.myService.load().subscribe(() => {
|
|
428
|
+
this.isLoading = false; // pas affiché sans zone.js
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Ce qu'il **faut** faire :
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
readonly isLoading = signal(true);
|
|
437
|
+
|
|
438
|
+
ngOnInit(): void {
|
|
439
|
+
this.myService.load().subscribe(() => {
|
|
440
|
+
this.isLoading.set(false);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
```html
|
|
446
|
+
@if (isLoading()) {
|
|
447
|
+
<p>{{ 'page.loading' | fromDictionary }}</p>
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
> 💡 Pour un observable affiché tel quel, `toSignal(this.myService.load())` ou le pipe `async` font
|
|
452
|
+
> le travail pour vous.
|
|
453
|
+
|
|
454
|
+
Dans vos pages (`AbstractPageComponent`), prestations-ng rafraîchit la page lorsqu'il charge `form`
|
|
455
|
+
et `reference`. Si vous modifiez vous-même le formulaire dans un callback asynchrone, appelez
|
|
456
|
+
`markForCheck()` ensuite :
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
private readonly changeDetectorRef = inject(ChangeDetectorRef);
|
|
460
|
+
|
|
461
|
+
ngOnInit(): void {
|
|
462
|
+
super.ngOnInit();
|
|
463
|
+
this.sessionInfoWithApplicationService.prefillForm((info) => {
|
|
464
|
+
this.form.email = info?.email ?? null;
|
|
465
|
+
this.changeDetectorRef.markForCheck();
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Agir sur le DOM après le rendu
|
|
471
|
+
|
|
472
|
+
> ⚠️ `NgZone` (`onStable`, `onMicrotaskEmpty`, `run()`, ...) ne fonctionne plus sans zone.js, et un
|
|
473
|
+
> `setTimeout()` n'attend pas le rendu. Pour agir sur le DOM rendu (focus, scroll, ...), utilisez
|
|
474
|
+
> `afterNextRender()`.
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
readonly detailsVisible = signal(false);
|
|
478
|
+
readonly detailsField = viewChild<ElementRef<HTMLElement>>('detailsField');
|
|
479
|
+
|
|
480
|
+
private readonly injector = inject(Injector);
|
|
481
|
+
|
|
482
|
+
showDetails(): void {
|
|
483
|
+
this.detailsVisible.set(true);
|
|
484
|
+
// Le champ n'existe qu'une fois le détail rendu
|
|
485
|
+
afterNextRender(() => this.detailsField()?.nativeElement.focus(), {
|
|
486
|
+
injector: this.injector,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Tester un composant zoneless
|
|
492
|
+
|
|
493
|
+
> ⚠️ `fixture.detectChanges()` **force** le rafraîchissement : un composant qui oublie de prévenir
|
|
494
|
+
> Angular (champ au lieu d'un signal, `markForCheck()` manquant, ...) passe le test, mais n'est pas
|
|
495
|
+
> rafraîchi dans l'application. Laissez Angular rafraîchir le composant, comme en production, avec
|
|
496
|
+
> `await fixture.whenStable()`.
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
it('should display the details', async () => {
|
|
500
|
+
fixture.componentRef.setInput('showDetails', true);
|
|
501
|
+
await fixture.whenStable();
|
|
502
|
+
|
|
503
|
+
expect(fixture.nativeElement.querySelector('.details')).not.toBeNull();
|
|
504
|
+
});
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
`await fixture.whenStable()` attend aussi les tâches en cours, comme les requêtes HTTP : le test ne
|
|
508
|
+
se termine pas avant elles (sinon `NG0205: Injector has already been destroyed`).
|
|
509
|
+
|
|
510
|
+
Gardez `fixture.detectChanges()` pour :
|
|
511
|
+
|
|
512
|
+
- vérifier un état intermédiaire, avant la fin d'un traitement asynchrone
|
|
513
|
+
- les tests avec `vi.useFakeTimers()` : le rafraîchissement d'Angular passe par des timers, qu'il
|
|
514
|
+
faut avancer (`await vi.advanceTimersByTimeAsync(...)`), sinon `whenStable()` ne se termine pas
|
|
515
|
+
|
|
516
|
+
> 💡 Pour que le test passe par le même chemin que l'utilisateur, déclenchez les événements dans le
|
|
517
|
+
> DOM (`input.dispatchEvent(new Event('change'))`, `button.click()`, ...) plutôt que d'appeler les
|
|
518
|
+
> méthodes du composant ou de ses services.
|
|
519
|
+
|
|
386
520
|
## Le routage de vos pages
|
|
387
521
|
|
|
388
522
|
### Formulaire simple
|
|
@@ -541,14 +675,14 @@ Votre composant doit :
|
|
|
541
675
|
1. `selector` devrait commencer par `"app-..."` pour éviter d'éventuels conflits de nommage
|
|
542
676
|
2. avoir un `providers`
|
|
543
677
|
3. étendre `FoehnInputComponent` et vous devez lui préciser votre modèle de données
|
|
544
|
-
4. répartir les attributs de votre modèle de données dans des
|
|
678
|
+
4. répartir les attributs de votre modèle de données dans des **signaux** publics utilisés dans
|
|
545
679
|
votre HTML
|
|
546
680
|
5. implémenter une fonction `update` (ici ce sera `updateDate`) qui fera appel à `updateNgModel` pour
|
|
547
681
|
maintenir la synchronisation de votre modèle de données avec votre page
|
|
548
682
|
6. implémenter une fonction `handleUserInput` qui fera appel à `handleChange` pour continuer à
|
|
549
683
|
notifier le parent des changements de comportement de votre composant
|
|
550
684
|
7. implémenter une fonction `onModelChange` qui vous permettra d'initialiser correctement les
|
|
551
|
-
|
|
685
|
+
signaux correspondant à votre modèle
|
|
552
686
|
8. implémenter une fonction `getValidValue` afin de reconstruire votre modèle de données avant de le
|
|
553
687
|
retransmettre à votre page et retourner `null` si tous les attributs de votre objet sont vides (ça
|
|
554
688
|
c'est pour que Java soit content)
|
|
@@ -558,11 +692,14 @@ Votre composant doit :
|
|
|
558
692
|
> - **Pas de `multi: true`** dans le `providers` (retiré depuis la migration standalone).
|
|
559
693
|
> - Les `imports` du composant se déclarent **directement** dans le décorateur `@Component`. Chaque
|
|
560
694
|
> composant charge ce dont il a besoin (voir `foehn-input-date` dans ce projet).
|
|
695
|
+
> - L'état affiché par le composant est tenu dans des **signaux** (point 4) : `onModelChange` est
|
|
696
|
+
> appelé lorsque le modèle change, parfois en dehors d'un événement du template, et un simple champ
|
|
697
|
+
> ne serait alors pas affiché sans zone.js.
|
|
561
698
|
|
|
562
699
|
`input-date.component.ts`
|
|
563
700
|
|
|
564
701
|
```ts
|
|
565
|
-
import { Component, forwardRef } from '@angular/core';
|
|
702
|
+
import { Component, forwardRef, signal } from '@angular/core';
|
|
566
703
|
|
|
567
704
|
import { FoehnInputComponent } from '../foehn-input/foehn-input.component';
|
|
568
705
|
import { FoehnInputNumberComponent } from '../foehn-input/foehn-input-number.component';
|
|
@@ -591,10 +728,10 @@ import { SdkDictionaryPipe } from '../sdk-dictionary/sdk-dictionary.pipe';
|
|
|
591
728
|
export class InputDateComponent extends FoehnInputComponent<number[]> {
|
|
592
729
|
// point (3) : extends FoehnInputComponent<number[]>
|
|
593
730
|
|
|
594
|
-
// point (4) : les attributs de votre modèle répartis en
|
|
595
|
-
day
|
|
596
|
-
month
|
|
597
|
-
year
|
|
731
|
+
// point (4) : les attributs de votre modèle répartis en signaux publics
|
|
732
|
+
readonly day = signal<string | null>(null);
|
|
733
|
+
readonly month = signal<string | null>(null);
|
|
734
|
+
readonly year = signal<string | null>(null);
|
|
598
735
|
|
|
599
736
|
// point (5)
|
|
600
737
|
updateDate(): void {
|
|
@@ -613,24 +750,24 @@ export class InputDateComponent extends FoehnInputComponent<number[]> {
|
|
|
613
750
|
}
|
|
614
751
|
|
|
615
752
|
// point (7)
|
|
616
|
-
override onModelChange(value: number[]): void {
|
|
753
|
+
override onModelChange(value: number[] | null): void {
|
|
617
754
|
if (value && value.length > 2) {
|
|
618
|
-
this.year
|
|
619
|
-
this.month
|
|
620
|
-
this.day
|
|
755
|
+
this.year.set(this.toString(value[0]));
|
|
756
|
+
this.month.set(this.toString(value[1]));
|
|
757
|
+
this.day.set(this.toString(value[2]));
|
|
621
758
|
} else if (!value) {
|
|
622
|
-
this.year
|
|
623
|
-
this.month
|
|
624
|
-
this.day
|
|
759
|
+
this.year.set(null);
|
|
760
|
+
this.month.set(null);
|
|
761
|
+
this.day.set(null);
|
|
625
762
|
}
|
|
626
763
|
}
|
|
627
764
|
|
|
628
765
|
// point (8)
|
|
629
766
|
getValidValue(): number[] | null | undefined {
|
|
630
767
|
if (
|
|
631
|
-
this.isEmpty(this.day) &&
|
|
632
|
-
this.isEmpty(this.month) &&
|
|
633
|
-
this.isEmpty(this.year)
|
|
768
|
+
this.isEmpty(this.day()) &&
|
|
769
|
+
this.isEmpty(this.month()) &&
|
|
770
|
+
this.isEmpty(this.year())
|
|
634
771
|
) {
|
|
635
772
|
if (this.model_ !== undefined) {
|
|
636
773
|
return null;
|
|
@@ -640,9 +777,9 @@ export class InputDateComponent extends FoehnInputComponent<number[]> {
|
|
|
640
777
|
}
|
|
641
778
|
|
|
642
779
|
return [
|
|
643
|
-
this.toNumber(this.year),
|
|
644
|
-
this.toNumber(this.month),
|
|
645
|
-
this.toNumber(this.day),
|
|
780
|
+
this.toNumber(this.year()),
|
|
781
|
+
this.toNumber(this.month()),
|
|
782
|
+
this.toNumber(this.day()),
|
|
646
783
|
];
|
|
647
784
|
}
|
|
648
785
|
|
|
@@ -655,7 +792,8 @@ Chaque `foehn-input-*` utilisé dans votre vue doit :
|
|
|
655
792
|
1. avoir un `id` qui concatène l'appel à la fonction `buildId()` et le nom de l'attribut de votre objet
|
|
656
793
|
2. avoir un `name` qui concatène l'input **signal** `name()` et le nom de l'attribut de votre objet
|
|
657
794
|
3. avoir un `label`
|
|
658
|
-
4. utiliser votre
|
|
795
|
+
4. utiliser votre signal public comme model en double-binding (banana in the box) : `[(model)]="day"`
|
|
796
|
+
lit et écrit le signal (sans les parenthèses)
|
|
659
797
|
5. faire appel à `updateDate()` lors du déclenchement de l'événement `(modelChange)`
|
|
660
798
|
6. faire appel à `handleUserInput()` lors du déclenchement de l'événement `(userInput)`
|
|
661
799
|
|
|
@@ -955,11 +1093,11 @@ devra lui aussi remonter l'événement.
|
|
|
955
1093
|
}
|
|
956
1094
|
|
|
957
1095
|
<!-- (1) votre boucle @for + track (identifiant unique) -->
|
|
958
|
-
@for (vehicule of vehicules; track vehicule.id; let i = $index) {
|
|
1096
|
+
@for (vehicule of vehicules(); track vehicule.id; let i = $index) {
|
|
959
1097
|
<!-- (2) votre composant réutilisable -->
|
|
960
1098
|
<app-vehicule-item
|
|
961
1099
|
[name]="name() + '[' + i + ']'"
|
|
962
|
-
[
|
|
1100
|
+
[model]="vehicule"
|
|
963
1101
|
(modelChange)="updateVehiculesList($event, i)"
|
|
964
1102
|
(userInput)="handleUserInput($event, i)"
|
|
965
1103
|
(removedVehicule)="handleRemovedVehicule(i)"
|
|
@@ -974,6 +1112,9 @@ devra lui aussi remonter l'événement.
|
|
|
974
1112
|
|
|
975
1113
|
> 💡 Le `name` est un input signal : `[name]="name() + '[' + i + ']'"` (et non plus
|
|
976
1114
|
> `[name]="name + '[' + i + ']'"`).
|
|
1115
|
+
>
|
|
1116
|
+
> 💡 La variable d'une boucle `@for` ne peut pas être modifiée : on passe l'élément avec `[model]` et
|
|
1117
|
+
> on remplace l'élément de la liste dans `updateVehiculesList()` (appelée par `(modelChange)`).
|
|
977
1118
|
|
|
978
1119
|
`vehicules-list.component.ts` remonte à son tour l'événement (1) au parent (`AbstractPageComponent`)
|
|
979
1120
|
ainsi que supprimer l'élément de la liste (2). C'est aussi ici que vous pouvez gérer l'ajout d'un
|
|
@@ -998,15 +1139,29 @@ ainsi que supprimer l'élément de la liste (2). C'est aussi ici que vous pouvez
|
|
|
998
1139
|
export class VehiculesListComponent extends FoehnInputComponent<Vehicule[]> {
|
|
999
1140
|
readonly removedVehicule = output<void>(); // (1) événement à remonter au parent
|
|
1000
1141
|
|
|
1001
|
-
|
|
1142
|
+
// Signal contenant vos éléments répétables : il est remplacé, jamais modifié sur place
|
|
1143
|
+
readonly vehicules = signal<Vehicule[]>([]);
|
|
1144
|
+
|
|
1145
|
+
updateVehiculesList(vehicule: Vehicule, index: number): void {
|
|
1146
|
+
this.vehicules.update((vehicules) =>
|
|
1147
|
+
vehicules.map((item, i) => (i === index ? vehicule : item)),
|
|
1148
|
+
);
|
|
1149
|
+
this.getValidValueAndUpdateNgModel(); // Mise à jour de votre modèle
|
|
1150
|
+
}
|
|
1002
1151
|
|
|
1003
1152
|
handleRemovedVehicule(index: number): void {
|
|
1004
1153
|
// (2) votre fonction de suppression de l'élément ainsi que la transmission de l'évènement
|
|
1005
|
-
this.vehicules.
|
|
1154
|
+
this.vehicules.update((vehicules) =>
|
|
1155
|
+
vehicules.filter((_vehicule, i) => i !== index),
|
|
1156
|
+
); // suppression de l'élément
|
|
1006
1157
|
this.getValidValueAndUpdateNgModel(); // Mise à jour de votre modèle
|
|
1007
1158
|
this.removedVehicule.emit(); // (1) on remonte l'évènement
|
|
1008
1159
|
}
|
|
1009
1160
|
|
|
1161
|
+
override onModelChange(value: Vehicule[] | null): void {
|
|
1162
|
+
this.vehicules.set(value ?? []);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1010
1165
|
addVehicule(): void {
|
|
1011
1166
|
// (3) Gestion de l'ajout d'un élément
|
|
1012
1167
|
// ...code
|
|
@@ -1217,7 +1372,9 @@ export class ... extends AbstractPageComponent<YOUR_MODEL> implements OnInit {
|
|
|
1217
1372
|
save(): void {
|
|
1218
1373
|
// Activer l'affichage des erreurs
|
|
1219
1374
|
this._validationHandlerService.shouldDisplayErrors(true);
|
|
1220
|
-
// Appel au back pour faire votre validation
|
|
1375
|
+
// Appel au back pour faire votre validation. Le callback ne modifie aucun champ de la page :
|
|
1376
|
+
// les services de prestations-ng (growl, erreurs) rafraîchissent eux-mêmes l'affichage, aussi
|
|
1377
|
+
// sans zone.js. Un état propre à la page modifié ici devrait être un signal.
|
|
1221
1378
|
this.httpClient
|
|
1222
1379
|
.post<PostResponse>('api_url_ici', votre_objet)
|
|
1223
1380
|
.subscribe((response: PostResponse) => {
|