@dsivd/prestations-ng 19.0.0-beta.4 → 19.0.0-beta.6

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
@@ -172,3 +172,1223 @@ mvn -U org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.activeRecipes=or
172
172
  ```
173
173
 
174
174
  Take a look at Sonar to see other things that you can improve or ignore.
175
+
176
+ ## PRESTATIONS-NG
177
+
178
+ For the commands in this guide, it is supposed that your are in the `front` folder of your project.
179
+
180
+ The main feature in `prestations-ng` v19 is the update of the `@angular/*` peer dependencies
181
+ to `^21.2.14`, using `node 22+` to build, `npm 10+` for package manager and `Foehn 2+ (Bootstrap 5+)` for our design system.
182
+
183
+ A lot of breaking because our library has migrated to **standalone** components, **signals** and **inject function** instead of constructor injection parameters.
184
+
185
+ > 🚨 **Read carefully following [PITFALLS](ANGULAR_SIGNALS.md#common_pitfalls), especially points 4, 6 & 8**
186
+
187
+ ### Migrate to ESLint
188
+
189
+ (NB : you can skip this step if you already have a `front/.eslintrc.json` file, it means it is already done.)
190
+
191
+ Follow [our migration guide](ESLINT_MIGRATION_GUIDE.md) to migrate your project from TSLint to ESLint.
192
+
193
+ ### Upgrade to Angular 20
194
+
195
+ Just FYI if you are curious about the details, you can find the Angular update guide at this address [https://angular.dev/update-guide?v=19.0-20.0&l=1](https://angular.dev/update-guide?v=19.0-20.0&l=1)
196
+
197
+ **NB: Commit before continuing so your repository is clean.**
198
+
199
+ Remove your `resolutions` in `package.json` before starting : if you have `resolutions` in `package.json`, remove manually the section.
200
+
201
+ In the next commands, commit before each ng update.
202
+
203
+ Unlink `@dsivd/prestations-ng` by running `yarn unlink @dsivd/prestations-ng` in your `front` folder if you have linked it before.
204
+
205
+ ```bash
206
+ npm install
207
+ npm install @angular/cli@19 -g
208
+ ng update @angular/core@19 @angular/cli@19
209
+ ng update @dsivd/prestations-ng@18
210
+ ```
211
+
212
+ ```bash
213
+ npm install @angular/cli@20 -g
214
+ ng update @angular/core@20 @angular/cli@20
215
+ ```
216
+
217
+ When asked the question "Select the migrations that you'd like to run",
218
+
219
+ accept **"use-application-builder"** by hitting "Space" key then "Enter".
220
+
221
+ When asked the question "Select the migrations that you'd like to run",
222
+
223
+ accept **"control-flow-migration"** by hitting "Space" key.
224
+
225
+ accept **"router-current-navigation"** by hitting "Space" key.
226
+
227
+ Then press "Enter" key.
228
+
229
+ **IMPORTANT: Search for every `@for` in your project and check that `track` statement is present and has a unique identifier**
230
+
231
+ **NB: When using `@angular/cdk` in your project**
232
+
233
+ ```bash
234
+ ng update @angular/cdk@20
235
+ ```
236
+
237
+ ### Running with node 22+
238
+
239
+ #### Switch to node 22+
240
+
241
+ ##### If you are using our `slave-apps`
242
+
243
+ Be sure to update your `slave-apps` git repository and source your `.bashrc` (or whatever file you're using).
244
+
245
+ Verify your node version in your terminal by executing:
246
+
247
+ ```bash
248
+ node -v
249
+ ```
250
+
251
+ **NB: If version is still v20+ by default, type `node22` in your terminal to switch to node v22+**
252
+
253
+ ```bash
254
+ node22
255
+ ```
256
+
257
+ ##### If you are NOT using our `slave-apps`
258
+
259
+ Be sure to use a node version 22+ in your terminal:
260
+
261
+ ```bash
262
+ node -v
263
+ ```
264
+
265
+ #### In your project
266
+
267
+ Update your `package.json` file to specify the node engine:
268
+
269
+ ```json
270
+ {
271
+ "engines": {
272
+ "node": ">=22.18.0"
273
+ }
274
+ }
275
+ ```
276
+
277
+ #### Update your jenkins files
278
+
279
+ - Change `nodeVersion: 20` to use node 22
280
+
281
+ ##### UT folder
282
+
283
+ ```diff
284
+ buildAppPipelinePodmanUT([
285
+ - node: 20,
286
+ + node: 22,
287
+ ])
288
+ ```
289
+
290
+ ### Upgrade to Angular 21
291
+
292
+ `prestations-ng` **v19+** now supports **Angular v21**.
293
+
294
+ Just FYI if you are curious about the details, you can find the Angular update guide at this address [https://angular.dev/update-guide?v=20.0-21.0&l=1](https://angular.dev/update-guide?v=20.0-21.0&l=1)
295
+
296
+ **NB: Commit before continuing so your repository is clean.**
297
+
298
+ ```bash
299
+ npm install @angular/cli@21 -g
300
+ ng update @dsivd/prestations-ng@latest
301
+ ```
302
+
303
+ When asked again the question "Select the migrations that you'd like to run",
304
+
305
+ accept **"router-current-navigation"** by hitting "Space" key then "Enter".
306
+
307
+ ```bash
308
+ ng update @angular/cli@21
309
+ ```
310
+
311
+ When asked the question "Select the migrations that you'd like to run",
312
+
313
+ accept **"use-application-builder"** by hitting "Space" key then "Enter".
314
+
315
+ ```bash
316
+ npm install @types/node@22 ts-node@10
317
+ ```
318
+
319
+ **NB: When using `@angular/cdk` in your project**
320
+
321
+ ```bash
322
+ ng update @angular/cdk@21
323
+ ```
324
+
325
+ **NB: When using `ngx-matomo-client` in your project**
326
+
327
+ ```bash
328
+ ng update ngx-matomo-client@9
329
+ ```
330
+
331
+ ### Quick fix
332
+
333
+ In all custom components extending `FoehnInputComponent`, in `providers` providing `FoehnInputComponent` remove `multi: true,`
334
+
335
+ ```diff
336
+ {
337
+ provide: FoehnInputComponent,
338
+ useExisting: forwardRef(() => ItemListComponent),
339
+ - multi: true,
340
+ }
341
+ ```
342
+
343
+ In your `.eslintrc.json`, remove `"/src/main.ts",` from `ignorePatterns`
344
+
345
+ In `main.ts`, replace `platformBrowserDynamic` by `platformBrowser`
346
+
347
+ ### Now fix your project before continuing
348
+
349
+ **IMPORTANT: Fix your project because our new prestations-ng v19+ is standalone, uses signals instead of observables and inject function instead of constructor injection parameter.**
350
+
351
+ Remove all your `@dsivd/prestations-ng` imports of modules and make sure you are using the new standalone components instead by adding them directly from your html file "alt+enter".
352
+
353
+ Check every `constructor` in your project, and remove useless `constructor` parameters that are not used anymore (like `ActivatedRoute` in components extending `AbstractPageComponent`).
354
+
355
+ Run the following command until you have no more errors.
356
+
357
+ ```bash
358
+ npm run build
359
+ ```
360
+
361
+ **TIP: If your IntelliJ is still showing you errors on `@if` or `@for`, close it and re-open it to fix.**
362
+
363
+ ### Migrate to inject function
364
+
365
+ ```bash
366
+ ng generate @angular/core:inject
367
+ ```
368
+
369
+ ✔ Which directory do you want to migrate? **./**
370
+
371
+ ✔ Do you want to migrate abstract classes? Abstract classes are not migrated by default, because their parameters aren't guaranteed to be injectable. **Yes**
372
+
373
+ ✔ Do you want to clean up all constructors or keep them backwards compatible? Enabling this option will include an additional signature of `constructor(...args:
374
+ unknown[]);` that will avoid errors for sub-classes, but will increase the amount of generated code by the migration. **No**
375
+
376
+ ✔ Do you want optional inject calls to be non-nullable? Enable this option if you want the return type to be identical to @Optional(), at the expense of worse
377
+ type safety. **Yes**
378
+
379
+ Go through every file that has changed and fix what is broken.
380
+
381
+ Reorder public / private and remove unused imports so ESLINT is happy again.
382
+
383
+ For example, when extending `AbstractPageComponent`:
384
+ Before:
385
+
386
+ ```ts
387
+ sessionInfoData: SessionInfoWithApplicationService;
388
+
389
+ constructor(
390
+ protected override activatedRoute: ActivatedRoute,
391
+ private readonly dictionaryService: SdkDictionaryService,
392
+ sessionInfoData: SessionInfoWithApplicationService
393
+ ) {
394
+ super(activatedRoute);
395
+ this.sessionInfoData = sessionInfoData;
396
+ }
397
+ ```
398
+
399
+ After:
400
+
401
+ ```ts
402
+ sessionInfoData = inject(SessionInfoWithApplicationService);
403
+
404
+ private readonly dictionaryService = inject(SdkDictionaryService);
405
+ ```
406
+
407
+ Most of the time, you can remove `activatedRoute` because it was used as a constructor parameter for `AbstractPageComponent`.
408
+
409
+ ### Migrate to self-closing tags
410
+
411
+ **WARNING: The following command will work only when your application compiles without errors. If you have errors, fix them before continuing.**
412
+
413
+ ```bash
414
+ npm run build
415
+ ```
416
+
417
+ Fix remaining errors and warnings, then run the following command:
418
+
419
+ ```bash
420
+ ng generate @angular/core:self-closing-tag
421
+ ```
422
+
423
+ ✔ Which directory do you want to migrate? **./**
424
+
425
+ ### Migrate to standalone
426
+
427
+ **WARNING: The following command will work only when your application compiles without errors. If you have errors, fix them before continuing.**
428
+
429
+ ```bash
430
+ npm run build
431
+ ```
432
+
433
+ Fix remaining errors and warnings, then run the following command:
434
+
435
+ ```bash
436
+ ng generate @angular/core:standalone
437
+ ```
438
+
439
+ Choose **"Convert all components, directives and pipes to standalone"** then
440
+
441
+ ✔ Which directory do you want to migrate? **./**
442
+
443
+ ```bash
444
+ npm run build
445
+ ```
446
+
447
+ ```bash
448
+ ng generate @angular/core:standalone
449
+ ```
450
+
451
+ Choose **"Remove unnecessary NgModule classes"** then
452
+
453
+ ✔ Which directory do you want to migrate? **./**
454
+
455
+ ```bash
456
+ npm run build
457
+ ```
458
+
459
+ ```bash
460
+ ng generate @angular/core:standalone
461
+ ```
462
+
463
+ Choose **"Bootstrap the application using standalone APIs"** then
464
+
465
+ ✔ Which directory do you want to migrate? **./**
466
+
467
+ #### Now check and fix manually the migration of your `app.component.ts` to standalone
468
+
469
+ It should look like that:
470
+
471
+ ```diff
472
+ @Component({
473
+ selector: 'app-root',
474
+ templateUrl: './app.component.html',
475
+ - // eslint-disable-next-line @angular-eslint/prefer-standalone
476
+ - standalone: false
477
+ + imports: [
478
+ + RouterOutlet,
479
+ + NgHttpLoaderComponent,
480
+ + RedirectComponent,
481
+ + ]
482
+ })
483
+ ```
484
+
485
+ Create a file under `src/app` called `app.routes.ts` with the following content:
486
+
487
+ ```ts
488
+ export const routes: Routes = [
489
+ // Your routes found in app-routing.module.ts go here
490
+ ];
491
+ ```
492
+
493
+ Create a file under `src/app` called `app.config.ts` with the following content:
494
+
495
+ ```ts
496
+ export const appConfig: ApplicationConfig = {
497
+ providers: [
498
+ provideZoneChangeDetection(),
499
+ providePrestationsNgCore(),
500
+ provideRouter(routes),
501
+ META_SERVICE_PROVIDER,
502
+ provideHttpClient(
503
+ withInterceptors([pendingRequestsInterceptor$]),
504
+ withXsrfConfiguration({
505
+ cookieName: 'XSRF-TOKEN',
506
+ headerName: 'X-XSRF-TOKEN',
507
+ }),
508
+ ),
509
+ ],
510
+ };
511
+ ```
512
+
513
+ Now replace the content of `main.ts` with the following content:
514
+
515
+ ```ts
516
+ import { bootstrapApplication } from '@angular/platform-browser';
517
+
518
+ import { AppComponent } from './app/app.component';
519
+ import { appConfig } from './app/app.config';
520
+
521
+ bootstrapApplication(AppComponent, appConfig).catch((err) =>
522
+ console.error(err),
523
+ );
524
+ ```
525
+
526
+ Clean up your project by removing `app-routing.module.ts` and `app.module.ts` files if it has not already been done.
527
+
528
+ #### Migration to lazy-loaded routes when working with large applications
529
+
530
+ You may have some other modules that are maybe lazy-loaded. Just be sure that all your components are standalone before continuing.
531
+
532
+ > You can run the following command to convert eagerly loaded component routes to lazy loaded routes. This allows the build process to split the production bundle into smaller chunks, to avoid a big JS bundle that includes all routes, which negatively affects initial page load of an application.
533
+
534
+ > This migration will also collect information about all the components declared in NgModules and output the list of routes that use them (including corresponding location of the file). Consider making those components standalone.
535
+
536
+ ```bash
537
+ ng generate @angular/core:route-lazy-loading
538
+ ```
539
+
540
+ Here is a example when doing it manually because schematic above does not work as intended:
541
+
542
+ Your `app.routing.ts` file should look something like this:
543
+
544
+ ```ts
545
+ const routes: Routes = [
546
+ {
547
+ path: '',
548
+ component: PageWrapperComponent,
549
+ data: { root: true },
550
+ children: [
551
+ {
552
+ path: 'demandes',
553
+ loadChildren: () =>
554
+ import('./modules/demandes/demandes.module').then(
555
+ (m) => m.DemandesModule,
556
+ ),
557
+ },
558
+ { path: '404', component: FoehnNotfoundComponent },
559
+ { path: '', redirectTo: 'demandes', pathMatch: 'full' },
560
+ { path: '**', redirectTo: '/404', pathMatch: 'full' },
561
+ ],
562
+ },
563
+ ];
564
+ ```
565
+
566
+ Now, navigate to `DemandesModule`, at the same file level as `demandes.module.ts`, you create a `demandes.routes.ts` file with the following content:
567
+
568
+ ```ts
569
+ export const demandes_routes: Routes = [
570
+ // Your routes found in demandes.module.ts go here
571
+ ];
572
+ ```
573
+
574
+ Then replace `loadChildren: () => import('./modules/demandes/demandes.module').then(m => m.DemandesModule)` by `loadChildren: () => import('./modules/demandes/demandes.routes').then(m => m.demandes_routes)`.
575
+
576
+ Now you can safely delete `demandes.module.ts` as all components are `standalone` now.
577
+
578
+ ---
579
+
580
+ You can `lazy-load` components as well, for example:
581
+
582
+ ```diff
583
+ export const demandes_routes: Routes = [
584
+ - { path: 'list', component: DemandeListPageComponent },
585
+ + { path: 'list', loadComponent: () => import('./demande-list-page/demande-list-page.component').then(m => m.DemandeListPageComponent) },
586
+ ];
587
+ ```
588
+
589
+ ### Migrate from NgClass to class bindings
590
+
591
+ **WARNING: The following command will work only when your application compiles without errors. If you have errors, fix them before continuing.**
592
+
593
+ ```bash
594
+ npm run build
595
+ ```
596
+
597
+ Fix remaining errors and warnings, then run the following command:
598
+
599
+ ```bash
600
+ ng generate @angular/core:ngclass-to-class
601
+ ```
602
+
603
+ ✔ Which directory do you want to migrate? **./**
604
+
605
+ ✔ Should the migration also migrate space-separated keys? **Yes**
606
+
607
+ Check every file that has changed and fix what is broken.
608
+
609
+ Check remaining `ngClass` usage in your project and replace it with class bindings.
610
+
611
+ **Be aware that `[class]` replaces all element classes. When you have static classes, concatenate them with dynamic ones.**
612
+
613
+ Replacement example:
614
+
615
+ Before:
616
+
617
+ ```html
618
+ [ngClass]="isLabelSrOnly ? 'visually-hidden' : 'vd-p'"
619
+ ```
620
+
621
+ After:
622
+
623
+ ```html
624
+ [class.visually-hidden]="isLabelSrOnly()" [class.vd-p]="!isLabelSrOnly()"
625
+ ```
626
+
627
+ ---
628
+
629
+ Before:
630
+
631
+ ```html
632
+ class="modal" [ngClass]="modalSize"
633
+ ```
634
+
635
+ After:
636
+
637
+ ```html
638
+ [class]="'modal ' + modalSize()"
639
+ ```
640
+
641
+ ---
642
+
643
+ Before:
644
+
645
+ ```html
646
+ class="form-label" [ngClass]="isLabelSrOnly() ? 'visually-hidden' :
647
+ labelStyleModifier()"
648
+ ```
649
+
650
+ After:
651
+
652
+ ```html
653
+ [class]="'form-label ' + (isLabelSrOnly() ? 'visually-hidden' :
654
+ (labelStyleModifier() ?? ''))"
655
+ ```
656
+
657
+ ### Migrate from NgStyle to style bindings
658
+
659
+ **WARNING: The following command will work only when your application compiles without errors. If you have errors, fix them before continuing.**
660
+
661
+ ```bash
662
+ npm run build
663
+ ```
664
+
665
+ Fix remaining errors and warnings, then run the following command:
666
+
667
+ ```bash
668
+ ng generate @angular/core:ngstyle-to-style
669
+ ```
670
+
671
+ ✔ Which directory do you want to migrate? **./**
672
+
673
+ ✔ Should the migration also migrate object references? **No**
674
+
675
+ Check every file that has changed and fix what is broken.
676
+
677
+ Check remaining `ngStyle` usage in your project and replace it with style bindings.
678
+
679
+ ### Convert CommonModule usage to standalone imports
680
+
681
+ **WARNING: The following command will work only when your application compiles without errors. If you have errors, fix them before continuing.**
682
+
683
+ ```bash
684
+ npm run build
685
+ ```
686
+
687
+ Fix remaining errors and warnings, then run the following command:
688
+
689
+ ```bash
690
+ ng generate @angular/core:common-to-standalone
691
+ ```
692
+
693
+ ✔ Which directory do you want to migrate? **./**
694
+
695
+ Check every file that has changed and fix what is broken.
696
+
697
+ ### Migrate @HostListener
698
+
699
+ Search for `@HostListener` in your project and replace it with the `host` object in the `@Component` or `@Directive` decorator.
700
+
701
+ For example, when using `@HostListener` in a directive:
702
+
703
+ Before:
704
+
705
+ ```ts
706
+ @Directive({
707
+ selector: '[numberCurrencyFormatter]',
708
+ })
709
+ export class NumberCurrencyFormatterDirective implements OnInit {
710
+ @HostListener('blur', ['$event.target.value'])
711
+ onBlur(value: string): void {
712
+ this.hasFocus = false;
713
+ this.setFormatting(value);
714
+ }
715
+ }
716
+ ```
717
+
718
+ After:
719
+
720
+ ```ts
721
+ @Directive({
722
+ selector: '[numberCurrencyFormatter]',
723
+ host: {
724
+ '(blur)': 'onBlur($event)',
725
+ },
726
+ })
727
+ export class NumberCurrencyFormatterDirective implements OnInit {
728
+ onBlur(event: FocusEvent): void {
729
+ this.hasFocus = false;
730
+ const value = (event.target as HTMLInputElement).value;
731
+ this.setFormatting(value);
732
+ }
733
+ }
734
+ ```
735
+
736
+ ### Migrate to signal queries
737
+
738
+ **WARNING: The following command will work only when your application compiles without errors. If you have errors, fix them before continuing.**
739
+
740
+ ```bash
741
+ npm run build
742
+ ```
743
+
744
+ Fix remaining errors and warnings, then run the following command:
745
+
746
+ ```bash
747
+ ng generate @angular/core:signal-queries-migration --insert-todos
748
+ ```
749
+
750
+ ✔ Which directory do you want to migrate? **./**
751
+
752
+ ✔ Do you want to migrate as much as possible, even if it may break your build? **No**
753
+
754
+ Check every file that has changed and fix what is broken.
755
+
756
+ Check every remaining `@ViewChild`, `@ViewChildren`, `@ContentChild`, and `@ContentChildren` usage in your project and replace them with signal queries.
757
+
758
+ Check every **TODO** comment added by the migration and fix them.
759
+
760
+ ### Migrate to output functions
761
+
762
+ **WARNING: The following command will work only when your application compiles without errors. If you have errors, fix them before continuing.**
763
+
764
+ ```bash
765
+ npm run build
766
+ ```
767
+
768
+ Fix remaining errors and warnings, then run the following command:
769
+
770
+ ```bash
771
+ ng generate @angular/core:output-migration
772
+ ```
773
+
774
+ ✔ Which directory do you want to migrate? **./**
775
+
776
+ Check every remaining `@Output` usage in your project and replace them with output function.
777
+
778
+ When needing to subscribe to an output, you can use `outputToObservable` from `@angular/core/rxjs-interop`.
779
+
780
+ ### Migrate to input signals
781
+
782
+ Check our [Angular signals guide](ANGULAR_SIGNALS.md) for more information about signals and input signals.
783
+
784
+ **WARNING: The following command will work only when your application compiles without errors. If you have errors, fix them before continuing.**
785
+
786
+ ```bash
787
+ npm run build
788
+ ```
789
+
790
+ Fix remaining errors and warnings, then run the following command:
791
+
792
+ ```bash
793
+ ng generate @angular/core:signal-input-migration --insert-todos
794
+ ```
795
+
796
+ ✔ Which directory do you want to migrate? **./**
797
+
798
+ ✔ Do you want to migrate as much as possible, even if it may break your build? **No**
799
+
800
+ Check every file that has changed and fix what is broken.
801
+
802
+ Check every remaining `@Input` usage in your project and replace them with input signals.
803
+
804
+ Check every **TODO** comment added by the migration and fix them.
805
+
806
+ ### Migrate to vitest
807
+
808
+ [Angular migration guide: migrating to Vitest](https://angular.dev/guide/testing/migrating-to-vitest)
809
+
810
+ ```bash
811
+ npm install --save-dev vitest jsdom
812
+ ```
813
+
814
+ In your `angular.json` file, remove `"zone.js/dist/zone-error"` from `"allowedCommonJsDependencies"`
815
+
816
+ then find the `test` target for your project and change the `builder` to `@angular/build:unit-test` and add following options:
817
+
818
+ ```json
819
+ {
820
+ "projects": {
821
+ "your-project-name": {
822
+ "architect": {
823
+ "test": {
824
+ "builder": "@angular/build:unit-test",
825
+ "options": {
826
+ "buildTarget": "your-project-name:build"
827
+ }
828
+ }
829
+ }
830
+ }
831
+ }
832
+ }
833
+ ```
834
+
835
+ In your package.json file, add the following scripts:
836
+
837
+ ```diff
838
+ {
839
+ "scripts": {
840
+ - "test": "ng test",
841
+ + "test": "ng test --watch=false",
842
+ }
843
+ }
844
+ ```
845
+
846
+ In your `tsconfig.spec.json` file, remove from `types` `jasmine` and `node` and add `vitest/globals`:
847
+
848
+ ```diff
849
+ {
850
+ "compilerOptions": {
851
+ "types": [
852
+ - "jasmine",
853
+ - "node"
854
+ + "vitest/globals"
855
+ ]
856
+ }
857
+ }
858
+ ```
859
+
860
+ In your `.gitignore` file, add the following line to ignore the `screenshot` folder:
861
+
862
+ ```
863
+ __screenshots__
864
+ ```
865
+
866
+ Now remove karma and jasmine dependencies
867
+
868
+ ```bash
869
+ npm uninstall jasmine jasmine-core jasmine-spec-reporter
870
+ npm uninstall karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter
871
+ npm uninstall puppeteer
872
+ npm uninstall @types/jasmine @types/jasminewd2
873
+ rm karma.conf.js
874
+ ```
875
+
876
+ Now search for `karma.conf.js` in your project and remove all references to it.
877
+ Now search for `protractor.conf.js` in your project and remove them if they still exist.
878
+
879
+ Then run following command to migrate from jasmine to vitest:
880
+
881
+ ```bash
882
+ ng g @schematics/angular:refactor-jasmine-vitest
883
+ ```
884
+
885
+ Now you can run your tests with `npm test` and check that everything is up and running.
886
+
887
+ In your tests, replace `waitForAsync(() => ...)` by `async(...)` and add `await` before `TestBed.configureTestingModule`.
888
+ For example, replace this:
889
+
890
+ Before:
891
+
892
+ ```ts
893
+ import {
894
+ provideHttpClient,
895
+ withInterceptorsFromDi,
896
+ } from '@angular/common/http';
897
+ import { provideHttpClientTesting } from '@angular/common/http/testing';
898
+ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
899
+ import { RouterModule } from '@angular/router';
900
+
901
+ import { AppComponent } from './app.component';
902
+ import { AppModule } from './app.module';
903
+
904
+ describe('AppComponent', () => {
905
+ let component: AppComponent;
906
+ let fixture: ComponentFixture<AppComponent>;
907
+
908
+ beforeEach(waitForAsync(() => {
909
+ TestBed.configureTestingModule({
910
+ imports: [AppModule, RouterModule.forRoot([])],
911
+ providers: [
912
+ provideHttpClient(withInterceptorsFromDi()),
913
+ provideHttpClientTesting(),
914
+ ],
915
+ }).compileComponents();
916
+ }));
917
+
918
+ beforeEach(() => {
919
+ fixture = TestBed.createComponent(AppComponent);
920
+ component = fixture.componentInstance;
921
+ fixture.detectChanges();
922
+ });
923
+
924
+ it('should create', () => {
925
+ expect(component).toBeTruthy();
926
+ });
927
+ });
928
+ ```
929
+
930
+ After:
931
+
932
+ ```ts
933
+ import {
934
+ provideHttpClient,
935
+ withInterceptorsFromDi,
936
+ } from '@angular/common/http';
937
+ import { provideHttpClientTesting } from '@angular/common/http/testing';
938
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
939
+ import { provideRouter } from '@angular/router';
940
+
941
+ import { AppComponent } from './app.component';
942
+
943
+ describe('AppComponent', () => {
944
+ let component: AppComponent;
945
+ let fixture: ComponentFixture<AppComponent>;
946
+
947
+ beforeEach(async () => {
948
+ await TestBed.configureTestingModule({
949
+ imports: [AppComponent],
950
+ providers: [
951
+ provideHttpClient(withInterceptorsFromDi()),
952
+ provideHttpClientTesting(),
953
+ provideRouter([]),
954
+ ],
955
+ }).compileComponents();
956
+
957
+ fixture = TestBed.createComponent(AppComponent);
958
+ component = fixture.componentInstance;
959
+ await fixture.whenStable();
960
+ });
961
+
962
+ it('should create', () => {
963
+ expect(component).toBeTruthy();
964
+ });
965
+ });
966
+ ```
967
+
968
+ ### Migrate to eslint flat configuration
969
+
970
+ ```bash
971
+ npm uninstall @angular-eslint/builder @angular-eslint/eslint-plugin @angular-eslint/eslint-plugin-template @angular-eslint/template-parser
972
+ npm uninstall @typescript-eslint/eslint-plugin @typescript-eslint/parser @typescript-eslint/types @typescript-eslint/utils
973
+ npm uninstall eslint-import-resolver-typescript eslint-plugin-cypress eslint-plugin-import eslint-plugin-jasmine eslint-plugin-rxjs eslint-plugin-rxjs-angular eslint-plugin-simple-npm eslint-plugin-simple-import-sort
974
+ npm uninstall eslint
975
+
976
+ npm install --save-dev eslint@^9 angular-eslint@21 typescript-eslint@^8
977
+ npm install --save-dev @typescript-eslint/parser@8 @smarttools/eslint-plugin-rxjs@1
978
+ npm install --save-dev eslint-plugin-simple-import-sort@13
979
+ npm install --save-dev eslint-config-prettier@10
980
+ npm install --save-dev eslint-plugin-import-x@4
981
+ npm install --save-dev prettier
982
+ ```
983
+
984
+ In your `front` folder, add a `.prettierrc` file with the following content:
985
+
986
+ ```json
987
+ {
988
+ "printWidth": 100,
989
+ "singleQuote": true,
990
+ "arrowParens": "always",
991
+ "trailingComma": "all",
992
+ "htmlWhitespaceSensitivity": "css",
993
+ "overrides": [
994
+ {
995
+ "files": ["*.html"],
996
+ "options": {
997
+ "parser": "angular"
998
+ }
999
+ }
1000
+ ]
1001
+ }
1002
+ ```
1003
+
1004
+ Add a `.prettierignore` file with the following content:
1005
+
1006
+ ```text
1007
+ node_modules
1008
+ .git
1009
+ target
1010
+ package.json
1011
+ package-lock.json
1012
+ dist
1013
+ coverage
1014
+ .angular
1015
+ ```
1016
+
1017
+ Remove your old `.eslintrc.json` file in your `front` folder:
1018
+
1019
+ ```bash
1020
+ rm .eslintrc.json
1021
+ ```
1022
+
1023
+ Now search for `.eslintrc.json` in your project and remove them.
1024
+
1025
+ Then add a new `eslint.config.mjs` file with the following content:
1026
+
1027
+ ```ts
1028
+ // @ts-check
1029
+ import eslint from '@eslint/js';
1030
+ import tseslint from 'typescript-eslint';
1031
+ import angular from 'angular-eslint';
1032
+ import rxjs from '@smarttools/eslint-plugin-rxjs';
1033
+ import simpleImportSort from 'eslint-plugin-simple-import-sort';
1034
+ import prettier from 'eslint-config-prettier';
1035
+ import importX from 'eslint-plugin-import-x';
1036
+
1037
+ export default tseslint.config(
1038
+ {
1039
+ ignores: ['**/coverage/**', '**/dist/**'],
1040
+ },
1041
+
1042
+ // ─── TypeScript ────────────────────────────────────────────────────────────
1043
+ {
1044
+ files: ['**/*.ts'],
1045
+ extends: [
1046
+ eslint.configs.recommended,
1047
+ ...tseslint.configs.recommended, // covers: no-explicit-any, ban-ts-comment,
1048
+ // no-unused-expressions, no-var, no-unused-vars…
1049
+ ...tseslint.configs.stylistic, // covers: array-type, consistent-type-assertions,
1050
+ // dot-notation, no-inferrable-types (default options),
1051
+ // no-empty-function (error), prefer-for-of,
1052
+ // prefer-function-type…
1053
+ ...angular.configs.tsRecommended, // covers: prefer-inject, no-empty-lifecycle-method,
1054
+ // use-lifecycle-interface…
1055
+ ],
1056
+ languageOptions: {
1057
+ parserOptions: {
1058
+ projectService: true,
1059
+ tsconfigRootDir: import.meta.dirname,
1060
+ },
1061
+ },
1062
+ plugins: {
1063
+ rxjs,
1064
+ 'simple-import-sort': simpleImportSort,
1065
+ 'import-x': importX,
1066
+ },
1067
+ processor: angular.processInlineTemplates,
1068
+ rules: {
1069
+ // ── @angular-eslint ──────────────────────────────────────────────
1070
+ '@angular-eslint/component-selector': [
1071
+ 'error',
1072
+ {
1073
+ type: 'element',
1074
+ prefix: 'app',
1075
+ style: 'kebab-case',
1076
+ },
1077
+ ],
1078
+ '@angular-eslint/directive-selector': [
1079
+ 'error',
1080
+ {
1081
+ type: 'attribute',
1082
+ prefix: 'app',
1083
+ style: 'camelCase',
1084
+ },
1085
+ ],
1086
+
1087
+ // ── @typescript-eslint ───────────────────────────────────────────
1088
+ '@typescript-eslint/explicit-function-return-type': [
1089
+ 'error',
1090
+ {
1091
+ allowExpressions: true,
1092
+ allowTypedFunctionExpressions: true,
1093
+ allowHigherOrderFunctions: true,
1094
+ allowDirectConstAssertionInArrowFunctions: true,
1095
+ },
1096
+ ],
1097
+ '@typescript-eslint/explicit-member-accessibility': [
1098
+ 'error',
1099
+ { accessibility: 'no-public' },
1100
+ ],
1101
+ '@typescript-eslint/member-delimiter-style': 'error',
1102
+ '@typescript-eslint/member-ordering': 'error',
1103
+ '@typescript-eslint/no-base-to-string': 'warn',
1104
+ // stylistic enables no-inferrable-types with default options (ignoreParameters: false);
1105
+ // override here to allow typed parameters explicitly
1106
+ '@typescript-eslint/no-inferrable-types': [
1107
+ 'error',
1108
+ { ignoreParameters: true },
1109
+ ],
1110
+ // stylistic sets no-empty-function to error; downgrade to warn
1111
+ '@typescript-eslint/no-empty-function': 'warn',
1112
+ '@typescript-eslint/prefer-includes': 'warn',
1113
+ '@typescript-eslint/return-await': ['error', 'never'],
1114
+ '@typescript-eslint/typedef': ['error', { parameter: true }],
1115
+ '@typescript-eslint/unified-signatures': 'error',
1116
+ // off here — @typescript-eslint/no-shadow handles it correctly for TS
1117
+ 'no-shadow': 'off',
1118
+ '@typescript-eslint/no-shadow': ['error', { hoist: 'all' }],
1119
+ '@typescript-eslint/no-unused-vars': [
1120
+ 'error',
1121
+ {
1122
+ varsIgnorePattern: '^_',
1123
+ argsIgnorePattern: '^_',
1124
+ caughtErrorsIgnorePattern: '^_',
1125
+ },
1126
+ ],
1127
+
1128
+ // ── eslint core ──────────────────────────────────────────────────
1129
+ 'arrow-body-style': 'error',
1130
+ 'arrow-parens': ['error', 'as-needed'],
1131
+ curly: 'error',
1132
+ eqeqeq: ['error', 'always'],
1133
+ 'guard-for-in': 'error',
1134
+ 'no-bitwise': 'error',
1135
+ 'no-caller': 'error',
1136
+ 'no-console': ['error', { allow: ['log', 'warn', 'error'] }],
1137
+ 'no-eval': 'error',
1138
+ 'no-extra-boolean-cast': 'off',
1139
+ 'no-multiple-empty-lines': 'error',
1140
+ 'no-new-wrappers': 'error',
1141
+ 'no-restricted-imports': [
1142
+ 'error',
1143
+ { paths: ['rxjs/Rx', 'primeng/primeng', 'primeng'] },
1144
+ ],
1145
+ 'no-return-assign': 'error',
1146
+ 'no-throw-literal': 'error',
1147
+ 'no-undef-init': 'error',
1148
+ 'no-useless-concat': 'error',
1149
+ 'object-shorthand': ['error', 'always', { avoidQuotes: true }],
1150
+ 'one-var': ['error', 'never'],
1151
+ 'prefer-arrow-callback': 'error',
1152
+ 'prefer-template': 'error',
1153
+ radix: 'error',
1154
+
1155
+ // ── rxjs ─────────────────────────────────────────────────────────
1156
+ 'rxjs/no-implicit-any-catch': ['error', { allowExplicitAny: true }],
1157
+ 'rxjs/no-sharereplay': 'off',
1158
+
1159
+ // ── import-x ─────────────────────────────────────────────────────
1160
+ 'import-x/no-cycle': [
1161
+ 'warn',
1162
+ { maxDepth: 3, ignoreExternal: true },
1163
+ ],
1164
+ 'import-x/no-deprecated': 'warn',
1165
+ 'import-x/first': 'error',
1166
+ 'import-x/newline-after-import': 'error',
1167
+ 'import-x/no-duplicates': 'error',
1168
+
1169
+ // ── simple-import-sort ───────────────────────────────────────────
1170
+ 'simple-import-sort/imports': 'error',
1171
+ 'simple-import-sort/exports': 'error',
1172
+ },
1173
+ },
1174
+
1175
+ // ─── HTML templates ────────────────────────────────────────────────────────
1176
+ {
1177
+ files: ['**/*.html'],
1178
+ extends: [
1179
+ ...angular.configs.templateRecommended,
1180
+ ...angular.configs.templateAccessibility,
1181
+ ],
1182
+ rules: {
1183
+ '@angular-eslint/template/no-negated-async': 'off',
1184
+ '@angular-eslint/template/button-has-type': 'error',
1185
+ },
1186
+ },
1187
+
1188
+ // ─── Prettier (must be last — disables formatting rules) ───────────────────
1189
+ prettier,
1190
+ );
1191
+ ```
1192
+
1193
+ #### Delete `typing.d.ts`
1194
+
1195
+ In `front/src/`, remove the file `typing.d.ts` if it is still there.
1196
+
1197
+ #### Update your tsconfig\*.json files
1198
+
1199
+ - Replace your `tsconfig.json` content with:
1200
+
1201
+ ```json
1202
+ {
1203
+ "compileOnSave": false,
1204
+ "compilerOptions": {
1205
+ "outDir": "./dist/out-tsc",
1206
+ "noImplicitOverride": true,
1207
+ "noImplicitReturns": true,
1208
+ "noFallthroughCasesInSwitch": true,
1209
+ "skipLibCheck": true,
1210
+ "esModuleInterop": true,
1211
+ "sourceMap": true,
1212
+ "declaration": false,
1213
+ "experimentalDecorators": true,
1214
+ "useDefineForClassFields": false,
1215
+ "moduleResolution": "bundler",
1216
+ "importHelpers": true,
1217
+ "isolatedModules": true,
1218
+ "target": "ES2022",
1219
+ "module": "ES2022",
1220
+ "paths": {
1221
+ "@dsivd/prestations-ng": ["./node_modules/@dsivd/prestations-ng"]
1222
+ }
1223
+ },
1224
+ "angularCompilerOptions": {
1225
+ "enableI18nLegacyMessageIdFormat": false,
1226
+ "strictInjectionParameters": true,
1227
+ "strictInputAccessModifiers": true,
1228
+ "strictTemplates": true,
1229
+ "strictStandalone": true
1230
+ },
1231
+ "files": [],
1232
+ "references": [
1233
+ {
1234
+ "path": "./tsconfig.app.json"
1235
+ },
1236
+ {
1237
+ "path": "./tsconfig.spec.json"
1238
+ }
1239
+ ]
1240
+ }
1241
+ ```
1242
+
1243
+ - Replace your `tsconfig.app.json` content with:
1244
+
1245
+ ```json
1246
+ {
1247
+ "extends": "./tsconfig.json",
1248
+ "compilerOptions": {
1249
+ "outDir": "../out-tsc/app",
1250
+ "types": []
1251
+ },
1252
+ "include": ["src/**/*.ts"],
1253
+ "exclude": ["src/**/*.spec.ts"]
1254
+ }
1255
+ ```
1256
+
1257
+ - Update your package.json, add the following script to run eslint:
1258
+
1259
+ ```diff
1260
+ "scripts": {
1261
+ - "lint": "ng lint",
1262
+ + "lint": "ng lint --fix=true",
1263
+ + "format:prettier": "prettier --config ./.prettierrc --write \"{,!(.scannerwork|target|coverage|node_modules|.angular)/**/}*{.ts,.js,.jsx,.json,.css,.scss,.md,.html}\"",
1264
+ }
1265
+ ```
1266
+
1267
+ ### Husky pre-commit hook
1268
+
1269
+ ```bash
1270
+ npm install --save-dev husky lint-staged
1271
+ npx husky init
1272
+ echo "cd front" > .husky/pre-commit
1273
+ echo "npx lint-staged" >> .husky/pre-commit
1274
+ chmod +x .husky/pre-commit
1275
+ mv ./.husky ../.husky
1276
+ git add -A
1277
+ ```
1278
+
1279
+ - You can manually run formating commands as well:
1280
+
1281
+ ```bash
1282
+ npm run format:prettier
1283
+ npm run lint
1284
+ ```
1285
+
1286
+ Fix your `meta.service.ts` but replacing:
1287
+
1288
+ Before:
1289
+
1290
+ ```ts
1291
+ export const metaServiceFactory =
1292
+ (metaService: MetaService): Function =>
1293
+ () =>
1294
+ lastValueFrom(metaService.load());
1295
+
1296
+ export const META_SERVICE_PROVIDER = provideAppInitializer(() => {
1297
+ const initializerFn = metaServiceFactory(inject(MetaService));
1298
+ return initializerFn();
1299
+ });
1300
+ ```
1301
+
1302
+ After:
1303
+
1304
+ ```ts
1305
+ export const META_SERVICE_PROVIDER = provideAppInitializer(() => {
1306
+ const metaService = inject(MetaService);
1307
+ return lastValueFrom(metaService.load());
1308
+ });
1309
+ ```
1310
+
1311
+ Run again until no more errors:
1312
+
1313
+ ```bash
1314
+ npm run lint
1315
+ ```
1316
+
1317
+ Then:
1318
+
1319
+ ```bash
1320
+ npm run format:prettier
1321
+ ```
1322
+
1323
+ ### Browsers list update
1324
+
1325
+ Run update schematic:
1326
+
1327
+ ```bash
1328
+ ng g @dsivd/prestations-ng:replace-browsers-list
1329
+ ```
1330
+
1331
+ ### Remove deprecated packages
1332
+
1333
+ ```bash
1334
+ npm uninstall @angular/platform-browser-dynamic
1335
+ npm uninstall @angular/animations
1336
+ ```
1337
+
1338
+ ### Configure IntelliJ IDEA to use ESLint and Prettier on save
1339
+
1340
+ - To configure ESLint automatically in the current project, open "_File -> Settings..._" (`Ctrl+Alt+S`) and either:
1341
+ - Search for `ESLint` and check radio box "_Automatic ESLint configuration_" and check checkbox "_Run eslint --fix on save_"
1342
+ - Search for `Prettier` and check radio box "_Automatic Prettier configuration_" and check checkbox "_Run on save_"
1343
+
1344
+ ### Language deprecations
1345
+
1346
+ When using `IntelliJ`, you can run code inspection on your project to fix deprecations manually. Two ways:
1347
+
1348
+ - IntelliJ window menu, go to: `Code > Analyze code > Run inspection by name...` (or **Ctrl+Alt+Maj+i**) and type `Deprecated symbol used`.
1349
+ - Or select your project in your project panel and `right click` on it then `Analyze > Run inspection by name...` (or **Ctrl+Alt+Maj+i**) and type `Deprecated symbol used`.
1350
+
1351
+ ### Last but not least, check if your application is working!!!
1352
+
1353
+ #### Be sure your application gets a fresh start
1354
+
1355
+ ```bash
1356
+ rm -rf .angular/
1357
+ rm -rf node_modules/
1358
+ npm install
1359
+ ```
1360
+
1361
+ #### Build and launch your application
1362
+
1363
+ **NB: You will most probably need to fix some compilation errors such as adding `override` when needed**
1364
+
1365
+ ```bash
1366
+ npm run build
1367
+ npm start
1368
+ ```
1369
+
1370
+ You're good if no errors are remaining!
1371
+ Finally check if your tests run well :
1372
+
1373
+ ```bash
1374
+ npm run test
1375
+ ```
1376
+
1377
+ > 🚨 **Read carefully following [PITFALLS](ANGULAR_SIGNALS.md#common_pitfalls), especially points 4, 6 & 8 and check again your application**
1378
+
1379
+ ### Release your application using `jenkins-libs`
1380
+
1381
+ When your code has been merged/rebased onto `master`, you need to update your application in `jenkins-libs` to use node 22 instead of node 20.
1382
+
1383
+ See [getImtPortailApplications](https://dsigit.etat-de-vaud.ch/outils/git/projects/PEE/repos/jenkins-libs/browse/vars/getImtPortailApplications.groovy)
1384
+
1385
+ ```diff
1386
+ [
1387
+ 'project': 'cybersdk',
1388
+ 'name' : 'skeleton',
1389
+ 'gitUrl' : 'ssh://git@bitbucket.etat-de-vaud.ch/cybsdk/skeleton.git',
1390
+ 'jdk' : '21',
1391
+ - 'node' : '20'
1392
+ + 'node' : '22'
1393
+ ],
1394
+ ```