@dsivd/prestations-ng 19.2.0-beta.3 → 19.2.0-beta.5

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/UPGRADING_V19.md CHANGED
@@ -1449,6 +1449,172 @@ npm run test
1449
1449
  npm start
1450
1450
  ```
1451
1451
 
1452
+ ### `foehn-list-summary` is now generic (since 19.2.0)
1453
+
1454
+ `FoehnListSummaryComponent` is now `FoehnListSummaryComponent<T extends FoehnListItem>` and `FoehnListItemDescription` now takes a type parameter: `FoehnListItemDescription<T>`.
1455
+ Using `FoehnListItemDescription` without its type parameter no longer compiles.
1456
+
1457
+ Replace every `FoehnListItemDescription` with `FoehnListItemDescription<MyItem>`, where `MyItem` is your class extending `FoehnListItem`. You can then drop the casts (or `any`) in `getFormattedValue` and `hideIfEmpty`:
1458
+
1459
+ Before:
1460
+
1461
+ ```typescript
1462
+ getListItemsDescription(): FoehnListItemDescription[] {
1463
+ return [
1464
+ {
1465
+ label: 'Name',
1466
+ getFormattedValue: (item: FoehnListItem) =>
1467
+ (item as MyItem).name ?? '',
1468
+ },
1469
+ ];
1470
+ }
1471
+ ```
1472
+
1473
+ After:
1474
+
1475
+ ```typescript
1476
+ getListItemsDescription(): FoehnListItemDescription<MyItem>[] {
1477
+ return [
1478
+ {
1479
+ label: 'Name',
1480
+ getFormattedValue: (item: MyItem) => item.name ?? '',
1481
+ },
1482
+ ];
1483
+ }
1484
+ ```
1485
+
1486
+ The same applies to the function passed to `getListItemTitle`, which now receives a `MyItem`.
1487
+
1488
+ ### `core-js` is no longer needed (since 19.2.0)
1489
+
1490
+ prestations-ng no longer imports `core-js` and no longer has it as a peer dependency. If your project does not
1491
+ import `core-js` itself, uninstall it:
1492
+
1493
+ ```bash
1494
+ npm uninstall core-js
1495
+ ```
1496
+
1497
+ Then, in your `angular.json` file, remove `"core-js/modules/es.array.includes"` from `"allowedCommonJsDependencies"`.
1498
+ The CommonJS dependencies of prestations-ng are now `"iban"` and `"dayjs"` (which also covers `dayjs/plugin/*` and
1499
+ `dayjs/locale/*`):
1500
+
1501
+ ```json
1502
+ "allowedCommonJsDependencies": ["iban", "dayjs"]
1503
+ ```
1504
+
1505
+ ### Zoneless (optional but recommended)
1506
+
1507
+ Since v19.2, prestations-ng does not need zone.js anymore: the state of its components is held by signals, which
1508
+ tell Angular when a component has to be refreshed. prestations-ng keeps working in an application using zone.js,
1509
+ so this step is optional, but a zoneless application is lighter, faster and easier to debug. See the
1510
+ [Angular zoneless guide](https://angular.dev/guide/zoneless).
1511
+
1512
+ In any case, if your code reads some fields of the prestations-ng components (e.g. through `viewChild()` or in
1513
+ your tests), check the fields that are now signals in the [CHANGELOG](CHANGELOG.md#1920---should-be-aligned-with-prestations-be-192x).
1514
+
1515
+ #### Enable the zoneless change detection
1516
+
1517
+ Zoneless is the default since Angular 21: `ng update` added `provideZoneChangeDetection()` to keep zone.js in your
1518
+ application. Replace it in your `app.config.ts` (`providePrestationsNgCore()` already provides
1519
+ `provideBrowserGlobalErrorListeners()`, which reports to your `ErrorHandler` the errors zone.js used to catch):
1520
+
1521
+ ```diff
1522
+ export const appConfig: ApplicationConfig = {
1523
+ providers: [
1524
+ - provideZoneChangeDetection({ eventCoalescing: true }),
1525
+ + provideZonelessChangeDetection(),
1526
+ providePrestationsNgCore(),
1527
+ ...
1528
+ ],
1529
+ };
1530
+ ```
1531
+
1532
+ Remove `zone.js` and `zone.js/testing` from the `polyfills` of the `build` and `test` targets in your
1533
+ `angular.json` (and from your `polyfills.ts` file if you still have one): without them, the `TestBed` is zoneless
1534
+ too.
1535
+
1536
+ ```diff
1537
+ "polyfills": [
1538
+ - "zone.js"
1539
+ ],
1540
+ ```
1541
+
1542
+ then uninstall zone.js:
1543
+
1544
+ ```bash
1545
+ npm uninstall zone.js
1546
+ ```
1547
+
1548
+ #### Check your components
1549
+
1550
+ Without zone.js, Angular refreshes a component only when it is notified that something changed:
1551
+
1552
+ - a listener of its template has been called (`(click)`, `(modelChange)`, `(userInput)`...), including the
1553
+ `[(model)]` two-way bindings
1554
+ - a signal read by its template has changed (`signal()`, `computed()`, `input()`, `model()`, `toSignal()`...)
1555
+ - `ChangeDetectorRef.markForCheck()` has been called, which is what the `async` pipe does when an observable emits
1556
+
1557
+ A field modified elsewhere (in a `subscribe()`, a `setTimeout()`, a `Promise`, a callback...) is not displayed
1558
+ anymore until something else refreshes the component. Hold it in a signal (see
1559
+ [INTRODUCTION_ANGULAR_SIGNALS.md](INTRODUCTION_ANGULAR_SIGNALS.md)):
1560
+
1561
+ ```diff
1562
+ - isLoading = true;
1563
+ + readonly isLoading = signal(true);
1564
+
1565
+ ngOnInit(): void {
1566
+ this.myService.load().subscribe(() => {
1567
+ - this.isLoading = false;
1568
+ + this.isLoading.set(false);
1569
+ });
1570
+ }
1571
+ ```
1572
+
1573
+ ```diff
1574
+ - @if (isLoading) {
1575
+ + @if (isLoading()) {
1576
+ ```
1577
+
1578
+ Pay attention to:
1579
+
1580
+ - your pages (`AbstractPageComponent` and its subclasses): prestations-ng refreshes them when it sets `form` and
1581
+ `reference`. But if you modify the form yourself in an asynchronous callback, e.g. the one given to
1582
+ `SessionInfoWithApplicationService.prefillForm()` or a `subscribe()` of your own service, call
1583
+ `markForCheck()` afterwards:
1584
+
1585
+ ```ts
1586
+ private readonly changeDetectorRef = inject(ChangeDetectorRef);
1587
+
1588
+ ngOnInit(): void {
1589
+ super.ngOnInit();
1590
+ this.sessionInfoWithApplicationService.prefillForm((info) => {
1591
+ this.form.email = info?.email ?? null;
1592
+ this.changeDetectorRef.markForCheck();
1593
+ });
1594
+ }
1595
+ ```
1596
+
1597
+ - `NgZone`: `onStable`, `onMicrotaskEmpty` and `onUnstable` never emit without zone.js, and `run()` or
1598
+ `runOutsideAngular()` are useless. To act on the rendered DOM (focus, scroll...), replace them, as well as the
1599
+ `setTimeout()` waiting for the view, by `afterNextRender()`. `AbstractMenuPageComponent.ngZone` is deprecated.
1600
+
1601
+ ```diff
1602
+ - this.ngZone.onStable.pipe(first()).subscribe(() => this.focusFirstField());
1603
+ + afterNextRender(() => this.focusFirstField(), { injector: this.injector });
1604
+ ```
1605
+
1606
+ #### Check your tests
1607
+
1608
+ - `fakeAsync()`, `tick()`, `flush()` and `waitForAsync()` need zone.js: write `async` tests, with
1609
+ `vi.useFakeTimers()` and `await vi.advanceTimersByTimeAsync(ms)` to control the timers (see
1610
+ [Migrate to vitest](#migrate-to-vitest)).
1611
+ - `fixture.detectChanges()` only refreshes the views which have been notified, then checks that nothing changed
1612
+ meanwhile: a field modified without notification makes the test fail with the error `NG0100`
1613
+ (`ExpressionChangedAfterItHasBeenCheckedError`). Fix the component (signal, `markForCheck()`) rather than the
1614
+ test.
1615
+ - `await fixture.whenStable()` waits for the change detection and for the pending tasks of the application
1616
+ (e.g. the debounced `userInput` of the prestations-ng inputs), not for the timers.
1617
+
1452
1618
  ### Last but not least, check if your application is working!!!
1453
1619
 
1454
1620
  #### Be sure your application gets a fresh start
@@ -8,6 +8,8 @@ import templateRules from './template-rules.mjs';
8
8
  *
9
9
  * Meant for the `extends` of a `**\/*.html` block. It brings its own parser and plugins,
10
10
  * so do not spread `angular.configs.templateRecommended` alongside it.
11
+ *
12
+ * @returns {import('typescript-eslint').ConfigArray}
11
13
  */
