@dsivd/prestations-ng 19.2.0-beta.12 → 19.2.0-beta.14

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/GOOD_PRACTICES.md CHANGED
@@ -1,103 +1,105 @@
1
- # Utilisation technique (DEV)
1
+ # Technical usage (DEV)
2
2
 
3
- > Ce guide décrit les bonnes pratiques pour développer une prestation avec `prestations-ng`.
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). 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)).
3
+ > This guide describes the good practices to develop a prestation with `prestations-ng`.
4
+ > It has been updated for version **v19+**: **standalone** components, **signals** and the
5
+ > **`inject`** function (no more modules, no more constructor injection). Since **v19.2**,
6
+ > prestations-ng is **zoneless**: the examples hold the state of your components in signals,
7
+ > which works with or without zone.js (see [Zoneless application](good-practices#zoneless_application)).
8
8
  >
9
- > 📚 Pour aller plus loin :
9
+ > 📚 To go further:
10
10
  > [ANGULAR_SIGNALS.md](ANGULAR_SIGNALS.md) · [COMPONENTS.md](COMPONENTS.md) · [UPGRADING_V19.md](UPGRADING_V19.md)
11
11
 
12
- ## Sommaire
12
+ ## Table of contents
13
13
 
14
14
  - [Documentation](good-practices#documentation)
15
- - [Mise à jour de la librairie](good-practices#mise___jour_de_la_librairie)
16
- - [Accessibilité](good-practices#accessibilit_)
17
- - [Structure HTML](good-practices#structure_html)
18
- - [Le focus](good-practices#le_focus)
19
- - [La balise `<label>`](good-practices#la_balise__code__lt_label_gt___code_)
20
- - [La balise `<ul>` ou `<ol>`](good-practices#la_balise__code__lt_ul_gt___code__ou__code__lt_ol_gt___code_)
21
- - [La balise `<fieldset>` et `<legend>`](good-practices#la_balise__code__lt_fieldset_gt___code__et__code__lt_legend_gt___code_)
22
- - [Liseuse d'écran (afficher / masquer)](good-practices#liseuse_d__cran__afficher___masquer_)
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)
28
- - [Le routage de vos pages](good-practices#le_routage_de_vos_pages)
29
- - [Formulaire simple](good-practices#formulaire_simple)
30
- - [Formulaire complexe (sous-routes)](good-practices#formulaire_complexe__sous-routes_)
31
- - [Création d'un composant réutilisable](good-practices#cr_ation_d_un_composant_r_utilisable)
32
- - [Exemple avec foehn-input-date de prestations-ng](good-practices#exemple_avec_foehn-input-date_de_prestations-ng)
33
- - [Afficher la validation globale du composant](good-practices#afficher_la_validation_globale_du_composant)
34
- - [Éviter des problèmes d'affichage d'erreurs dans une boucle `@for`](good-practices#_viter_des_probl_mes_d_affichage_d_erreurs_dans_une_boucle__code__for__code_)
35
- - [Validation du formulaire](good-practices#validation_du_formulaire)
36
- - [Gestion standard](good-practices#gestion_standard)
37
- - [Gestion manuelle](good-practices#gestion_manuelle)
15
+ - [Updating the library](good-practices#updating_the_library)
16
+ - [Accessibility](good-practices#accessibility)
17
+ - [HTML structure](good-practices#html_structure)
18
+ - [Focus](good-practices#focus)
19
+ - [The `<label>` tag](good-practices#the__code__lt_label_gt___code__tag)
20
+ - [The `<ul>` or `<ol>` tag](good-practices#the__code__lt_ul_gt___code__or__code__lt_ol_gt___code__tag)
21
+ - [The `<fieldset>` and `<legend>` tags](good-practices#the__code__lt_fieldset_gt___code__and__code__lt_legend_gt___code__tags)
22
+ - [Screen reader (show / hide)](good-practices#screen_reader__show___hide_)
23
+ - [A prestations-ng page](good-practices#a_prestations-ng_page)
24
+ - [Zoneless application](good-practices#zoneless_application)
25
+ - [Displaying an asynchronous state](good-practices#displaying_an_asynchronous_state)
26
+ - [Deriving a state from view queries](good-practices#deriving_a_state_from_view_queries)
27
+ - [Acting on the DOM after rendering](good-practices#acting_on_the_dom_after_rendering)
28
+ - [Testing a zoneless component](good-practices#testing_a_zoneless_component)
29
+ - [Routing your pages](good-practices#routing_your_pages)
30
+ - [Simple form](good-practices#simple_form)
31
+ - [Complex form (sub-routes)](good-practices#complex_form__sub-routes_)
32
+ - [Creating a reusable component](good-practices#creating_a_reusable_component)
33
+ - [Example with foehn-input-date of prestations-ng](good-practices#example_with_foehn-input-date_of_prestations-ng)
34
+ - [Displaying the global validation of the component](good-practices#displaying_the_global_validation_of_the_component)
35
+ - [Avoiding error display problems in a `@for` loop](good-practices#avoiding_error_display_problems_in_a__code__for__code__loop)
36
+ - [Types of the form fields](good-practices#types_of_the_form_fields)
37
+ - [Form validation](good-practices#form_validation)
38
+ - [Standard handling](good-practices#standard_handling)
39
+ - [Manual handling](good-practices#manual_handling)
38
40
  - [Back-end](good-practices#back-end)
39
41
  - [Front-end](good-practices#front-end)
40
- - [Validation manuelle d'un composant](good-practices#validation_manuelle_d_un_composant)
41
- - [Cacher une erreur](good-practices#cacher_une_erreur)
42
- - [Afficher une erreur](good-practices#afficher_une_erreur)
42
+ - [Manual validation of a component](good-practices#manual_validation_of_a_component)
43
+ - [Hiding an error](good-practices#hiding_an_error)
44
+ - [Displaying an error](good-practices#displaying_an_error)
43
45
 
44
- > ⚠️ Dans toutes vos pages HTML vous devez avoir un `<foehn-form>` et celui-ci **ne peut pas être
45
- > conditionnel** (pas de `@if` autour de `<foehn-form>`).
46
+ > ⚠️ All your HTML pages must have a `<foehn-form>`, and it **can't be
47
+ > conditional** (no `@if` around `<foehn-form>`).
46
48
 
47
49
  ## Documentation
48
50
 
49
51
  - [PrestaKit](https://www.vd.ch/toutes-les-autorites/departements/departement-des-infrastructures-et-des-ressources-humaines-dirh/direction-generale-du-numerique-et-des-systemes-dinformation-dgnsi/prestakit-sdk-pour-le-developpement-des-prestations-en-ligne/)
50
52
  - [prestations-ng](https://dsi-vd.github.io/prestations-ng/)
51
- - [prestations-ng (doc autogénérée)](https://dsi-vd.github.io/prestations-ng/generateddoc/)
52
- - [foehn](https://dsi-vd.github.io/foehn-design-system/components/detail/formulaire-prestation--default.html) (Charte graphique)
53
+ - [prestations-ng (generated doc)](https://dsi-vd.github.io/prestations-ng/generateddoc/)
54
+ - [foehn](https://dsi-vd.github.io/foehn-design-system/components/detail/formulaire-prestation--default.html) (Graphic charter)
53
55
  - [CSS](https://www.w3schools.com/css/default.asp)
54
56
  - [Bootstrap](https://getbootstrap.com/docs/5.3/layout/grid/)
55
57
  - [Flex - Bootstrap](https://getbootstrap.com/docs/5.3/utilities/flex/)
56
58
  - [Flex](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)
57
59
  - [HTML](https://www.w3schools.com/html/default.asp)
58
- - [Éléments de section](https://www.alsacreations.com/article/lire/1376-html5-section-article-nav-header-footer-aside.html)
60
+ - [Section elements](https://www.alsacreations.com/article/lire/1376-html5-section-article-nav-header-footer-aside.html) (in French)
59
61
  - [Description list](https://www.w3schools.com/tags/tag_dl.asp)
60
62
  - [RXJS](https://rxjs-dev.firebaseapp.com/)
61
63
 
62
- ## Mise à jour de la librairie
64
+ ## Updating the library
63
65
 
64
- > ℹ️ Maintenez à jour vos librairies en vous inscrivant sur la page [PrestaKit](https://www.vd.ch/toutes-les-autorites/departements/departement-des-infrastructures-et-des-ressources-humaines-dirh/direction-generale-du-numerique-et-des-systemes-dinformation-dgnsi/prestakit-sdk-pour-le-developpement-des-prestations-en-ligne/)
65
- > et vous serez ainsi notifié des nouvelles releases. Cela vous permettra de profiter des corrections
66
- > de bug, améliorations et nouvelles fonctionnalités.
66
+ > ℹ️ Keep your libraries up to date by subscribing on the [PrestaKit](https://www.vd.ch/toutes-les-autorites/departements/departement-des-infrastructures-et-des-ressources-humaines-dirh/direction-generale-du-numerique-et-des-systemes-dinformation-dgnsi/prestakit-sdk-pour-le-developpement-des-prestations-en-ligne/)
67
+ > page: you will be notified of the new releases. You will then benefit from the bug fixes,
68
+ > improvements and new features.
67
69
  >
68
- > Prenez le temps de lire le [changelog](CHANGELOG.md) de **prestations-ng** à chaque nouvelle release.
70
+ > Take the time to read the [changelog](CHANGELOG.md) of **prestations-ng** at each new release.
69
71
  >
70
- > N'hésitez pas à ouvrir des tickets JIRA (PRESTAKIT-xxx) dans [PrestaKit (anc SDK Cyber)](https://issuetracker.etat-de-vaud.ch/outils/issuetracker/browse/PRESTAKIT)
71
- > pour signaler des bugs ou même à faire des propositions d'améliorations afin de
72
- > [contribuer](CONTRIBUTING.md) au développement de prestations-ng.
72
+ > Don't hesitate to open JIRA tickets (PRESTAKIT-xxx) in [PrestaKit (formerly SDK Cyber)](https://issuetracker.etat-de-vaud.ch/outils/issuetracker/browse/PRESTAKIT)
73
+ > to report bugs, or even to suggest improvements in order to
74
+ > [contribute](CONTRIBUTING.md) to the development of prestations-ng.
73
75
 
74
- Mettre à jour la librairie prestations-ng (npm) :
76
+ Update the prestations-ng library (npm):
75
77
 
76
78
  ```bash
77
79
  ng update @dsivd/prestations-ng
78
80
  ```
79
81
 
80
- ## Accessibilité
82
+ ## Accessibility
81
83
 
82
- > ℹ️ En développant des prestations pour l'État, nous devons faire tout notre possible pour rendre nos
83
- > pages web les plus accessibles possibles au grand public.
84
+ > ℹ️ When developing prestations for the State, we must do everything we can to make our web pages
85
+ > as accessible as possible to the general public.
84
86
  >
85
- > Prestations-ng fait une bonne partie du boulot pour nous mais c'est au développeur de prendre le
86
- > soin de mettre les efforts qu'il faut pour que la prestation reste accessible.
87
+ > Prestations-ng does a good part of the job for us, but it's up to the developer to make the
88
+ > necessary efforts so that the prestation stays accessible.
87
89
 
88
- ### Structure HTML
90
+ ### HTML structure
89
91
 
90
- > ⚠️ Nous recommandons :
92
+ > ⚠️ We recommend to:
91
93
  >
92
- > - une seule balise h1 (`<h1>`)
93
- > - respecter la hiérarchie h1, h2, h3..
94
- > - regrouper vos informations dans des sections (`<section>`) si vous avez plusieurs h2 (`<h2>`)
95
- > - utiliser la classe CSS `col-md-8` de Bootstrap pour définir la largeur de votre formulaire
96
- > - organisez votre texte avec des paragraphes (`<p>`)
97
- > - utiliser les balises `<dl>`, `<dt>` et `<dd>` pour afficher des clés / valeurs (voir [foehn-recap-section](https://dsi-vd.github.io/prestations-ng/recap))
98
- > - utiliser [foehn-abbr](https://dsi-vd.github.io/prestations-ng/misc) pour les abréviations tant que possible et sinon la balise abbr (`<abbr>`)
94
+ > - have a single h1 tag (`<h1>`)
95
+ > - respect the hierarchy h1, h2, h3..
96
+ > - group your information in sections (`<section>`) if you have several h2 (`<h2>`)
97
+ > - use the Bootstrap CSS class `col-md-8` to define the width of your form
98
+ > - organize your text with paragraphs (`<p>`)
99
+ > - use the `<dl>`, `<dt>` and `<dd>` tags to display keys / values (see [foehn-recap-section](https://dsi-vd.github.io/prestations-ng/recap))
100
+ > - use [foehn-abbr](https://dsi-vd.github.io/prestations-ng/misc) for the abbreviations whenever possible, and the abbr tag (`<abbr>`) otherwise
99
101
 
100
- Ce qu'il ne faut **pas** faire :
102
+ What **not** to do:
101
103
 
102
104
  ```html
103
105
  <h1>...</h1>
@@ -107,7 +109,7 @@ Ce qu'il ne faut **pas** faire :
107
109
  ... ... ...
108
110
  ```
109
111
 
110
- Ce qu'il **faut** faire :
112
+ What **to** do:
111
113
 
112
114
  ```html
113
115
  <h1>...</h1>
@@ -122,27 +124,27 @@ Ce qu'il **faut** faire :
122
124
  <section>...</section>
123
125
  ```
124
126
 
125
- Quelques attributs HTML utiles :
127
+ Some useful HTML attributes:
126
128
 
127
- - Pour les images (`<img>`)
128
- - `title="[texte explicite ici]"`
129
- - `alt="[texte explicite ici]"`
130
- - Pour les liens (`<a>`)
131
- - `title="[texte explicite ici]"`
129
+ - For the images (`<img>`)
130
+ - `title="[explicit text here]"`
131
+ - `alt="[explicit text here]"`
132
+ - For the links (`<a>`)
133
+ - `title="[explicit text here]"`
132
134
  - `target="_blank"`
133
- - [Rendre les balises accessibles](https://www.w3.org/TR/wai-aria-1.2/#intro_ria_accessibility)
135
+ - [Make the tags accessible](https://www.w3.org/TR/wai-aria-1.2/#intro_ria_accessibility)
134
136
  - [`aria-*`](https://w3c.github.io/using-aria/#aria-states-and-properties-aria-attributes)
135
137
  - [`role`](https://www.w3.org/WAI/PF/HTML/wiki/RoleAttribute)
136
138
 
137
- ### Le focus
139
+ ### Focus
138
140
 
139
- > ⚠️ Il est important que le focus dans une page HTML soit maîtrisé. Si le focus est perdu, alors la
140
- > liseuse d'écran le sera aussi.
141
+ > ⚠️ It's important that the focus in an HTML page is under control. If the focus is lost, the
142
+ > screen reader is lost too.
141
143
  >
142
- > Si, par exemple, on ouvre une modale en cliquant sur un bouton, alors à la fermeture de la modale,
143
- > le focus doit être remis sur le bouton déclencheur.
144
+ > If, for example, a modal is opened by clicking on a button, then when the modal is closed, the
145
+ > focus must be put back on the triggering button.
144
146
 
145
- Exemple :
147
+ Example:
146
148
 
147
149
  ```html
148
150
  <button
@@ -151,7 +153,7 @@ Exemple :
151
153
  (click)="openModal()"
152
154
  #modalTrigger
153
155
  >
154
- <!-- on déclare le déclencheur de la modale -->
156
+ <!-- we declare the trigger of the modal -->
155
157
  Open Modal
156
158
  </button>
157
159
  <foehn-modal
@@ -160,13 +162,13 @@ Exemple :
160
162
  [(isModalVisible)]="isModalVisible"
161
163
  [modalTriggerHtmlElement]="modalTrigger"
162
164
  >
163
- <!-- on utilise le déclencheur -->
165
+ <!-- we use the trigger -->
164
166
  <div>Here is the dialog</div>
165
167
  </foehn-modal>
166
168
  ```
167
169
 
168
170
  ```ts
169
- // Un signal : la modale s'affiche et se masque aussi sans zone.js
171
+ // A signal: the modal is also displayed and hidden without zone.js
170
172
  readonly isModalVisible = signal(false);
171
173
 
172
174
  openModal(): void {
@@ -174,105 +176,106 @@ openModal(): void {
174
176
  }
175
177
  ```
176
178
 
177
- ### La balise `<label>`
179
+ ### The `<label>` tag
178
180
 
179
- > ⚠️ Tous nos composants `foehn-input-*` disposent d'une balise label ([`<label>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/label))
180
- > qui décrit le champ de saisie. Il est important que celle-ci soit remplie avec un contenu explicite.
181
+ > ⚠️ All our `foehn-input-*` components have a label tag ([`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label))
182
+ > which describes the input field. It's important that it is filled with an explicit content.
181
183
  >
182
- > Si pour une raison quelconque, le métier ne souhaite pas l'afficher, vous devez tout de même la
183
- > renseigner et ajouter un attribut `[isLabelSrOnly]="true"`. Le label ne sera pas affiché mais
184
- > uniquement lu par la liseuse d'écran.
184
+ > If for any reason the business doesn't want to display it, you must still fill it and add an
185
+ > `[isLabelSrOnly]="true"` attribute. The label won't be displayed, only read by the screen
186
+ > reader.
185
187
 
186
- Exemple :
188
+ Example:
187
189
 
188
190
  ```html
189
191
  <foehn-input-number
190
192
  name="..."
191
193
  [(model)]="..."
192
- [label]="'Le texte que la liseuse va lire'"
194
+ [label]="'The text read by the screen reader'"
193
195
  [isLabelSrOnly]="true"
194
196
  [required]="..."
195
197
  [maxlength]="..."
196
198
  />
197
199
  ```
198
200
 
199
- > 💡 `label`, `isLabelSrOnly`, `required`, ... sont désormais des **input signals** côté composant.
200
- > Vous continuez à les alimenter depuis le template parent via un binding `[label]="..."`.
201
- > À l'intérieur du composant, vous les lisez en les invoquant : `label()`, `required()`, ...
201
+ > 💡 `label`, `isLabelSrOnly`, `required`, ... are now **input signals** in the component.
202
+ > You keep feeding them from the parent template with a `[label]="..."` binding.
203
+ > Inside the component, you read them by calling them: `label()`, `required()`, ...
202
204
 
203
- ### La balise `<ul>` ou `<ol>`
205
+ ### The `<ul>` or `<ol>` tag
204
206
 
205
207
  > ⚠️
206
208
  >
207
- > - L'élément [`<ul>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/ul) doit être utilisé
208
- > pour regrouper plusieurs éléments qui n'ont pas de relation d'ordre. Si on hésite entre
209
- > [`<ol>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/ol) et
210
- > [`<ul>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/ul), on se demandera si changer
211
- > l'ordre des éléments de la liste a un impact : si le déplacement d'un élément change la
212
- > signification, cela signifie que l'ordre est important et qu'il faudra utiliser
213
- > [`<ol>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/ol), sinon l'ordre n'importe pas
214
- > et [`<ul>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/ul) peut être utilisé.
215
- > - Si [`<ol>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/ol) ou
216
- > [`<ul>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/ul) ne contient qu'un seul élément
217
- > [`<li>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/li) (non dynamique), alors on se
218
- > demandera si l'utilisation d'une liste est nécessaire et si un paragraphe
219
- > ([`<p>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/p)) ne serait pas une meilleure
209
+ > - The [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul) element must be used
210
+ > to group several items that have no order relationship. If you hesitate between
211
+ > [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol) and
212
+ > [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul), ask yourself whether
213
+ > changing the order of the items of the list has an impact: if moving an item changes the
214
+ > meaning, the order is important and
215
+ > [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol) must be used, otherwise
216
+ > the order doesn't matter and [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul)
217
+ > can be used.
218
+ > - If [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol) or
219
+ > [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul) only contains a single
220
+ > [`<li>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li) element (not dynamic),
221
+ > ask yourself whether a list is necessary and whether a paragraph
222
+ > ([`<p>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p)) wouldn't be a better
220
223
  > option.
221
- > - Ne **JAMAIS** mettre une autre balise que [`<li>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/li)
222
- > entre deux balises [`<ol>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/ol) ou
223
- > [`<ul>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/ul).
224
+ > - **NEVER** put a tag other than [`<li>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li)
225
+ > between two [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol) or
226
+ > [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul) tags.
224
227
 
225
- Exemple :
228
+ Example:
226
229
 
227
230
  ```html
228
231
  <ul>
229
- <li>un artichaut</li>
232
+ <li>an artichoke</li>
230
233
  <li>
231
- Les trucs pour le gâteau
232
- <!-- On voit que </li> n'est pas là -->
234
+ The things for the cake
235
+ <!-- Note that </li> isn't there -->
233
236
  <ul>
234
- <li>trois œufs</li>
237
+ <li>three eggs</li>
235
238
  <li>
236
- La génoise
237
- <!-- Là on ouvre une autre liste -->
239
+ The sponge cake
240
+ <!-- Here we open another list -->
238
241
  <ul>
239
- <li>100g de sucre</li>
240
- <li>un œuf</li>
241
- <li>150g de farine</li>
242
+ <li>100g of sugar</li>
243
+ <li>an egg</li>
244
+ <li>150g of flour</li>
242
245
  </ul>
243
246
  </li>
244
- <!-- On ferme la liste la plus imbriquée -->
245
- <li>200g de chocolat</li>
247
+ <!-- We close the most nested list -->
248
+ <li>200g of chocolate</li>
246
249
  </ul>
247
- <!-- On ferme la liste imbriquée avec </li> -->
250
+ <!-- We close the nested list with </li> -->
248
251
  </li>
249
- <li>De l'essuie-tout</li>
252
+ <li>Paper towels</li>
250
253
  <li>
251
- A faire dans l'ordre pour une belle vaisselle
254
+ To do in this order for a nice dishwashing
252
255
  <ol>
253
- <li>Remplir le lave vaisselle</li>
254
- <li>Mettre une pastille pour lave vaisselle</li>
255
- <li>Fermer la porte du lave vaisselle</li>
256
- <li>Lancer le lave vaisselle</li>
256
+ <li>Fill the dishwasher</li>
257
+ <li>Put a dishwasher tablet</li>
258
+ <li>Close the door of the dishwasher</li>
259
+ <li>Start the dishwasher</li>
257
260
  </ol>
258
261
  </li>
259
262
  </ul>
260
263
  ```
261
264
 
262
- ### La balise `<fieldset>` et `<legend>`
265
+ ### The `<fieldset>` and `<legend>` tags
263
266
 
264
- > ⚠️ L'élément HTML [`<fieldset>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/fieldset) est
265
- > utilisé afin de regrouper plusieurs contrôles interactifs ainsi que des étiquettes
266
- > ([`<label>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/label)) dans un formulaire HTML
267
- > et on doit lui associer une légende
268
- > ([`<legend>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/legend)) décrivant ce groupe.
267
+ > ⚠️ The HTML element [`<fieldset>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset) is
268
+ > used to group several interactive controls as well as labels
269
+ > ([`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)) in an HTML form,
270
+ > and it must have a caption
271
+ > ([`<legend>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend)) describing this group.
269
272
  >
270
- > L'élément HTML [`<legend>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/legend) représente
271
- > une légende pour le contenu de son élément parent
272
- > [`<fieldset>`](https://developer.mozilla.org/fr/docs/Web/HTML/Element/fieldset). Il ne peut pas être
273
- > utilisé tout seul.
273
+ > The HTML element [`<legend>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend) represents
274
+ > a caption for the content of its parent element
275
+ > [`<fieldset>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset). It can't be
276
+ > used on its own.
274
277
 
275
- Exemple :
278
+ Example:
276
279
 
277
280
  ```html
278
281
  <form>
@@ -291,45 +294,45 @@ Exemple :
291
294
  </form>
292
295
  ```
293
296
 
294
- ### Liseuse d'écran (afficher / masquer)
297
+ ### Screen reader (show / hide)
295
298
 
296
- > ⚠️ Il est important de garder en tête qu'une liseuse d'écran va parcourir notre code HTML. De ce
297
- > fait, nous pouvons à tout moment décider de lui afficher explicitement du contenu à lire ou de lui
298
- > masquer du contenu pour ne pas surcharger l'information.
299
+ > ⚠️ It's important to keep in mind that a screen reader goes through our HTML code. So we can
300
+ > decide at any time to explicitly show it content to read, or to hide content from it so as not to
301
+ > overload the information.
299
302
 
300
- - Afficher uniquement pour la liseuse : utiliser la classe CSS `sr-only`
301
- - Masquer du contenu à la liseuse : utiliser l'attribut `aria-hidden="true"`
303
+ - Show only to the screen reader: use the CSS class `sr-only`
304
+ - Hide content from the screen reader: use the attribute `aria-hidden="true"`
302
305
 
303
- Exemple :
306
+ Example:
304
307
 
305
308
  ```html
306
309
  <a href="https://www.vd.ch">
307
310
  <span class="sr-only">
308
- <!-- Afficher uniquement pour la liseuse d'écran -->
309
- Retour à la page d'accueil
311
+ <!-- Show only to the screen reader -->
312
+ Back to the home page
310
313
  </span>
311
314
  <img
312
315
  aria-hidden="true"
313
316
  class="img-fluid footer-logo"
314
- src="[url_vers_mon_image]"
317
+ src="[url_to_my_image]"
315
318
  alt="Canton de Vaud"
316
319
  />
317
- <!-- Masquer uniquement pour la liseuse d'écran -->
320
+ <!-- Hide only from the screen reader -->
318
321
  </a>
319
322
  ```
320
323
 
321
- Le résultat de l'exemple ci-dessus sera un lien représenté par une image pour les voyants et un lien
322
- avec du texte pour les non-voyants.
324
+ The result of the example above is a link represented by an image for sighted people, and a link
325
+ with text for visually impaired people.
323
326
 
324
- ### Une page prestations-ng
327
+ ### A prestations-ng page
325
328
 
326
- 1. Étendre la classe abstraite `AbstractPageComponent` en spécifiant un modèle
327
- 2. Ajouter un titre h1 en utilisant `setPageTitle`
328
- 3. Implémenter la fonction `newForm`
329
- 4. Ajouter le `<foehn-form>` dans la page (⚠️ **il ne doit pas être conditionnel**)
330
- 5. La première balise d'en-tête de votre page doit être un h2 (`<h2>`)
331
- 6. Ajouter `<foehn-navigation>`
332
- 7. Si vous disposez de plusieurs pages, utiliser le composant `<foehn-page-counter>`
329
+ 1. Extend the abstract class `AbstractPageComponent`, specifying a model
330
+ 2. Add an h1 title with `setPageTitle`
331
+ 3. Implement the `newForm` function
332
+ 4. Add the `<foehn-form>` in the page (⚠️ **it must not be conditional**)
333
+ 5. The first heading tag of your page must be an h2 (`<h2>`)
334
+ 6. Add `<foehn-navigation>`
335
+ 7. If you have several pages, use the `<foehn-page-counter>` component
333
336
 
334
337
  `page-one.component.ts`
335
338
 
@@ -337,12 +340,12 @@ avec du texte pour les non-voyants.
337
340
  @Component({
338
341
  templateUrl: './page-one.component.html',
339
342
  imports: [
340
- // chaque composant importe ce dont il a besoin dans son template
343
+ // each component imports what its template needs
341
344
  FoehnFormComponent,
342
345
  FoehnNavigationComponent,
343
346
  FoehnPageCounterComponent,
344
347
  SdkDictionaryPipe,
345
- // ... vos composants foehn-* utilisés dans le template
348
+ // ... your foehn-* components used in the template
346
349
  ],
347
350
  })
348
351
  export class PageOneComponent
@@ -351,7 +354,7 @@ export class PageOneComponent
351
354
  {
352
355
  ngOnInit(): void {
353
356
  super.ngOnInit();
354
- // point (2) : ajoutera le titre h1
357
+ // point (2): adds the h1 title
355
358
  this.setPageTitle(this._dictionaryService.getKeySync('page.one.title'));
356
359
  }
357
360
 
@@ -362,28 +365,28 @@ export class PageOneComponent
362
365
  }
363
366
  ```
364
367
 
365
- > 💡 Plus de `constructor` : les dépendances de `AbstractPageComponent` (dont `_dictionaryService`)
366
- > sont injectées via `inject()` dans la classe abstraite. `activatedRoute` n'est donc plus un
367
- > paramètre de constructeur à passer.
368
+ > 💡 No more `constructor`: the dependencies of `AbstractPageComponent` (including `_dictionaryService`)
369
+ > are injected with `inject()` in the abstract class. `activatedRoute` is therefore no longer a
370
+ > constructor parameter to pass.
368
371
 
369
372
  `page-one.component.html`
370
373
 
371
374
  ```html
372
375
  <div class="container mt-5">
373
376
  <foehn-page-counter />
374
- <!-- point (7) : si besoin -->
377
+ <!-- point (7): if needed -->
375
378
 
376
- <!-- point (4) : doit toujours être présent dans la page et pas conditionnel (pas de @if) -->
379
+ <!-- point (4): must always be present in the page and not conditional (no @if) -->
377
380
  <foehn-form>
378
381
  <div class="row">
379
382
  <div class="col-md-8">
380
- <!-- point (5) : toujours avoir un <h2> en premier dans votre page -->
383
+ <!-- point (5): always have an <h2> first in your page -->
381
384
  <h2>
382
- {{ 'La_clé_du_dictionnaire_de_votre_titre_H2_ici' |
385
+ {{ 'The_dictionary_key_of_your_H2_title_here' |
383
386
  fromDictionary }}
384
387
  </h2>
385
388
 
386
- <!-- [VOS COMPOSANTS FOEHN ICI] -->
389
+ <!-- [YOUR FOEHN COMPONENTS HERE] -->
387
390
 
388
391
  <foehn-navigation
389
392
  id="navigation"
@@ -397,40 +400,40 @@ export class PageOneComponent
397
400
  </div>
398
401
  ```
399
402
 
400
- ## Application zoneless
403
+ ## Zoneless application
401
404
 
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
405
+ > ℹ️ Since v19.2, prestations-ng no longer needs zone.js and we recommend that you make your
406
+ > application zoneless (`provideZonelessChangeDetection()`, see
404
407
  > [UPGRADING_V19.md](UPGRADING_V19.md#zoneless-optional-but-recommended)).
405
408
  >
406
- > Sans zone.js, Angular ne rafraîchit un composant que lorsqu'il est prévenu d'un changement :
409
+ > Without zone.js, Angular only refreshes a component when it is notified of a change:
407
410
  >
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
+ > - a listener of its template has been called (`(click)`, `(modelChange)`, `(userInput)`, ...),
412
+ > including the `[(model)]` two-way bindings
413
+ > - a signal read by its template has changed (`signal()`, `computed()`, `input()`, `model()`,
411
414
  > `toSignal()`, ...)
412
- > - `ChangeDetectorRef.markForCheck()` a été appelé, ce que fait le pipe `async` lorsqu'un observable
413
- > émet
415
+ > - `ChangeDetectorRef.markForCheck()` has been called, which is what the `async` pipe does when an
416
+ > observable emits
414
417
 
415
- ### Afficher un état asynchrone
418
+ ### Displaying an asynchronous state
416
419
 
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
+ > ⚠️ A field modified elsewhere (in a `subscribe()`, a `setTimeout()`, a `Promise`, a
421
+ > callback, ...) is no longer displayed until something else refreshes the component. Hold it in
422
+ > a **signal**.
420
423
 
421
- Ce qu'il ne faut **pas** faire :
424
+ What **not** to do:
422
425
 
423
426
  ```ts
424
427
  isLoading = true;
425
428
 
426
429
  ngOnInit(): void {
427
430
  this.myService.load().subscribe(() => {
428
- this.isLoading = false; // pas affiché sans zone.js
431
+ this.isLoading = false; // not displayed without zone.js
429
432
  });
430
433
  }
431
434
  ```
432
435
 
433
- Ce qu'il **faut** faire :
436
+ What **to** do:
434
437
 
435
438
  ```ts
436
439
  readonly isLoading = signal(true);
@@ -448,12 +451,12 @@ ngOnInit(): void {
448
451
  }
449
452
  ```
450
453
 
451
- > 💡 Pour un observable affiché tel quel, `toSignal(this.myService.load())` ou le pipe `async` font
452
- > le travail pour vous.
454
+ > 💡 For an observable displayed as is, `toSignal(this.myService.load())` or the `async` pipe do
455
+ > the job for you.
453
456
 
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
+ In your pages (`AbstractPageComponent`), prestations-ng refreshes the page when it loads `form`
458
+ and `reference`. If you modify the form yourself in an asynchronous callback, call
459
+ `markForCheck()` afterwards:
457
460
 
458
461
  ```ts
459
462
  private readonly changeDetectorRef = inject(ChangeDetectorRef);
@@ -469,10 +472,57 @@ ngOnInit(): void {
469
472
  }
470
473
  ```
471
474
 
472
- ### Agir sur le DOM après le rendu
475
+ If a service fills the form for the page, the page must still be notified: let the service return an
476
+ `Observable` (or take a callback), and call `markForCheck()` in the page.
473
477
 
474
- > ⚠️ `NgZone` (`onStable`, `onMicrotaskEmpty`, `run()`, ...) ne fonctionne plus sans zone.js, et un
475
- > `setTimeout()` n'attend pas le rendu. Pour agir sur le DOM rendu (focus, scroll, ...), utilisez
478
+ ### Deriving a state from view queries
479
+
480
+ > ⚠️ A field read by the template and modified in `ngAfterViewInit()`, `ngAfterContentInit()` or
481
+ > `ngAfterViewChecked()` is no longer displayed until something else refreshes the component (with
482
+ > zone.js, the next refresh displayed it, after an `NG0100` error in development).
483
+ > Compute it with `computed()` from `viewChild()` or `contentChild()`.
484
+
485
+ What **not** to do:
486
+
487
+ ```ts
488
+ @ViewChild('referenceLink') referenceLink?: TemplateRef<unknown>;
489
+ columns: FoehnTableColumnConfiguration<Order>[] = [];
490
+
491
+ ngAfterViewInit(): void {
492
+ // not displayed without zone.js
493
+ this.columns = [{ id: 'reference', columnLabelKey: 'orders.reference', template: this.referenceLink }];
494
+ }
495
+ ```
496
+
497
+ What **to** do:
498
+
499
+ ```ts
500
+ readonly referenceLink = viewChild<TemplateRef<unknown>>('referenceLink');
501
+ readonly columns = computed<FoehnTableColumnConfiguration<Order>[]>(() => [
502
+ { id: 'reference', columnLabelKey: 'orders.reference', template: this.referenceLink() },
503
+ ]);
504
+ ```
505
+
506
+ ```html
507
+ <foehn-table
508
+ [name]="'orders'"
509
+ [columnsConfiguration]="columns()"
510
+ [model]="orders()"
511
+ ></foehn-table>
512
+
513
+ <ng-template #referenceLink let-order="item">
514
+ <a [routerLink]="['/demande', order.id]">{{ order.reference }}</a>
515
+ </ng-template>
516
+ ```
517
+
518
+ > 💡 The `computed()` follows the result of the `viewChild()`, including when the element appears or
519
+ > disappears in an `@if`: no need to hide the content until it is resolved. If the element must
520
+ > always exist, `viewChild.required()` guarantees it (error `NG0951` otherwise).
521
+
522
+ ### Acting on the DOM after rendering
523
+
524
+ > ⚠️ `NgZone` (`onStable`, `onMicrotaskEmpty`, `run()`, ...) no longer works without zone.js, and a
525
+ > `setTimeout()` doesn't wait for the rendering. To act on the rendered DOM (focus, scroll, ...), use
476
526
  > `afterNextRender()`.
477
527
 
478
528
  ```ts
@@ -483,18 +533,18 @@ private readonly injector = inject(Injector);
483
533
 
484
534
  showDetails(): void {
485
535
  this.detailsVisible.set(true);
486
- // Le champ n'existe qu'une fois le détail rendu
536
+ // The field only exists once the details are rendered
487
537
  afterNextRender(() => this.detailsField()?.nativeElement.focus(), {
488
538
  injector: this.injector,
489
539
  });
490
540
  }
491
541
  ```
492
542
 
493
- ### Tester un composant zoneless
543
+ ### Testing a zoneless component
494
544
 
495
- > ⚠️ `fixture.detectChanges()` **force** le rafraîchissement : un composant qui oublie de prévenir
496
- > Angular (champ au lieu d'un signal, `markForCheck()` manquant, ...) passe le test, mais n'est pas
497
- > rafraîchi dans l'application. Laissez Angular rafraîchir le composant, comme en production, avec
545
+ > ⚠️ `fixture.detectChanges()` **forces** the refresh: a component that forgets to notify
546
+ > Angular (a field instead of a signal, a missing `markForCheck()`, ...) passes the test, but isn't
547
+ > refreshed in the application. Let Angular refresh the component, as in production, with
498
548
  > `await fixture.whenStable()`.
499
549
 
500
550
  ```ts
@@ -506,22 +556,34 @@ it('should display the details', async () => {
506
556
  });
507
557
  ```
508
558
 
509
- `await fixture.whenStable()` attend aussi les tâches en cours, comme les requêtes HTTP : le test ne
510
- se termine pas avant elles (sinon `NG0205: Injector has already been destroyed`).
559
+ `await fixture.whenStable()` also waits for the pending tasks of the application, such as the debounced
560
+ `userInput` of the prestations-ng inputs: the test doesn't end before them (otherwise
561
+ `NG0205: Injector has already been destroyed`). But it doesn't wait for the HTTP requests of a spec:
562
+ `provideHttpClientTesting()` keeps them pending until the test answers them. Answer them first with
563
+ `HttpTestingController`, then `await fixture.whenStable()`:
564
+
565
+ ```ts
566
+ it('should display the reference of the demand', async () => {
567
+ httpMock.expectOne('api/demande/1').flush({ reference: 'DEM-1' });
568
+ await fixture.whenStable();
569
+
570
+ expect(fixture.nativeElement.textContent).toContain('DEM-1');
571
+ });
572
+ ```
511
573
 
512
- Gardez `fixture.detectChanges()` pour :
574
+ Keep `fixture.detectChanges()` for:
513
575
 
514
- - vérifier un état intermédiaire, avant la fin d'un traitement asynchrone
515
- - les tests avec `vi.useFakeTimers()` : le rafraîchissement d'Angular passe par des timers, qu'il
516
- faut avancer (`await vi.advanceTimersByTimeAsync(...)`), sinon `whenStable()` ne se termine pas
576
+ - checking an intermediate state, before the end of an asynchronous processing
577
+ - the tests with `vi.useFakeTimers()`: the refresh of Angular goes through timers, which must be
578
+ advanced (`await vi.advanceTimersByTimeAsync(...)`), otherwise `whenStable()` never ends
517
579
 
518
- > 💡 Pour que le test passe par le même chemin que l'utilisateur, déclenchez les événements dans le
519
- > DOM (`input.dispatchEvent(new Event('change'))`, `button.click()`, ...) plutôt que d'appeler les
520
- > méthodes du composant ou de ses services.
580
+ > 💡 So that the test goes through the same path as the user, trigger the events in the
581
+ > DOM (`input.dispatchEvent(new Event('change'))`, `button.click()`, ...) rather than calling the
582
+ > methods of the component or of its services.
521
583
 
522
- ## Le routage de vos pages
584
+ ## Routing your pages
523
585
 
524
- ### Formulaire simple
586
+ ### Simple form
525
587
 
526
588
  `app.routes.ts`
527
589
 
@@ -561,14 +623,14 @@ export const routes: Routes = [
561
623
  ];
562
624
  ```
563
625
 
564
- ### Formulaire complexe (sous-routes)
626
+ ### Complex form (sub-routes)
565
627
 
566
- > ℹ️ Préférez l'utilisation de **sous-routes** (`*.routes.ts`) pour regrouper vos routes et ainsi
567
- > mieux structurer votre projet.
628
+ > ℹ️ Prefer **sub-routes** (`*.routes.ts`) to group your routes and thus better structure your
629
+ > project.
568
630
  >
569
- > Comme il n'y a plus de modules (composants **standalone**), on ne charge plus un `*.module.ts` mais
570
- > un fichier de routes qui exporte une constante `Routes`. Voir [UPGRADING_V19.md](UPGRADING_V19.md)
571
- > pour le détail de la migration.
631
+ > As there are no more modules (**standalone** components), you no longer load a `*.module.ts` but
632
+ > a routes file exporting a `Routes` constant. See [UPGRADING_V19.md](UPGRADING_V19.md)
633
+ > for the details of the migration.
572
634
 
573
635
  `app.routes.ts`
574
636
 
@@ -608,9 +670,9 @@ export const routes: Routes = [
608
670
  ];
609
671
  ```
610
672
 
611
- > 💡 On charge donc `welcome.routes.ts` (et non plus `welcome.module.ts`) :
673
+ > 💡 So we load `welcome.routes.ts` (and no longer `welcome.module.ts`):
612
674
  > `loadChildren: () => import('./modules/welcome/welcome.routes').then((m) => m.welcome_routes)`
613
- > au lieu de `import('./modules/welcome/welcome.module').then((m) => m.WelcomeModule)`.
675
+ > instead of `import('./modules/welcome/welcome.module').then((m) => m.WelcomeModule)`.
614
676
 
615
677
  `welcome.routes.ts`
616
678
 
@@ -658,45 +720,44 @@ export const inscription_routes: Routes = [
658
720
  ];
659
721
  ```
660
722
 
661
- > 💡 Vous pouvez aussi charger paresseusement (`lazy-load`) un composant :
723
+ > 💡 You can also lazy-load a component:
662
724
  >
663
725
  > ```ts
664
726
  > { path: 'list', loadComponent: () => import('./demande-list-page/demande-list-page.component').then((m) => m.DemandeListPageComponent) },
665
727
  > ```
666
728
 
667
- ## Création d'un composant réutilisable
729
+ ## Creating a reusable component
668
730
 
669
- > ℹ️ Créer un composant réutilisable en Angular n'est pas difficile mais pour qu'il soit adapté à
670
- > prestations-ng, c'est une autre affaire. Il va falloir respecter certaines règles, sans quoi, ça ne
671
- > fonctionnera pas correctement.
731
+ > ℹ️ Creating a reusable component in Angular isn't difficult, but making it fit prestations-ng is
732
+ > another matter. Some rules must be respected, otherwise it won't work properly.
672
733
 
673
- ### Exemple avec foehn-input-date de prestations-ng
734
+ ### Example with foehn-input-date of prestations-ng
674
735
 
675
- Votre composant doit :
736
+ Your component must:
676
737
 
677
- 1. `selector` devrait commencer par `"app-..."` pour éviter d'éventuels conflits de nommage
678
- 2. avoir un `providers`
679
- 3. étendre `FoehnInputComponent` et vous devez lui préciser votre modèle de données
680
- 4. répartir les attributs de votre modèle de données dans des **signaux** publics utilisés dans
681
- votre HTML
682
- 5. implémenter une fonction `update` (ici ce sera `updateDate`) qui fera appel à `updateNgModel` pour
683
- maintenir la synchronisation de votre modèle de données avec votre page
684
- 6. implémenter une fonction `handleUserInput` qui fera appel à `handleChange` pour continuer à
685
- notifier le parent des changements de comportement de votre composant
686
- 7. implémenter une fonction `onModelChange` qui vous permettra d'initialiser correctement les
687
- signaux correspondant à votre modèle
688
- 8. implémenter une fonction `getValidValue` afin de reconstruire votre modèle de données avant de le
689
- retransmettre à votre page et retourner `null` si tous les attributs de votre objet sont vides (ça
690
- c'est pour que Java soit content)
738
+ 1. `selector` should start with `"app-..."` to avoid possible naming conflicts
739
+ 2. have a `providers`
740
+ 3. extend `FoehnInputComponent`, to which you must give your data model
741
+ 4. spread the attributes of your data model in public **signals** used in
742
+ your HTML
743
+ 5. implement an `update` function (here it will be `updateDate`) calling `updateNgModel` to
744
+ keep your data model synchronized with your page
745
+ 6. implement a `handleUserInput` function calling `handleChange` to keep
746
+ notifying the parent of the behavior changes of your component
747
+ 7. implement an `onModelChange` function to correctly initialize the
748
+ signals corresponding to your model
749
+ 8. implement a `getValidValue` function to rebuild your data model before sending it
750
+ back to your page, and return `null` if all the attributes of your object are empty (this
751
+ is to keep Java happy)
691
752
 
692
- > 🚨 Points **standalone / signals** à respecter :
753
+ > 🚨 **standalone / signals** points to respect:
693
754
  >
694
- > - **Pas de `multi: true`** dans le `providers` (retiré depuis la migration standalone).
695
- > - Les `imports` du composant se déclarent **directement** dans le décorateur `@Component`. Chaque
696
- > composant charge ce dont il a besoin (voir `foehn-input-date` dans ce projet).
697
- > - L'état affiché par le composant est tenu dans des **signaux** (point 4) : `onModelChange` est
698
- > appelé lorsque le modèle change, parfois en dehors d'un événement du template, et un simple champ
699
- > ne serait alors pas affiché sans zone.js.
755
+ > - **No `multi: true`** in the `providers` (removed since the standalone migration).
756
+ > - The `imports` of the component are declared **directly** in the `@Component` decorator. Each
757
+ > component loads what it needs (see `foehn-input-date` in this project).
758
+ > - The state displayed by the component is held in **signals** (point 4): `onModelChange` is
759
+ > called when the model changes, sometimes outside of an event of the template, and a plain field
760
+ > would then not be displayed without zone.js.
700
761
 
701
762
  `input-date.component.ts`
702
763
 
@@ -709,7 +770,7 @@ import { FoehnValidationAlertsComponent } from '../foehn-validation-alerts/foehn
709
770
  import { SdkDictionaryPipe } from '../sdk-dictionary/sdk-dictionary.pipe';
710
771
 
711
772
  @Component({
712
- // point (1) : dans votre prestation, le selector sera plutôt 'app-...'
773
+ // point (1): in your prestation, the selector will rather be 'app-...'
713
774
  selector: 'app-input-date',
714
775
  templateUrl: './input-date.component.html',
715
776
  // point (2)
@@ -717,10 +778,10 @@ import { SdkDictionaryPipe } from '../sdk-dictionary/sdk-dictionary.pipe';
717
778
  {
718
779
  provide: FoehnInputComponent,
719
780
  useExisting: forwardRef(() => InputDateComponent),
720
- // ⚠️ plus de `multi: true` en standalone
781
+ // ⚠️ no more `multi: true` in standalone
721
782
  },
722
783
  ],
723
- // les imports se déclarent directement ici : chaque composant charge ce dont il a besoin
784
+ // the imports are declared directly here: each component loads what it needs
724
785
  imports: [
725
786
  FoehnValidationAlertsComponent,
726
787
  FoehnInputNumberComponent,
@@ -728,9 +789,9 @@ import { SdkDictionaryPipe } from '../sdk-dictionary/sdk-dictionary.pipe';
728
789
  ],
729
790
  })
730
791
  export class InputDateComponent extends FoehnInputComponent<number[]> {
731
- // point (3) : extends FoehnInputComponent<number[]>
792
+ // point (3): extends FoehnInputComponent<number[]>
732
793
 
733
- // point (4) : les attributs de votre modèle répartis en signaux publics
794
+ // point (4): the attributes of your model spread in public signals
734
795
  readonly day = signal<string | null>(null);
735
796
  readonly month = signal<string | null>(null);
736
797
  readonly year = signal<string | null>(null);
@@ -789,17 +850,17 @@ export class InputDateComponent extends FoehnInputComponent<number[]> {
789
850
  }
790
851
  ```
791
852
 
792
- Chaque `foehn-input-*` utilisé dans votre vue doit :
853
+ Each `foehn-input-*` used in your view must:
793
854
 
794
- 1. avoir un `id` qui concatène l'appel à la fonction `buildId()` et le nom de l'attribut de votre objet
795
- 2. avoir un `name` qui concatène l'input **signal** `name()` et le nom de l'attribut de votre objet
796
- 3. avoir un `label`
797
- 4. utiliser votre signal public comme model en double-binding (banana in the box) : `[(model)]="day"`
798
- lit et écrit le signal (sans les parenthèses)
799
- 5. faire appel à `updateDate()` lors du déclenchement de l'événement `(modelChange)`
800
- 6. faire appel à `handleUserInput()` lors du déclenchement de l'événement `(userInput)`
855
+ 1. have an `id` concatenating the call to the `buildId()` function and the name of the attribute of your object
856
+ 2. have a `name` concatenating the **signal** input `name()` and the name of the attribute of your object
857
+ 3. have a `label`
858
+ 4. use your public signal as model in two-way binding (banana in the box): `[(model)]="day"`
859
+ reads and writes the signal (without the parentheses)
860
+ 5. call `updateDate()` when the `(modelChange)` event is triggered
861
+ 6. call `handleUserInput()` when the `(userInput)` event is triggered
801
862
 
802
- > 💡 `name` est désormais un **input signal**. On l'invoque : `name() + '_day'` (et non plus
863
+ > 💡 `name` is now an **input signal**. You call it: `name() + '_day'` (and no longer
803
864
  > `name + '_day'`).
804
865
 
805
866
  `input-date.component.html`
@@ -842,20 +903,20 @@ Chaque `foehn-input-*` utilisé dans votre vue doit :
842
903
  />
843
904
  ```
844
905
 
845
- ### Afficher la validation globale du composant
906
+ ### Displaying the global validation of the component
846
907
 
847
- - Il faut ajouter dans la partie HTML de votre composant le code décrit ci-dessous
848
- - Remonter une erreur correspondant au `name` de votre composant lorsque celui-ci est utilisé dans
849
- votre page de formulaire.
908
+ - Add the code below in the HTML part of your component
909
+ - Return an error matching the `name` of your component when it is used in
910
+ your form page.
850
911
 
851
- > 💡 Points **signals** à noter dans ce template :
912
+ > 💡 **signals** points to note in this template:
852
913
  >
853
- > - `@if` remplace `*ngIf` et les attributs du composant sont des signaux : on les invoque
914
+ > - `@if` replaces `*ngIf`, and the attributes of the component are signals: you call them
854
915
  > (`label()`, `required()`, `isLabelSrOnly()`, `hideNotRequiredExtraLabel()`, `helpText()`).
855
- > - `[ngClass]` est remplacé par des bindings `[class.xxx]="..."`.
916
+ > - `[ngClass]` is replaced by `[class.xxx]="..."` bindings.
856
917
 
857
918
  ```html
858
- <!-- (obligatoire) Affichera une barre verticale rouge sur l'entier du composant -->
919
+ <!-- (mandatory) Displays a red vertical bar on the whole component -->
859
920
  <div
860
921
  class="form-group"
861
922
  [class.has-danger]="hasErrorsToDisplay()"
@@ -863,12 +924,12 @@ Chaque `foehn-input-*` utilisé dans votre vue doit :
863
924
  [attr.id]="buildId('Container')"
864
925
  tabindex="-1"
865
926
  >
866
- <!-- (obligatoire) Permet de regrouper vos champs pour une validation globale -->
927
+ <!-- (mandatory) Groups your fields for a global validation -->
867
928
  <fieldset
868
929
  [attr.aria-describedby]="getDescribedBy()"
869
930
  [attr.aria-invalid]="hasErrorsToDisplay() || null"
870
931
  >
871
- <!-- (facultatif) Seulement si vous souhaitez afficher un label/legend global à votre composant -->
932
+ <!-- (optional) Only if you want to display a global label/legend for your component -->
872
933
  @if (!!label()) {
873
934
  <legend
874
935
  [class.visually-hidden]="isLabelSrOnly()"
@@ -883,10 +944,10 @@ Chaque `foehn-input-*` utilisé dans votre vue doit :
883
944
  </legend>
884
945
  }
885
946
 
886
- <!-- (obligatoire) -->
947
+ <!-- (mandatory) -->
887
948
  <foehn-validation-alerts [component]="this" />
888
949
 
889
- <!-- (facultatif) Seulement si vous souhaitez pouvoir afficher un helpText global au composant -->
950
+ <!-- (optional) Only if you want to be able to display a global helpText for the component -->
890
951
  @if (helpText()) {
891
952
  <small
892
953
  [attr.id]="buildId() + 'Help'"
@@ -895,36 +956,36 @@ Chaque `foehn-input-*` utilisé dans votre vue doit :
895
956
  ></small>
896
957
  }
897
958
 
898
- <!-- ...vos foehn-input-* ici -->
959
+ <!-- ...your foehn-input-* here -->
899
960
  </fieldset>
900
961
  </div>
901
962
  ```
902
963
 
903
- ### Éviter des problèmes d'affichage d'erreurs dans une boucle `@for`
964
+ ### Avoiding error display problems in a `@for` loop
904
965
 
905
- > ⚠️ Ne pas oublier de mettre un `track` avec un identifiant unique dans la boucle `@for` sinon la
906
- > validation ne s'affichera pas. Quand un identifiant unique tel que `id` n'est pas possible, tracker
907
- > par objet ou, en dernier recours, `track $index`.
966
+ > ⚠️ Don't forget to put a `track` with a unique identifier in the `@for` loop, otherwise the
967
+ > validation won't be displayed. When a unique identifier such as `id` isn't possible, track
968
+ > by object or, as a last resort, `track $index`.
908
969
 
909
- > ℹ️ Dans une boucle `@for` d'Angular avec un composant `FoehnInputComponent`, si vous validez votre
910
- > formulaire, les erreurs remontées sont synchronisées avec l'index de vos composants répétés dans la
911
- > boucle.
970
+ > ℹ️ In an Angular `@for` loop with a `FoehnInputComponent` component, if you validate your
971
+ > form, the errors returned are synchronized with the index of your components repeated in the
972
+ > loop.
912
973
  >
913
- > **Scénario :** Dans votre liste, lorsque vous supprimez un composant qui contient des erreurs à
914
- > l'index 0 (par ex.) et que le composant suivant (index 1) n'a pas d'erreur ou pas les mêmes
915
- > erreurs, alors ce dernier se retrouve avec les erreurs du composant fraîchement supprimé à
916
- > l'index 0.
974
+ > **Scenario:** In your list, when you remove a component that has errors at
975
+ > index 0 (for example) and the next component (index 1) has no errors or not the same
976
+ > errors, then the latter ends up with the errors of the component just removed at
977
+ > index 0.
917
978
  >
918
- > **Explication :** Ce phénomène est dû au fait que les erreurs remontées par GESDEM ne se
919
- > rafraîchissent que lors de la demande de sauvegarde des données. Du coup, le composant supprimé à
920
- > l'index 0 est remplacé par celui à l'index 1 mais les erreurs à l'index 0, elles, existent toujours
921
- > et se reportent donc sur le composant venant de l'index 1.
979
+ > **Explanation:** This happens because the errors returned by GESDEM are only
980
+ > refreshed when the data is saved. So the component removed at
981
+ > index 0 is replaced by the one at index 1, but the errors at index 0 still exist
982
+ > and are therefore carried over to the component coming from index 1.
922
983
 
923
- Voici la bonne pratique à mettre en place pour palier à ces problèmes d'affichage d'erreurs dans une
924
- boucle `@for`.
984
+ Here is the good practice to put in place to work around these error display problems in a
985
+ `@for` loop.
925
986
 
926
- Lorsque vous supprimez un élément dans votre liste, vous devez appeler une méthode se trouvant dans
927
- votre `AbstractPageComponent`. Cette méthode contiendra le code suivant :
987
+ When you remove an item from your list, you must call a method of
988
+ your `AbstractPageComponent`. This method will contain the following code:
928
989
 
929
990
  ```ts
930
991
  /*
@@ -934,30 +995,30 @@ votre `AbstractPageComponent`. Cette méthode contiendra le code suivant :
934
995
  this._gesdemService.validate(this.form).pipe(first()).subscribe();
935
996
  ```
936
997
 
937
- Voici un exemple de page de formulaire contenant cette méthode (1) :
998
+ Here is an example of a form page containing this method (1):
938
999
 
939
- > 💡 Plus de `constructor` : on fait de l'**inject**. Les dépendances de `AbstractPageComponent`
940
- > (dont `_dictionaryService` et `_gesdemService`) sont déjà injectées dans la classe abstraite. Pour
941
- > vos propres dépendances, utilisez `inject()`.
1000
+ > 💡 No more `constructor`: we use **inject**. The dependencies of `AbstractPageComponent`
1001
+ > (including `_dictionaryService` and `_gesdemService`) are already injected in the abstract class. For
1002
+ > your own dependencies, use `inject()`.
942
1003
 
943
1004
  ```ts
944
1005
  @Component({
945
- templateUrl: './votre-page.component.html',
1006
+ templateUrl: './your-page.component.html',
946
1007
  imports: [
947
- // ... vos composants foehn-*
1008
+ // ... your foehn-* components
948
1009
  ],
949
1010
  })
950
- export class VotrePageComponent
1011
+ export class YourPageComponent
951
1012
  extends AbstractPageComponent<BusinessForm>
952
1013
  implements OnInit
953
1014
  {
954
- // vos propres dépendances : inject() au lieu du constructor
1015
+ // your own dependencies: inject() instead of the constructor
955
1016
  private readonly metaService = inject(MetaService);
956
1017
 
957
1018
  ngOnInit(): void {
958
1019
  super.ngOnInit();
959
1020
  this.setPageTitle(
960
- this._dictionaryService.getKeySync('votre-page.title'),
1021
+ this._dictionaryService.getKeySync('your-page.title'),
961
1022
  );
962
1023
  }
963
1024
 
@@ -976,12 +1037,12 @@ export class VotrePageComponent
976
1037
  }
977
1038
  ```
978
1039
 
979
- Il ne tient qu'à vous de décider comment appeler cette méthode lors de la suppression de l'élément de
980
- votre liste.
1040
+ It's up to you to decide how to call this method when the item is removed from
1041
+ your list.
981
1042
 
982
- Voici une manière élégante de gérer vos listes d'éléments :
1043
+ Here is an elegant way to handle your lists of items:
983
1044
 
984
- - Structure des dossiers et fichiers
1045
+ - Structure of the folders and files
985
1046
 
986
1047
  ```text
987
1048
  demande-page/
@@ -998,7 +1059,7 @@ demande-page/
998
1059
 
999
1060
  **vehicule-item**
1000
1061
 
1001
- `vehicule-item.component.html` contient le bouton pour la suppression de l'élément dans la liste (1).
1062
+ `vehicule-item.component.html` contains the button removing the item from the list (1).
1002
1063
 
1003
1064
  ```html
1004
1065
  <div
@@ -1011,7 +1072,7 @@ demande-page/
1011
1072
  <foehn-validation-alerts [component]="this" />
1012
1073
 
1013
1074
  <div class="row d-flex align-items-end vehicule-infos">
1014
- <!-- ... vos composants foehn-input-* -->
1075
+ <!-- ... your foehn-input-* components -->
1015
1076
 
1016
1077
  <div class="col-md-2 mt-md-0 mt-xs-2">
1017
1078
  <button class="btn btn-danger" (click)="removeVehicule()">
@@ -1023,10 +1084,10 @@ demande-page/
1023
1084
  </div>
1024
1085
  ```
1025
1086
 
1026
- `vehicule-item.component.ts` contient la fonction de suppression qui va remonter l'événement à son
1027
- parent (la liste des éléments).
1087
+ `vehicule-item.component.ts` contains the removal function, which sends the event up to its
1088
+ parent (the list of items).
1028
1089
 
1029
- > 💡 `@Output() + EventEmitter` sont remplacés par la fonction `output()`.
1090
+ > 💡 `@Output() + EventEmitter` are replaced by the `output()` function.
1030
1091
 
1031
1092
  ```ts
1032
1093
  @Component({
@@ -1040,7 +1101,7 @@ parent (la liste des éléments).
1040
1101
  ],
1041
1102
  imports: [
1042
1103
  FoehnValidationAlertsComponent,
1043
- // ... vos foehn-input-*
1104
+ // ... your foehn-input-*
1044
1105
  SdkDictionaryPipe,
1045
1106
  ],
1046
1107
  })
@@ -1057,11 +1118,11 @@ export class VehiculeItemComponent extends FoehnInputComponent<Vehicule> {
1057
1118
 
1058
1119
  **vehicules-list**
1059
1120
 
1060
- `vehicules-list.component.html` contient votre boucle `@for` (1) et votre composant répétable (2) qui
1061
- devra lui aussi remonter l'événement.
1121
+ `vehicules-list.component.html` contains your `@for` loop (1) and your repeatable component (2), which
1122
+ must also send the event up.
1062
1123
 
1063
- > ⚠️ Dans la boucle `@for` ne pas oublier de mettre un `track` (identifiant unique) sinon la
1064
- > validation ne s'affichera pas. Ici nous trackons par exemple avec `vehicule.id`.
1124
+ > ⚠️ In the `@for` loop, don't forget to put a `track` (unique identifier), otherwise the
1125
+ > validation won't be displayed. Here, for example, we track with `vehicule.id`.
1065
1126
 
1066
1127
  ```html
1067
1128
  <div
@@ -1094,9 +1155,9 @@ devra lui aussi remonter l'événement.
1094
1155
  ></small>
1095
1156
  }
1096
1157
 
1097
- <!-- (1) votre boucle @for + track (identifiant unique) -->
1158
+ <!-- (1) your @for loop + track (unique identifier) -->
1098
1159
  @for (vehicule of vehicules(); track vehicule.id; let i = $index) {
1099
- <!-- (2) votre composant réutilisable -->
1160
+ <!-- (2) your reusable component -->
1100
1161
  <app-vehicule-item
1101
1162
  [name]="name() + '[' + i + ']'"
1102
1163
  [model]="vehicule"
@@ -1112,15 +1173,15 @@ devra lui aussi remonter l'événement.
1112
1173
  </div>
1113
1174
  ```
1114
1175
 
1115
- > 💡 Le `name` est un input signal : `[name]="name() + '[' + i + ']'"` (et non plus
1176
+ > 💡 The `name` is an input signal: `[name]="name() + '[' + i + ']'"` (and no longer
1116
1177
  > `[name]="name + '[' + i + ']'"`).
1117
1178
  >
1118
- > 💡 La variable d'une boucle `@for` ne peut pas être modifiée : on passe l'élément avec `[model]` et
1119
- > on remplace l'élément de la liste dans `updateVehiculesList()` (appelée par `(modelChange)`).
1179
+ > 💡 The variable of a `@for` loop can't be modified: the item is passed with `[model]`, and
1180
+ > the item of the list is replaced in `updateVehiculesList()` (called by `(modelChange)`).
1120
1181
 
1121
- `vehicules-list.component.ts` remonte à son tour l'événement (1) au parent (`AbstractPageComponent`)
1122
- ainsi que supprimer l'élément de la liste (2). C'est aussi ici que vous pouvez gérer l'ajout d'un
1123
- élément (3).
1182
+ `vehicules-list.component.ts` in turn sends the event (1) up to the parent (`AbstractPageComponent`)
1183
+ and removes the item from the list (2). This is also where you can handle the addition of an
1184
+ item (3).
1124
1185
 
1125
1186
  ```ts
1126
1187
  @Component({
@@ -1139,25 +1200,25 @@ ainsi que supprimer l'élément de la liste (2). C'est aussi ici que vous pouvez
1139
1200
  ],
1140
1201
  })
1141
1202
  export class VehiculesListComponent extends FoehnInputComponent<Vehicule[]> {
1142
- readonly removedVehicule = output<void>(); // (1) événement à remonter au parent
1203
+ readonly removedVehicule = output<void>(); // (1) event to send up to the parent
1143
1204
 
1144
- // Signal contenant vos éléments répétables : il est remplacé, jamais modifié sur place
1205
+ // Signal holding your repeatable items: it is replaced, never modified in place
1145
1206
  readonly vehicules = signal<Vehicule[]>([]);
1146
1207
 
1147
1208
  updateVehiculesList(vehicule: Vehicule, index: number): void {
1148
1209
  this.vehicules.update((vehicules) =>
1149
1210
  vehicules.map((item, i) => (i === index ? vehicule : item)),
1150
1211
  );
1151
- this.getValidValueAndUpdateNgModel(); // Mise à jour de votre modèle
1212
+ this.getValidValueAndUpdateNgModel(); // Update of your model
1152
1213
  }
1153
1214
 
1154
1215
  handleRemovedVehicule(index: number): void {
1155
- // (2) votre fonction de suppression de l'élément ainsi que la transmission de l'évènement
1216
+ // (2) your function removing the item and sending the event
1156
1217
  this.vehicules.update((vehicules) =>
1157
1218
  vehicules.filter((_vehicule, i) => i !== index),
1158
- ); // suppression de l'élément
1159
- this.getValidValueAndUpdateNgModel(); // Mise à jour de votre modèle
1160
- this.removedVehicule.emit(); // (1) on remonte l'évènement
1219
+ ); // removal of the item
1220
+ this.getValidValueAndUpdateNgModel(); // Update of your model
1221
+ this.removedVehicule.emit(); // (1) the event is sent up
1161
1222
  }
1162
1223
 
1163
1224
  override onModelChange(value: Vehicule[] | null): void {
@@ -1165,19 +1226,19 @@ export class VehiculesListComponent extends FoehnInputComponent<Vehicule[]> {
1165
1226
  }
1166
1227
 
1167
1228
  addVehicule(): void {
1168
- // (3) Gestion de l'ajout d'un élément
1229
+ // (3) Handling of the addition of an item
1169
1230
  // ...code
1170
1231
  }
1171
1232
  }
1172
1233
  ```
1173
1234
 
1174
- > 💡 Plus besoin de définir une `trackByFn` dédiée : la nouvelle syntaxe `@for` utilise directement
1175
- > l'expression `track` (ici `vehicule.id`).
1235
+ > 💡 No need to define a dedicated `trackByFn` anymore: the new `@for` syntax directly uses
1236
+ > the `track` expression (here `vehicule.id`).
1176
1237
 
1177
1238
  **demande-page**
1178
1239
 
1179
- `demande-page.component.html` utilisera votre composant de liste de véhicule (1) et écoutera pour
1180
- l'événement de suppression (2).
1240
+ `demande-page.component.html` uses your vehicle list component (1) and listens to
1241
+ the removal event (2).
1181
1242
 
1182
1243
  ```html
1183
1244
  <foehn-form>
@@ -1191,7 +1252,7 @@ l'événement de suppression (2).
1191
1252
  [required]="true"
1192
1253
  (removedVehicule)="handleRemovedVehicule()"
1193
1254
  />
1194
- <!-- (1) Utilisation de votre liste - (2) Appel de la méthode qui sauvegardera les données dans GESDEM afin de rafraîchir les erreurs -->
1255
+ <!-- (1) Use of your list - (2) Call of the method saving the data in GESDEM to refresh the errors -->
1195
1256
 
1196
1257
  <foehn-navigation
1197
1258
  id="navigation"
@@ -1201,8 +1262,8 @@ l'événement de suppression (2).
1201
1262
  </foehn-form>
1202
1263
  ```
1203
1264
 
1204
- `demande-page.component.ts` se chargera de l'appel à GESDEM afin de re-générer les erreurs du
1205
- formulaire et ainsi rafraîchir l'index.
1265
+ `demande-page.component.ts` calls GESDEM to generate the errors of the
1266
+ form again, and thus refresh the index.
1206
1267
 
1207
1268
  ```ts
1208
1269
  @Component({
@@ -1224,20 +1285,73 @@ export class DemandePageComponent
1224
1285
  }
1225
1286
  ```
1226
1287
 
1227
- ## Validation du formulaire
1288
+ ## Types of the form fields
1289
+
1290
+ > ℹ️ `AbstractPageComponent` replaces `form` with the JSON sent by the backend, as is: at runtime, a field has
1291
+ > the type of the JSON written from the Java model, whatever its TypeScript type says.
1292
+
1293
+ Give the Java field the type matching the model of the component:
1294
+
1295
+ | Component | TypeScript model | Java field |
1296
+ | ------------------------------------------------------------------ | -------------------- | -------------------------------------- |
1297
+ | `foehn-input-text`, `foehn-input-textarea`, `foehn-input-email`... | `string` | `String` |
1298
+ | `foehn-input-time` | `string` | `String` with `@ValidStringTime` |
1299
+ | `foehn-input-date` | `(number \| null)[]` | `LenientLocalDate` |
1300
+ | `foehn-input-date-time` | `(number \| null)[]` | `LenientLocalDateTime` |
1301
+ | `foehn-boolean-checkbox`, `foehn-boolean-radio` | `boolean` | `Boolean` |
1302
+ | `foehn-input-number` | `string` | `Integer`, `BigDecimal`... (see below) |
1303
+
1304
+ > ⚠️ The model of `foehn-input-number` is a `string`: the component keeps the input as typed (decimal comma,
1305
+ > leading zeros, number of decimals). But the Java field is a number (`Integer`, `BigDecimal`), which is
1306
+ > written as a number in the JSON. The field holds `'0'` after the input of the user, and `0` once the form
1307
+ > comes back from the backend. The same happens with any component whose model is a `string` bound to a Java
1308
+ > field which isn't a `String`.
1309
+
1310
+ Never test such a field by its truthiness (`!!value`, `if (value)`, `value || ...`): `'0'` is true, `0` is
1311
+ false.
1312
+
1313
+ What **not** to do:
1314
+
1315
+ ```ts
1316
+ isAmountFilled(): boolean {
1317
+ // true after the input of '0', false once the form is loaded (0)
1318
+ return !!this.form?.amount;
1319
+ }
1320
+ ```
1321
+
1322
+ What **to** do:
1323
+
1324
+ ```ts
1325
+ isAmountFilled(): boolean {
1326
+ const amount = this.form?.amount;
1327
+ return amount !== null && amount !== undefined && amount !== '';
1328
+ }
1329
+
1330
+ getTotal(): number {
1331
+ // Number() converts '12.50' as well as 12.5
1332
+ return Number(this.form?.amount ?? 0) + Number(this.form?.fees ?? 0);
1333
+ }
1334
+ ```
1335
+
1336
+ > ⚠️ The values of the elements of `foehn-select` and `foehn-checkbox` must have the type of the Java field:
1337
+ > they are compared to the model with `===`. With `'1'` as value of an element and `1` sent by the backend
1338
+ > (`Integer`), no element is selected anymore once the form is loaded, and the component warns that the saved
1339
+ > value no longer exists in the list.
1340
+
1341
+ ## Form validation
1228
1342
 
1229
- > ℹ️ Lors de la sauvegarde de votre formulaire dans GESDEM, l'`AbstractPageComponent` gère pour vous
1230
- > l'affichage éventuel des erreurs.
1343
+ > ℹ️ When your form is saved in GESDEM, the `AbstractPageComponent` handles the
1344
+ > display of the errors for you.
1231
1345
  >
1232
- > Si par contre vous souhaitez sauvegarder les données ailleurs que dans GESDEM, vous serez confronté
1233
- > à la gestion manuelle de l'affichage des erreurs.
1346
+ > If, however, you want to save the data somewhere else than in GESDEM, you will have to handle
1347
+ > the display of the errors manually.
1234
1348
 
1235
- ### Gestion standard
1349
+ ### Standard handling
1236
1350
 
1237
- > ℹ️ Lors de l'enregistrement de vos données dans GESDEM, l'affichage des erreurs est automatiquement
1238
- > géré pour vous. Cependant, vous devez impérativement respecter certaines règles.
1351
+ > ℹ️ When your data is saved in GESDEM, the display of the errors is handled automatically
1352
+ > for you. However, you must respect some rules.
1239
1353
 
1240
- Prenons un exemple de modèle simple côté front à deux niveaux de profondeur :
1354
+ Let's take an example of a simple front-end model with two levels of depth:
1241
1355
 
1242
1356
  `identification.ts`
1243
1357
 
@@ -1257,8 +1371,8 @@ export class BusinessForm {
1257
1371
  }
1258
1372
  ```
1259
1373
 
1260
- > ⚠️ Le modèle ci-dessus devra impérativement être reflété dans le `name` de vos composants
1261
- > `foehn-input-*` de votre page.
1374
+ > ⚠️ The model above must be reflected in the `name` of the `foehn-input-*`
1375
+ > components of your page.
1262
1376
 
1263
1377
  `page-1.html`
1264
1378
 
@@ -1267,7 +1381,7 @@ export class BusinessForm {
1267
1381
  [(model)]="form.identification.firstName"
1268
1382
  name="identification.firstName"
1269
1383
  />
1270
- <!-- 2 niveaux de profondeur -->
1384
+ <!-- 2 levels of depth -->
1271
1385
 
1272
1386
  <foehn-input-text
1273
1387
  [(model)]="form.identification.lastName"
@@ -1275,26 +1389,26 @@ export class BusinessForm {
1275
1389
  />
1276
1390
 
1277
1391
  <foehn-boolean-checkbox [(model)]="form.acceptCookies" name="acceptCookies" />
1278
- <!-- 1 niveau de profondeur -->
1392
+ <!-- 1 level of depth -->
1279
1393
  ```
1280
1394
 
1281
- - Côté back-end, vous pouvez gérer vos erreurs grâce aux annotations (`@NotEmpty`, `@NotNull`, ...),
1282
- et **prestations-be** fera le reste pour vous.
1283
- - Si vous souhaitez remonter une erreur spécifique à un champ de votre formulaire, voici la syntaxe :
1395
+ - On the back-end side, you can handle your errors with the annotations (`@NotEmpty`, `@NotNull`, ...),
1396
+ and **prestations-be** does the rest for you.
1397
+ - If you want to return an error specific to a field of your form, here is the syntax:
1284
1398
 
1285
1399
  ```java
1286
- new AbstractSdkController.FormError("identification.lastName", "ErrorCodeHere", "Le nom est obligatoire si vous ne spécifiez pas de prénom")
1400
+ new AbstractSdkController.FormError("identification.lastName", "ErrorCodeHere", "The last name is required if you don't specify a first name")
1287
1401
  ```
1288
1402
 
1289
- > ⚠️ Le nom de l'erreur doit impérativement correspondre au nom de votre composant dans votre page
1290
- > HTML.
1403
+ > ⚠️ The name of the error must match the name of your component in your HTML
1404
+ > page.
1291
1405
 
1292
- ### Gestion manuelle
1406
+ ### Manual handling
1293
1407
 
1294
1408
  #### Back-end
1295
1409
 
1296
- Côté back-end JAVA, le serveur devra retourner un objet contenant les erreurs du formulaire afin
1297
- d'être compatible avec prestations-ng.
1410
+ On the JAVA back-end side, the server must return an object containing the errors of the form to
1411
+ be compatible with prestations-ng.
1298
1412
 
1299
1413
  ```java
1300
1414
  @Getter
@@ -1308,23 +1422,23 @@ public class PostResponse<T> {
1308
1422
 
1309
1423
  @Builder.Default
1310
1424
  private List<AbstractSdkController.FormError> errors = new ArrayList<>();
1311
- // AbstractSdkController.FormError vient de prestations-be qui est la partie backend de prestations-ng
1425
+ // AbstractSdkController.FormError comes from prestations-be, the backend part of prestations-ng
1312
1426
  }
1313
1427
  ```
1314
1428
 
1315
- Ensuite, ajoutez vos erreurs dans votre modèle avant de le retourner au front-end :
1429
+ Then, add your errors to your model before returning it to the front-end:
1316
1430
 
1317
1431
  ```java
1318
1432
  List<AbstractSdkController.FormError> errors = new ArrayList<>();
1319
- errors.add(new AbstractSdkController.FormError("identification.lastName", "ErrorCodeHere", "Le nom est obligatoire si vous ne spécifiez pas de prénom"));
1433
+ errors.add(new AbstractSdkController.FormError("identification.lastName", "ErrorCodeHere", "The last name is required if you don't specify a first name"));
1320
1434
 
1321
1435
  PostResponse.<MyObject>builder().dto(myObject).errors(errors).build();
1322
1436
  ```
1323
1437
 
1324
1438
  #### Front-end
1325
1439
 
1326
- Côté front-end, comme la validation standard, les noms de vos composants `foehn-input-*` de votre
1327
- page HTML et le nom de vos erreurs devront correspondre.
1440
+ On the front-end side, as for the standard validation, the names of the `foehn-input-*` components of your
1441
+ HTML page and the names of your errors must match.
1328
1442
 
1329
1443
  ```html
1330
1444
  <foehn-input-text
@@ -1333,15 +1447,15 @@ page HTML et le nom de vos erreurs devront correspondre.
1333
1447
  />
1334
1448
  ```
1335
1449
 
1336
- > ⚠️ Au retour de la sauvegarde de vos données, vous allez devoir déclencher manuellement la
1337
- > validation.
1450
+ > ⚠️ When your data has been saved, you will have to trigger the validation
1451
+ > manually.
1338
1452
 
1339
- 1. Dans votre `page.ts`, créez une fonction `handleValidationErrors` pour gérer l'affichage des
1340
- erreurs.
1341
- 2. Cette fonction sera appelée au retour du back-end.
1453
+ 1. In your `page.ts`, create a `handleValidationErrors` function to handle the display of the
1454
+ errors.
1455
+ 2. This function is called when the back-end answers.
1342
1456
 
1343
- > 💡 Point **signals** : plus de `@ViewChild('formWithModels')`. Pour utiliser un composant dans le
1344
- > TypeScript, on utilise un **signal** `viewChild(...)`. On accède ensuite à l'instance en l'invoquant :
1457
+ > 💡 **signals** point: no more `@ViewChild('formWithModels')`. To use a component in the
1458
+ > TypeScript, use a `viewChild(...)` **signal**. Then access the instance by calling it:
1345
1459
  > `this.formWithModels()`.
1346
1460
 
1347
1461
  `page.ts`
@@ -1358,41 +1472,41 @@ import {
1358
1472
  // ...
1359
1473
  })
1360
1474
  export class ... extends AbstractPageComponent<YOUR_MODEL> implements OnInit {
1361
- // point (7 des signaux) : un signal viewChild au lieu de @ViewChild
1475
+ // a viewChild signal instead of @ViewChild
1362
1476
  readonly formWithModels = viewChild(FoehnFormComponent);
1363
1477
 
1364
- // vos propres dépendances via inject()
1478
+ // your own dependencies with inject()
1365
1479
  private readonly httpClient = inject(HttpClient);
1366
1480
 
1367
1481
  ngOnInit(): void {
1368
1482
  super.ngOnInit();
1369
- // Activer l'affichage des erreurs
1483
+ // Enable the display of the errors
1370
1484
  this._validationHandlerService.shouldDisplayErrors(true);
1371
1485
  }
1372
1486
 
1373
- // Fonction de sauvegarde
1487
+ // Save function
1374
1488
  save(): void {
1375
- // Activer l'affichage des erreurs
1489
+ // Enable the display of the errors
1376
1490
  this._validationHandlerService.shouldDisplayErrors(true);
1377
- // Appel au back pour faire votre validation. Le callback ne modifie aucun champ de la page :
1378
- // les services de prestations-ng (growl, erreurs) rafraîchissent eux-mêmes l'affichage, aussi
1379
- // sans zone.js. Un état propre à la page modifié ici devrait être un signal.
1491
+ // Call to the back-end to do your validation. The callback modifies no field of the page:
1492
+ // the services of prestations-ng (growl, errors) refresh the display themselves, also
1493
+ // without zone.js. A state of the page modified here should be a signal.
1380
1494
  this.httpClient
1381
- .post<PostResponse>('api_url_ici', votre_objet)
1495
+ .post<PostResponse>('api_url_here', your_object)
1382
1496
  .subscribe((response: PostResponse) => {
1383
1497
  if (!response.errors.length) {
1384
- // On prévient l'utilisateur que tout s'est bien passé
1498
+ // Tell the user that everything went well
1385
1499
  this._growlService.addWithType(
1386
1500
  GrowlType.SUCCESS,
1387
- 'Enregistrement effectué avec succès',
1501
+ 'Saved successfully',
1388
1502
  );
1389
1503
  }
1390
- // Gestion des erreurs au retour du back-end
1504
+ // Handling of the errors returned by the back-end
1391
1505
  this.handleValidationErrors(response.errors); // point (2)
1392
1506
  });
1393
1507
  }
1394
1508
 
1395
- // Affichage des erreurs
1509
+ // Display of the errors
1396
1510
  private handleValidationErrors(errors: ErrorTemplate[]): void {
1397
1511
  // point (1)
1398
1512
  const form = this.formWithModels();
@@ -1410,21 +1524,21 @@ export class ... extends AbstractPageComponent<YOUR_MODEL> implements OnInit {
1410
1524
 
1411
1525
  ```html
1412
1526
  <foehn-form>
1413
- <!-- ... vos composants foehn-input-* -->
1527
+ <!-- ... your foehn-input-* components -->
1414
1528
  </foehn-form>
1415
1529
  ```
1416
1530
 
1417
- ## Validation manuelle d'un composant
1531
+ ## Manual validation of a component
1418
1532
 
1419
- ### Cacher une erreur
1533
+ ### Hiding an error
1420
1534
 
1421
- > ✅ Utiliser la fonction `markAsDirty()` permet de simuler une interaction avec le composant.
1535
+ > ✅ The `markAsDirty()` function simulates an interaction with the component.
1422
1536
  >
1423
- > ℹ️ Permet de cacher l'erreur d'un composant foehn-input en erreur.
1537
+ > ℹ️ Hides the error of a foehn-input component in error.
1424
1538
 
1425
- ### Afficher une erreur
1539
+ ### Displaying an error
1426
1540
 
1427
- > ✅ Utiliser la fonction `markAsPristine()` remet le composant dans son état d'origine comme si
1428
- > aucune interaction n'avait été effectuée.
1541
+ > ✅ The `markAsPristine()` function puts the component back in its original state, as if
1542
+ > no interaction had happened.
1429
1543
  >
1430
- > ℹ️ Permet d'afficher l'erreur d'un composant foehn-input en erreur.
1544
+ > ℹ️ Displays the error of a foehn-input component in error.