12
14
  const templateRecommended = (plugin) => [
13
15
  ...angular.configs.templateRecommended,
@@ -1,7 +1,11 @@
1
1
  import templateBase from './template-base.mjs';
2
2
 
3
- // Only the rules written by this library. `templateRecommended` includes it, alongside
4
- // the house style; use this layer directly to opt out of the latter.
3
+ /**
4
+ * Only the rules written by this library. `templateRecommended` includes it, alongside
5
+ * the house style; use this layer directly to opt out of the latter.
6
+ *
7
+ * @returns {import('typescript-eslint').ConfigArray}
8
+ */
5
9
  const templateRules = (plugin) => [
6
10
  templateBase(plugin),
7
11
  {
@@ -14,6 +14,8 @@ import tsRules from './ts-rules.mjs';
14
14
  * Meant for the `extends` of a `**\/*.ts` block. It brings its own parser and plugins,
15
15
  * so do not spread `angular.configs.tsRecommended` alongside it — declaring the same
16
16
  * plugin twice from two different copies makes ESLint fail outright.
17
+ *
18
+ * @returns {import('typescript-eslint').ConfigArray}
17
19
  */
18
20
  const tsRecommended = (plugin) => [
19
21
  eslint.configs.recommended,
@@ -1,7 +1,11 @@
1
1
  import tsBase from './ts-base.mjs';
2
2
 
3
- // Only the rules written by this library. `tsRecommended` includes it, alongside the
4
- // house style; use this layer directly to opt out of the latter.
3
+ /**
4
+ * Only the rules written by this library. `tsRecommended` includes it, alongside the
5
+ * house style; use this layer directly to opt out of the latter.
6
+ *
7
+ * @returns {import('typescript-eslint').ConfigArray}
8
+ */
5
9
  const tsRules = (plugin) => [
6
10
  tsBase(plugin),
7
11
  {