From 27d0fa055a4ba2d138c344204b561587d07ee77e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr>
Date: Thu, 19 May 2022 09:25:04 +0200
Subject: [PATCH 01/11] fix: emit validity signal only on validity change

refs #544
---
 .../field-set/field-set.component.ts          | 15 ++++++++++----
 .../fieldset-container.component.ts           | 20 ++++++++++++++-----
 .../pab-table/pab-table.component.ts          | 17 ++++++++++++----
 .../pb-schema/pb-schema.component.ts          |  7 ++++++-
 4 files changed, 45 insertions(+), 14 deletions(-)

diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts
index d4a614ab8..584f42efa 100644
--- a/src/app/components/field-set/field-set.component.ts
+++ b/src/app/components/field-set/field-set.component.ts
@@ -223,8 +223,7 @@ export class FieldSetComponent implements DoCheck {
      * calcul de la validité de tous les ParamFieldLineComponent et tous les
      * SelectFieldLineComponent de la vue
      */
-    private updateValidity() {
-        this._isValid = false;
+    private computeValidity(): boolean {
         let paramsAreValid = true;
         let selectAreValid = true;
 
@@ -261,9 +260,17 @@ export class FieldSetComponent implements DoCheck {
         }
 
         // global validity
-        this._isValid = (paramsAreValid && selectAreValid);
+        return (paramsAreValid && selectAreValid);
+    }
+
+    private updateValidity() {
+        const oldValidity = this._isValid;
 
-        this.validChange.emit();
+        // global validity
+        this._isValid = this.computeValidity();
+        if (this._isValid !== oldValidity) {
+            this.validChange.emit();
+        }
     }
 
     /**
diff --git a/src/app/components/fieldset-container/fieldset-container.component.ts b/src/app/components/fieldset-container/fieldset-container.component.ts
index 630da2d19..4d60303d1 100644
--- a/src/app/components/fieldset-container/fieldset-container.component.ts
+++ b/src/app/components/fieldset-container/fieldset-container.component.ts
@@ -111,11 +111,11 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
     /**
     * calcul de la validité de tous les FieldSet de la vue
     */
-    private updateValidity() {
-        this._isValid = false;
+    private computeValidity(): boolean {
+        let res = false;
 
         if (this._fieldsetComponents?.length > 0) {
-            this._isValid = this._fieldsetComponents.reduce(
+            res = this._fieldsetComponents.reduce(
                 // callback
                 (
                     // accumulator (valeur précédente du résultat)
@@ -133,10 +133,20 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
                 , this._fieldsetComponents.length > 0);
         } else {
             // empty / hidden container ? everything OK.
-            this._isValid = true;
+            res = true;
         }
 
-        this.validChange.emit();
+        return res;
+    }
+
+    private updateValidity() {
+        const oldValidity = this._isValid;
+
+        // global validity
+        this._isValid = this.computeValidity();
+        if (this._isValid !== oldValidity) {
+            this.validChange.emit();
+        }
     }
 
     /**
diff --git a/src/app/components/pab-table/pab-table.component.ts b/src/app/components/pab-table/pab-table.component.ts
index 486789521..597d28f3a 100644
--- a/src/app/components/pab-table/pab-table.component.ts
+++ b/src/app/components/pab-table/pab-table.component.ts
@@ -1440,14 +1440,23 @@ export class PabTableComponent implements AfterViewInit, AfterViewChecked, OnIni
     /**
      * Computes the global Pab validity : validity of every cell of every row
      */
-    private updateValidity() {
-        this._isValid = true;
+    private computeValidity(): boolean {
+        let res = true;
         for (const r of this.rows) {
             for (const c of r.cells) {
-                this._isValid = this._isValid && ! this.isInvalid(c);
+                res = res && !this.isInvalid(c);
             }
         }
-        this.validChange.emit();
+
+        return res;
+    }
+
+    private updateValidity() {
+        const oldValidity = this._isValid;
+        this._isValid = this.computeValidity();
+        if (this._isValid !== oldValidity) {
+            this.validChange.emit();
+        }
     }
 
     public get uitextEditPabTable() {
diff --git a/src/app/components/pb-schema/pb-schema.component.ts b/src/app/components/pb-schema/pb-schema.component.ts
index 7ea4ba81d..bfc497888 100644
--- a/src/app/components/pb-schema/pb-schema.component.ts
+++ b/src/app/components/pb-schema/pb-schema.component.ts
@@ -641,13 +641,18 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni
      * Computes the global Pab validity : validity of every cell of every row
      */
     private updateValidity() {
+        const oldValidity = this._isValid;
+
         // check that at least 1 basin is present and a route from river
         // upstream to river downstream exists (2nd check includes 1st)
         this._isValid = (
             this.model.hasUpDownConnection()
             && ! this.model.hasBasinNotConnected()
         );
-        this.validChange.emit();
+
+        if (this._isValid !== oldValidity) {
+            this.validChange.emit();
+        }
     }
 
     private clearHighlightedItems() {
-- 
GitLab


From 3761c39a428e1f2a9e3fc87d63dafdcfa5634c41 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr>
Date: Thu, 19 May 2022 10:49:03 +0200
Subject: [PATCH 02/11] refactor: remove unused validity signals

refs #544
---
 .../param-field-line/param-field-line.component.html      | 6 ++----
 .../param-field-line/param-field-line.component.ts        | 8 --------
 src/app/components/param-link/param-link.component.ts     | 4 ----
 3 files changed, 2 insertions(+), 16 deletions(-)

diff --git a/src/app/components/param-field-line/param-field-line.component.html b/src/app/components/param-field-line/param-field-line.component.html
index daad0ba4b..a9114f5f2 100644
--- a/src/app/components/param-field-line/param-field-line.component.html
+++ b/src/app/components/param-field-line/param-field-line.component.html
@@ -12,13 +12,11 @@
         </param-computed>
 
         <!-- composant pour gérer le cas "paramètre à varier" (min-max/liste de valeurs) -->
-        <param-values *ngIf="isRadioVarChecked" [title]="title" [param]="param" (change)="onInputChange($event)"
-            (valid)=onParamValuesValid($event)>
+        <param-values *ngIf="isRadioVarChecked" [title]="title" [param]="param" (change)="onInputChange($event)">
         </param-values>
 
         <!-- composant pour gérer le cas "paramètre lié" -->
-        <param-link *ngIf="isRadioLinkChecked" [title]="title" [param]="param" (change)="onInputChange($event)"
-            (valid)=onParamValuesValid($event)>
+        <param-link *ngIf="isRadioLinkChecked" [title]="title" [param]="param" (change)="onInputChange($event)">
         </param-link>
     </div>
 
diff --git a/src/app/components/param-field-line/param-field-line.component.ts b/src/app/components/param-field-line/param-field-line.component.ts
index 1cb5750c7..64caf333a 100644
--- a/src/app/components/param-field-line/param-field-line.component.ts
+++ b/src/app/components/param-field-line/param-field-line.component.ts
@@ -312,14 +312,6 @@ export class ParamFieldLineComponent implements OnChanges {
         }
     }
 
-    /**
-     * réception d'un événement de validité de ParamValuesComponent
-     */
-    public onParamValuesValid(event: boolean) {
-        this._isRangeValid = event;
-        this.emitValidity();
-    }
-
     public ngOnChanges() {
         this._ngParamInputComponent.model = this.param;
         this._ngParamInputComponent.showError = this.isRadioFixChecked;
diff --git a/src/app/components/param-link/param-link.component.ts b/src/app/components/param-link/param-link.component.ts
index 89255ef13..2571d9ec7 100644
--- a/src/app/components/param-link/param-link.component.ts
+++ b/src/app/components/param-link/param-link.component.ts
@@ -22,9 +22,6 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
     @Input()
     public title: string;
 
-    @Output()
-    public valid: EventEmitter<boolean>;
-
     /**
      * événement signalant un changement de valeur du modèle
      * @TODO l'utiliser aussi pour le changement de validité à
@@ -83,7 +80,6 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
         private intlService: I18nService,
         private formService: FormulaireService
     ) {
-        this.valid = new EventEmitter();
         this.formService.addObserver(this);
     }
 
-- 
GitLab


From 8c4e4132d5ecb7e0e167880ff33713cd618837cf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr>
Date: Thu, 19 May 2022 16:16:32 +0200
Subject: [PATCH 03/11] fix: SVG schema nodes not instantly displayed on error
 in case of invalid input in a fieldset

refs #544
---
 .../calculator.component.ts                   |  3 +++
 .../generic-input/generic-input.component.ts  |  4 +--
 .../ngparam-input/ngparam-input.component.ts  | 26 ++++++++++++++++++-
 .../pb-schema/pb-schema.component.ts          |  7 +++++
 4 files changed, 37 insertions(+), 3 deletions(-)

diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 9650543c6..859463c39 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -526,6 +526,9 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
                 this._isUIValid = this._isUIValid && form.checkParameters().length === 0;
             }
         }
+
+        // update prébarrage schema validity
+            this._pbSchemaComponent.updateItemsValidity();
     }
 
     public getElementStyleDisplay(id: string) {
diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts
index 3dc390d50..444127f7f 100644
--- a/src/app/components/generic-input/generic-input.component.ts
+++ b/src/app/components/generic-input/generic-input.component.ts
@@ -131,7 +131,7 @@ export abstract class GenericInputComponentDirective implements OnChanges {
         return this._isValidUI && this._isValidModel;
     }
 
-    private setUIValid(b: boolean) {
+    protected setUIValid(b: boolean) {
         const old = this.isValid;
         this._isValidUI = b;
         if (this.isValid !== old) {
@@ -147,7 +147,7 @@ export abstract class GenericInputComponentDirective implements OnChanges {
         return isValid;
     }
 
-    private setModelValid(b: boolean) {
+    protected setModelValid(b: boolean) {
         const old = this.isValid;
         this._isValidModel = b;
         if (this.isValid !== old) {
diff --git a/src/app/components/ngparam-input/ngparam-input.component.ts b/src/app/components/ngparam-input/ngparam-input.component.ts
index 2449f691e..581886d3a 100644
--- a/src/app/components/ngparam-input/ngparam-input.component.ts
+++ b/src/app/components/ngparam-input/ngparam-input.component.ts
@@ -2,7 +2,7 @@
 
 import { Component, ChangeDetectorRef, OnDestroy, Input, ElementRef } from "@angular/core";
 
-import { Message, Observer } from "jalhyd";
+import { Message, MessageCode, Observer } from "jalhyd";
 
 import { I18nService } from "../../services/internationalisation.service";
 import { NgParameter } from "../../formulaire/elements/ngparam";
@@ -99,6 +99,9 @@ export class NgParamInputComponent extends GenericInputComponentDirective implem
             msg = "internal error, model undefined";
         } else {
             try {
+                if (!this._paramDef.allowEmpty && v === undefined) { // from nghyd#501 commit 425ae8fa
+                    throw new Message(MessageCode.ERROR_PARAMDEF_VALUE_UNDEFINED);
+                }
                 this._paramDef.checkValue(v);
                 valid = true;
             } catch (e) {
@@ -113,6 +116,26 @@ export class NgParamInputComponent extends GenericInputComponentDirective implem
         return { isValid: valid, message: msg };
     }
 
+    private undefineModel() {
+        if (this.getModelValue() !== undefined) {
+            this.setModelValue(this, undefined);
+        }
+    }
+
+    protected setModelValid(b: boolean) {
+        if (!b) {
+            this.undefineModel();
+        }
+        super.setModelValid(b);
+    }
+
+    protected setUIValid(b: boolean) {
+        if (!b) {
+            this.undefineModel();
+        }
+        super.setUIValid(b);
+    }
+
     public update(sender: any, data: any): void {
         switch (data["action"]) {
             case "ngparamAfterValue":
@@ -148,6 +171,7 @@ export class NgParamInputComponent extends GenericInputComponentDirective implem
     }
 
     public ngOnDestroy() {
+        // résoudre le conflit en supprimant le code ajouté cad ne conserver que removeObserver()
         this._paramDef.removeObserver(this);
     }
 }
diff --git a/src/app/components/pb-schema/pb-schema.component.ts b/src/app/components/pb-schema/pb-schema.component.ts
index bfc497888..425b1ba88 100644
--- a/src/app/components/pb-schema/pb-schema.component.ts
+++ b/src/app/components/pb-schema/pb-schema.component.ts
@@ -655,6 +655,13 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni
         }
     }
 
+    /**
+     * update all items validity rendering
+     */
+    public updateItemsValidity() {
+        this.highlightErrorItems(this._selectedItem?.uid);
+    }
+
     private clearHighlightedItems() {
         this.nativeElement.querySelectorAll("g.node").forEach(item => {
             item.classList.remove("node-highlighted");
-- 
GitLab


From 04a7f46b7244661d2dd9962b73490baa0b98a9f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr>
Date: Fri, 20 May 2022 08:14:08 +0200
Subject: [PATCH 04/11] refactor: use DefinedBoolean to manage form validity
 flag

refs #544
---
 .../field-set/field-set.component.ts          | 15 +++---
 .../fieldset-container.component.ts           | 15 +++---
 .../calculator.component.ts                   | 24 ++++++----
 .../generic-input/generic-input.component.ts  | 32 +++++++------
 .../pab-table/pab-table.component.ts          | 11 +++--
 .../pb-schema/pb-schema.component.ts          | 15 +++---
 src/app/definedvalue/definedboolean.ts        |  7 +++
 src/app/definedvalue/definedvalue.ts          | 46 +++++++++++++++++++
 8 files changed, 115 insertions(+), 50 deletions(-)
 create mode 100644 src/app/definedvalue/definedboolean.ts
 create mode 100644 src/app/definedvalue/definedvalue.ts

diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts
index 584f42efa..36d2701bf 100644
--- a/src/app/components/field-set/field-set.component.ts
+++ b/src/app/components/field-set/field-set.component.ts
@@ -16,6 +16,7 @@ import { I18nService } from "../../services/internationalisation.service";
 import { sprintf } from "sprintf-js";
 
 import { capitalize } from "jalhyd";
+import { DefinedBoolean } from "app/definedvalue/definedboolean";
 
 @Component({
     selector: "field-set",
@@ -51,7 +52,7 @@ export class FieldSetComponent implements DoCheck {
     }
 
     public get isValid() {
-        return this._isValid;
+        return this._isValid.value;
     }
 
     /** flag d'affichage des boutons ajouter, supprimer, monter, descendre */
@@ -133,7 +134,7 @@ export class FieldSetComponent implements DoCheck {
     /**
      * flag de validité de la saisie
      */
-    private _isValid = false;
+    private _isValid: DefinedBoolean;
 
     /**
      * événement de changement d'état d'un radio
@@ -149,7 +150,9 @@ export class FieldSetComponent implements DoCheck {
         private notifService: NotificationsService,
         private i18nService: I18nService,
         private appSetupService: ApplicationSetupService
-    ) { }
+    ) {
+        this._isValid = new DefinedBoolean();
+    }
 
     public hasRadioFix(): boolean {
         if (this._fieldSet.hasInputs) {
@@ -264,11 +267,9 @@ export class FieldSetComponent implements DoCheck {
     }
 
     private updateValidity() {
-        const oldValidity = this._isValid;
-
         // global validity
-        this._isValid = this.computeValidity();
-        if (this._isValid !== oldValidity) {
+        this._isValid.value = this.computeValidity();
+        if (this._isValid.changed) {
             this.validChange.emit();
         }
     }
diff --git a/src/app/components/fieldset-container/fieldset-container.component.ts b/src/app/components/fieldset-container/fieldset-container.component.ts
index 4d60303d1..74fac2031 100644
--- a/src/app/components/fieldset-container/fieldset-container.component.ts
+++ b/src/app/components/fieldset-container/fieldset-container.component.ts
@@ -6,6 +6,7 @@ import { FieldSet } from "../../formulaire/elements/fieldset";
 import { FormulaireDefinition } from "../../formulaire/definition/form-definition";
 import { I18nService } from "../../services/internationalisation.service";
 import { ApplicationSetupService } from "../../services/app-setup.service";
+import { DefinedBoolean } from "app/definedvalue/definedboolean";
 
 @Component({
     selector: "fieldset-container",
@@ -27,7 +28,7 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
     }
 
     public get isValid() {
-        return this._isValid;
+        return this._isValid.value;
     }
     @Input()
     private _container: FieldsetContainer;
@@ -41,7 +42,7 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
     /**
      * flag de validité des FieldSet enfants
      */
-    private _isValid = false;
+    private _isValid: DefinedBoolean;
 
     /**
      * événément de changement d'état d'un radio
@@ -68,7 +69,9 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
     public constructor(
         private i18nService: I18nService,
         private appSetupService: ApplicationSetupService
-    ) {}
+    ) {
+        this._isValid = new DefinedBoolean();
+    }
 
     /**
      * Ajoute un nouveau sous-nub (Structure, PabCloisons, YAXN… selon le cas)
@@ -140,11 +143,9 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
     }
 
     private updateValidity() {
-        const oldValidity = this._isValid;
-
         // global validity
-        this._isValid = this.computeValidity();
-        if (this._isValid !== oldValidity) {
+        this._isValid.value = this.computeValidity();
+        if (this._isValid.changed) {
             this.validChange.emit();
         }
     }
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 859463c39..31aa8ae8e 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -61,6 +61,7 @@ import { sprintf } from "sprintf-js";
 
 import * as XLSX from "xlsx";
 import { ServiceFactory } from "app/services/service-factory";
+import { DefinedBoolean } from "app/definedvalue/definedboolean";
 
 @Component({
     selector: "hydrocalc",
@@ -110,7 +111,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
      * La validité de l'UI comprend la forme (pas de chaîne alpha dans les champs numériques, etc..).
      * La validité formulaire comprend le domaine de définition des valeurs saisies.
      */
-    private _isUIValid = false;
+    private _isUIValid: DefinedBoolean;
 
     /**
      * flag disabled du bouton "calculer"
@@ -158,6 +159,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
         private formulaireService: FormulaireService,
         private matomoTracker: MatomoTracker
     ) {
+        this._isUIValid = new DefinedBoolean();
         // hotkeys listeners
         this.hotkeysService.add(new Hotkey("alt+w", AppComponent.onHotkey(this.closeCalculator, this)));
         this.hotkeysService.add(new Hotkey("alt+d", AppComponent.onHotkey(this.cloneCalculator, this)));
@@ -331,7 +333,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
      * the UI validity state)
      */
     ngDoCheck() {
-        this.isCalculateDisabled = !this._isUIValid;
+        this.isCalculateDisabled = !this._isUIValid.value;
     }
 
     ngOnDestroy() {
@@ -473,12 +475,12 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
      * calcul de la validité globale de la vue
      */
     private updateUIValidity() {
-        this._isUIValid = false;
+        let res = false;
         if (!this._formulaire.calculateDisabled) {
             // all fieldsets must be valid
-            this._isUIValid = true;
+            res = true;
             if (this._fieldsetComponents !== undefined) {
-                this._isUIValid = this._isUIValid && this._fieldsetComponents.reduce(
+                res = res && this._fieldsetComponents.reduce(
                     // callback
                     (
                         // accumulator (valeur précédente du résultat)
@@ -497,7 +499,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
             }
             // all fieldset containers must be valid
             if (this._fieldsetContainerComponents !== undefined) {
-                this._isUIValid = this._isUIValid && this._fieldsetContainerComponents.reduce<boolean>(
+                res = res && this._fieldsetContainerComponents.reduce<boolean>(
                     // callback
                     (
                         // accumulator (valeur précédente du résultat)
@@ -516,19 +518,23 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
             }
             // special components must be valid
             if (this._pabTableComponent !== undefined) {
-                this._isUIValid = this._isUIValid && this._pabTableComponent.isValid;
+                res = res && this._pabTableComponent.isValid;
             }
             if (this._pbSchemaComponent !== undefined) {
-                this._isUIValid = this._isUIValid && this._pbSchemaComponent.isValid;
+                res = res && this._pbSchemaComponent.isValid;
             }
             if (this._formulaire.currentNub.calcType === CalculatorType.PreBarrage) {
                 const form: FormulairePrebarrage = this._formulaire as FormulairePrebarrage;
-                this._isUIValid = this._isUIValid && form.checkParameters().length === 0;
+                res = res && form.checkParameters().length === 0;
             }
         }
 
+        this._isUIValid.value = res;
+
         // update prébarrage schema validity
+        if (this._isUIValid.changed) {
             this._pbSchemaComponent.updateItemsValidity();
+        }
     }
 
     public getElementStyleDisplay(id: string) {
diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts
index 444127f7f..97d1f801d 100644
--- a/src/app/components/generic-input/generic-input.component.ts
+++ b/src/app/components/generic-input/generic-input.component.ts
@@ -5,6 +5,7 @@ import { FormulaireDefinition } from "../../formulaire/definition/form-definitio
 import { NgParameter } from "../../formulaire/elements/ngparam";
 import { I18nService } from "../../services/internationalisation.service";
 import { ApplicationSetupService } from "../../services/app-setup.service";
+import { DefinedBoolean } from "app/definedvalue/definedboolean";
 
 /**
  * classe de gestion générique d'un champ de saisie avec titre, validation et message d'erreur
@@ -79,6 +80,11 @@ export abstract class GenericInputComponentDirective implements OnChanges {
      */
     private _isValidModel = false;
 
+    /**
+     * flag de validité globale
+     */
+    private _isValid: DefinedBoolean;
+
     /**
      * message d'erreur UI
      */
@@ -96,7 +102,9 @@ export abstract class GenericInputComponentDirective implements OnChanges {
         private cdRef: ChangeDetectorRef,
         protected intlService: I18nService,
         protected appSetupService: ApplicationSetupService
-    ) { }
+    ) {
+        this._isValid = new DefinedBoolean();
+    }
 
     public get isDisabled(): boolean {
         if (this._model instanceof NgParameter) {
@@ -107,10 +115,13 @@ export abstract class GenericInputComponentDirective implements OnChanges {
     }
 
     /**
-     * événement de changement de la validité de la saisie
+     * modification et émission d'un événement de changement de la validité
      */
-    private emitValidChanged() {
-        this.change.emit({ "action": "valid", "value": this.isValid });
+    private setAndEmitValid() {
+        this._isValid.value = this._isValidUI && this._isValidModel;
+        if (this._isValid.changed) {
+            this.change.emit({ "action": "valid", "value": this._isValid.value });
+        }
     }
 
     /**
@@ -128,15 +139,12 @@ export abstract class GenericInputComponentDirective implements OnChanges {
      * calcul de la validité globale du composant (UI+modèle)
      */
     public get isValid() {
-        return this._isValidUI && this._isValidModel;
+        return this._isValid.value;
     }
 
     protected setUIValid(b: boolean) {
-        const old = this.isValid;
         this._isValidUI = b;
-        if (this.isValid !== old) {
-            this.emitValidChanged();
-        }
+        this.setAndEmitValid();
     }
 
     protected validateUI() {
@@ -148,11 +156,9 @@ export abstract class GenericInputComponentDirective implements OnChanges {
     }
 
     protected setModelValid(b: boolean) {
-        const old = this.isValid;
         this._isValidModel = b;
-        if (this.isValid !== old) {
-            this.emitValidChanged();
-        }
+        this.setAndEmitValid();
+
         // répercussion des erreurs sur le Form angular, pour faire apparaître/disparaître les mat-error
         if (b) {
             this.inputField.control.setErrors(null);
diff --git a/src/app/components/pab-table/pab-table.component.ts b/src/app/components/pab-table/pab-table.component.ts
index 597d28f3a..7d95042fb 100644
--- a/src/app/components/pab-table/pab-table.component.ts
+++ b/src/app/components/pab-table/pab-table.component.ts
@@ -28,6 +28,7 @@ import { PabTable } from "../../formulaire/elements/pab-table";
 import { DialogEditPabComponent } from "../dialog-edit-pab/dialog-edit-pab.component";
 import { AppComponent } from "../../app.component";
 import { NgParameter, ParamRadioConfig } from "../../formulaire/elements/ngparam";
+import { DefinedBoolean } from "app/definedvalue/definedboolean";
 
 /**
  * The big editable data grid for calculator type "Pab" (component)
@@ -45,7 +46,7 @@ export class PabTableComponent implements AfterViewInit, AfterViewChecked, OnIni
     private pabTable: PabTable;
 
     /** flag de validité des FieldSet enfants */
-    private _isValid = false;
+    private _isValid: DefinedBoolean;
 
     /** événément de changement de validité */
     @Output()
@@ -84,6 +85,7 @@ export class PabTableComponent implements AfterViewInit, AfterViewChecked, OnIni
         private notifService: NotificationsService
     ) {
         this.selectedItems = [];
+        this._isValid = new DefinedBoolean();
     }
 
     /** update vary value from pab fish ladder and unable compute Button */
@@ -98,7 +100,7 @@ export class PabTableComponent implements AfterViewInit, AfterViewChecked, OnIni
 
     /** Global Pab validity */
     public get isValid() {
-        return this._isValid;
+        return this._isValid.value;
     }
 
     /** returns true if the cell has an underlying model (ie. is editable) */
@@ -1452,9 +1454,8 @@ export class PabTableComponent implements AfterViewInit, AfterViewChecked, OnIni
     }
 
     private updateValidity() {
-        const oldValidity = this._isValid;
-        this._isValid = this.computeValidity();
-        if (this._isValid !== oldValidity) {
+        this._isValid.value = this.computeValidity();
+        if (this._isValid.changed) {
             this.validChange.emit();
         }
     }
diff --git a/src/app/components/pb-schema/pb-schema.component.ts b/src/app/components/pb-schema/pb-schema.component.ts
index 425b1ba88..ae157c56f 100644
--- a/src/app/components/pb-schema/pb-schema.component.ts
+++ b/src/app/components/pb-schema/pb-schema.component.ts
@@ -22,6 +22,7 @@ import { AppComponent } from "../../app.component";
 import { fv } from "app/util";
 import { FormulaireNode } from "app/formulaire/elements/formulaire-node";
 import { ServiceFactory } from "app/services/service-factory";
+import { DefinedBoolean } from "app/definedvalue/definedboolean";
 
 /**
  * The interactive schema for calculator type "PreBarrage" (component)
@@ -45,7 +46,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni
     private nativeElement: any;
 
     /** flag de validité du composant */
-    private _isValid = false;
+    private _isValid: DefinedBoolean;
 
     private upstreamId = "amont";
 
@@ -75,6 +76,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni
         private newPbCloisonDialog: MatDialog
     ) {
         this.hotkeysService.add(new Hotkey("del", AppComponent.onHotkey(this.removeOnHotkey, this)));
+        this._isValid = new DefinedBoolean();
     }
 
     /** tracks the fullscreen state */
@@ -334,7 +336,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni
 
     /** Global Pb validity */
     public get isValid() {
-        return this._isValid;
+        return this._isValid.value;
     }
 
     /** used for a cosmetics CSS trick only (mat-card-header right margin) */
@@ -641,16 +643,11 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni
      * Computes the global Pab validity : validity of every cell of every row
      */
     private updateValidity() {
-        const oldValidity = this._isValid;
-
         // check that at least 1 basin is present and a route from river
         // upstream to river downstream exists (2nd check includes 1st)
-        this._isValid = (
-            this.model.hasUpDownConnection()
-            && ! this.model.hasBasinNotConnected()
-        );
+        this._isValid.value = this.model.hasUpDownConnection() && !this.model.hasBasinNotConnected();
 
-        if (this._isValid !== oldValidity) {
+        if (this._isValid.changed) {
             this.validChange.emit();
         }
     }
diff --git a/src/app/definedvalue/definedboolean.ts b/src/app/definedvalue/definedboolean.ts
new file mode 100644
index 000000000..2d8d9a3b8
--- /dev/null
+++ b/src/app/definedvalue/definedboolean.ts
@@ -0,0 +1,7 @@
+import { DefinedValue } from "./definedvalue";
+
+/**
+ * boolean value with initialised, changed, defined states
+ */
+export class DefinedBoolean extends DefinedValue<boolean> {
+}
diff --git a/src/app/definedvalue/definedvalue.ts b/src/app/definedvalue/definedvalue.ts
new file mode 100644
index 000000000..1e72754b0
--- /dev/null
+++ b/src/app/definedvalue/definedvalue.ts
@@ -0,0 +1,46 @@
+/**
+ * value management with initialised, changed and defined states
+ */
+export abstract class DefinedValue<T> {
+    private _initialised: boolean;
+
+    private _value: T;
+
+    private _changed: boolean;
+
+    constructor() {
+        this._initialised = false;
+        this._changed = false;
+    }
+
+    /**
+     * @returns true if setter has been called at least once
+     */
+    public get initialised(): boolean {
+        return this._initialised;
+    }
+
+    /**
+     * @returns true if value is not undefined
+     */
+    public get defined(): boolean {
+        return this._value !== undefined;
+    }
+
+    /**
+     * @returns true if value has been modified by last call to setter
+     */
+    public get changed(): boolean {
+        return this._changed;
+    }
+
+    public get value(): T {
+        return this._value;
+    }
+
+    public set value(v: T) {
+        this._changed = this._value !== v;
+        this._initialised = true;
+        this._value = v;
+    }
+}
-- 
GitLab


From df4d60fdcac18b93632c2ee8755c3031c6755566 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr>
Date: Fri, 20 May 2022 08:15:05 +0200
Subject: [PATCH 05/11] refactor: optimise
 PbSchemaComponent.highlightErrorItems()

refs #544
---
 .../pb-schema/pb-schema.component.ts          | 32 +++++++++++--------
 1 file changed, 19 insertions(+), 13 deletions(-)

diff --git a/src/app/components/pb-schema/pb-schema.component.ts b/src/app/components/pb-schema/pb-schema.component.ts
index ae157c56f..b2f00304c 100644
--- a/src/app/components/pb-schema/pb-schema.component.ts
+++ b/src/app/components/pb-schema/pb-schema.component.ts
@@ -305,6 +305,9 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni
         return (sommeA <= sommeB ? -1 : 1);
     }
 
+    /**
+     * @param item DOM element
+     */
     private selectNode(item: any) {
         // console.debug(`PbSchemaComponent.selectNode(${item?.id})`);
         // highlight clicked element
@@ -671,21 +674,24 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni
             item.classList.remove("node-highlighted-error");
         });
         const invalidUids: string[] = this.pbSchema.form.checkParameters();
-        this.nativeElement.querySelectorAll("g.node").forEach(item => {
-            let itemId: string;
-            if ([this.upstreamId, this.downstreamId].includes(item.id)) {
-                itemId = this.model.uid;
-            } else {
-                itemId = item.id
-            }
-            if (invalidUids.includes(itemId)) {
-                if (item.id === selectedUid) {
-                    item.classList.add("node-highlighted-error");
+        if (invalidUids.length > 0) {
+            this.nativeElement.querySelectorAll("g.node").forEach(item => {
+                // in this case, item is a HTML node of the SVG schema which id is a nud uid
+                let itemId: string;
+                if ([this.upstreamId, this.downstreamId].includes(item.id)) {
+                    itemId = this.model.uid;
                 } else {
-                    item.classList.add("node-error");
+                    itemId = item.id
                 }
-            }
-        });
+                if (invalidUids.includes(itemId)) {
+                    if (item.id === selectedUid) {
+                        item.classList.add("node-highlighted-error");
+                    } else {
+                        item.classList.add("node-error");
+                    }
+                }
+            });
+        }
     }
 
     private unselect() {
-- 
GitLab


From 10bb546b6defbc33697aa41a2d60b1ce8a757c98 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr>
Date: Tue, 24 May 2022 17:40:28 +0200
Subject: [PATCH 06/11] test(e2e): check that calculate button enabled status
 does not depend of other calculators validity status

refs #544
---
 e2e/calculate-button-validation.e2e-spec.ts | 74 +++++++++++++++++++++
 1 file changed, 74 insertions(+)
 create mode 100644 e2e/calculate-button-validation.e2e-spec.ts

diff --git a/e2e/calculate-button-validation.e2e-spec.ts b/e2e/calculate-button-validation.e2e-spec.ts
new file mode 100644
index 000000000..c6c29ec75
--- /dev/null
+++ b/e2e/calculate-button-validation.e2e-spec.ts
@@ -0,0 +1,74 @@
+import { ListPage } from "./list.po";
+import { CalculatorPage } from "./calculator.po";
+import { Navbar } from "./navbar.po";
+import { browser } from "protractor";
+import { PreferencesPage } from "./preferences.po";
+
+describe("Calculate button - ", () => {
+    let listPage: ListPage;
+    let calcPage: CalculatorPage;
+    let navBar: Navbar;
+    let prefPage: PreferencesPage;
+
+    beforeAll(async () => {
+        prefPage = new PreferencesPage();
+        listPage = new ListPage();
+        calcPage = new CalculatorPage();
+        navBar = new Navbar();
+    });
+
+    beforeEach(async () => {
+        await prefPage.navigateTo();
+        // disable evil option "empty fields on module creation"
+        await prefPage.disableEvilEmptyFields();
+        await browser.sleep(200);
+    });
+
+    it("check button status only depends on calculator (no link between calculators)", async () => {
+        // start page
+        await navBar.clickNewCalculatorButton();
+        await browser.sleep(200);
+
+        // open PAB: chute calculator
+        await listPage.clickMenuEntryForCalcType(12);
+        await browser.sleep(200);
+
+        // start page
+        await navBar.clickNewCalculatorButton();
+        await browser.sleep(200);
+
+        // open PAB: dimensions
+        await listPage.clickMenuEntryForCalcType(5);
+        await browser.sleep(200);
+
+        // fill width field with invalid data
+        const inputW = calcPage.getInputById("W");
+        await inputW.clear();
+        await browser.sleep(20);
+        await inputW.sendKeys("-1");
+        await browser.sleep(200);
+        debugger
+        // check that "compute" button is inactive
+        let calcButtonClone = calcPage.getCalculateButton();
+        let disabledStateClone = await calcButtonClone.getAttribute("disabled");
+        expect(disabledStateClone).toBe("true");
+
+        // back to PAB: chute
+        await navBar.clickCalculatorTab(0);
+        await browser.sleep(200);
+
+        // check that "compute" button is active
+        calcButtonClone = calcPage.getCalculateButton();
+        disabledStateClone = await calcButtonClone.getAttribute("disabled");
+        expect(disabledStateClone).not.toBe("true");
+
+        // back to PAB: dimensions
+        await navBar.clickCalculatorTab(1);
+        await browser.sleep(200);
+
+        // check that "compute" button is inactive
+        calcButtonClone = calcPage.getCalculateButton();
+        disabledStateClone = await calcButtonClone.getAttribute("disabled");
+        expect(disabledStateClone).toBe("true");
+    });
+});
-- 
GitLab


From 91b62e818ea9a21ce75068c1c45323a2f71f14e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr>
Date: Tue, 24 May 2022 17:42:04 +0200
Subject: [PATCH 07/11] fix: calculate enabled button of a calculator depends
 on other calculator validity status

refs #544
---
 src/app/components/field-set/field-set.component.ts         | 6 +++---
 .../fieldset-container/fieldset-container.component.ts      | 6 +++---
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts
index 36d2701bf..f3095f8f7 100644
--- a/src/app/components/field-set/field-set.component.ts
+++ b/src/app/components/field-set/field-set.component.ts
@@ -266,10 +266,10 @@ export class FieldSetComponent implements DoCheck {
         return (paramsAreValid && selectAreValid);
     }
 
-    private updateValidity() {
+    private updateValidity(forceEmit: boolean = false) {
         // global validity
         this._isValid.value = this.computeValidity();
-        if (this._isValid.changed) {
+        if (forceEmit || this._isValid.changed) {
             this.validChange.emit();
         }
     }
@@ -282,7 +282,7 @@ export class FieldSetComponent implements DoCheck {
     }
 
     public ngDoCheck() {
-        this.updateValidity();
+        this.updateValidity(true);
     }
 
     /**
diff --git a/src/app/components/fieldset-container/fieldset-container.component.ts b/src/app/components/fieldset-container/fieldset-container.component.ts
index 74fac2031..7a59f0342 100644
--- a/src/app/components/fieldset-container/fieldset-container.component.ts
+++ b/src/app/components/fieldset-container/fieldset-container.component.ts
@@ -108,7 +108,7 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
     }
 
     public ngDoCheck() {
-        this.updateValidity();
+        this.updateValidity(true);
     }
 
     /**
@@ -142,10 +142,10 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
         return res;
     }
 
-    private updateValidity() {
+    private updateValidity(forceEmit: boolean = false) {
         // global validity
         this._isValid.value = this.computeValidity();
-        if (this._isValid.changed) {
+        if (forceEmit || this._isValid.changed) {
             this.validChange.emit();
         }
     }
-- 
GitLab


From cac860899066f5357163eba4b42fc118f7e2e2d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr>
Date: Wed, 25 May 2022 11:17:35 +0200
Subject: [PATCH 08/11] update jalhyd_branch

refs #544
---
 jalhyd_branch | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/jalhyd_branch b/jalhyd_branch
index 554b541df..d64531f13 100644
--- a/jalhyd_branch
+++ b/jalhyd_branch
@@ -1 +1 @@
-308-log-ameliorer-la-synthese-de-journal
\ No newline at end of file
+devel
-- 
GitLab


From 178fd968594f1e98b3d6df391b3ea6b54b4acb0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr>
Date: Wed, 25 May 2022 11:18:08 +0200
Subject: [PATCH 09/11] refactor: improve fieldset/fieldset container DoCheck
 validation

refs #544
---
 src/app/components/field-set/field-set.component.ts  | 12 +++++++++++-
 .../fieldset-container.component.ts                  | 12 +++++++++++-
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts
index f3095f8f7..1bde22b41 100644
--- a/src/app/components/field-set/field-set.component.ts
+++ b/src/app/components/field-set/field-set.component.ts
@@ -146,6 +146,11 @@ export class FieldSetComponent implements DoCheck {
     @Output()
     protected tabPressed = new EventEmitter<any>();
 
+    /**
+     * nombre d'appels à DoCheck
+     */
+    private _DoCheckCount: number = 0;
+
     public constructor(
         private notifService: NotificationsService,
         private i18nService: I18nService,
@@ -282,7 +287,12 @@ export class FieldSetComponent implements DoCheck {
     }
 
     public ngDoCheck() {
-        this.updateValidity(true);
+        this._DoCheckCount++;
+        // à priori, DoCheck n'est plus utile après quelques cycles de détection de changement
+        // puisque la validité du fieldset est déterminée par les saisies dans les inputs
+        if (this._DoCheckCount < 3) {
+            this.updateValidity(true);
+        }
     }
 
     /**
diff --git a/src/app/components/fieldset-container/fieldset-container.component.ts b/src/app/components/fieldset-container/fieldset-container.component.ts
index 7a59f0342..c98450cdc 100644
--- a/src/app/components/fieldset-container/fieldset-container.component.ts
+++ b/src/app/components/fieldset-container/fieldset-container.component.ts
@@ -66,6 +66,11 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
     @Output()
     protected tabPressed = new EventEmitter<any>();
 
+    /**
+     * nombre d'appels à DoCheck
+     */
+    private _DoCheckCount: number = 0;
+
     public constructor(
         private i18nService: I18nService,
         private appSetupService: ApplicationSetupService
@@ -108,7 +113,12 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
     }
 
     public ngDoCheck() {
-        this.updateValidity(true);
+        this._DoCheckCount++;
+        // à priori, DoCheck n'est plus utile après quelques cycles de détection de changement
+        // puisque la validité du fieldset container est déterminée par les saisies dans les inputs
+        if (this._DoCheckCount < 3) {
+            this.updateValidity(true);
+        }
     }
 
     /**
-- 
GitLab


From 170981eabd414a215e1889040dd5c28423021bff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr>
Date: Wed, 25 May 2022 15:16:03 +0200
Subject: [PATCH 10/11] test(e2e): check pre-dams calculate button/schema items
 validity

refs #544
---
 e2e/calculate-button-validation.e2e-spec.ts | 73 ++++++++++++++++++++-
 e2e/calculator.po.ts                        |  7 ++
 2 files changed, 79 insertions(+), 1 deletion(-)

diff --git a/e2e/calculate-button-validation.e2e-spec.ts b/e2e/calculate-button-validation.e2e-spec.ts
index c6c29ec75..137071d83 100644
--- a/e2e/calculate-button-validation.e2e-spec.ts
+++ b/e2e/calculate-button-validation.e2e-spec.ts
@@ -1,7 +1,7 @@
 import { ListPage } from "./list.po";
 import { CalculatorPage } from "./calculator.po";
 import { Navbar } from "./navbar.po";
-import { browser } from "protractor";
+import { browser, by, element } from "protractor";
 import { PreferencesPage } from "./preferences.po";
 
 describe("Calculate button - ", () => {
@@ -71,4 +71,75 @@ describe("Calculate button - ", () => {
         disabledStateClone = await calcButtonClone.getAttribute("disabled");
         expect(disabledStateClone).toBe("true");
     });
+
+    describe("check button status in prébarrages - ", () => {
+        it("invalid data in Q input", async () => {
+            // start page
+            await navBar.clickNewCalculatorButton();
+            await browser.sleep(200);
+
+            // open prébarrages calculator
+            await listPage.clickMenuEntryForCalcType(30);
+            await browser.sleep(200);
+
+            // Q input
+            const inputQ = element(by.id("Q"));
+            await inputQ.clear();
+            await browser.sleep(200);
+            await inputQ.sendKeys("-1");
+            await browser.sleep(200);
+
+            calcPage.checkCalcButtonEnabled(false);
+
+            // upstream item
+            const upstream = element(by.id("amont"));
+            // should be displayed in error
+            expect(await upstream.getAttribute('class')).toContain("node-error");
+        });
+
+        it("add basin, invalid data in Q input", async () => {
+            // start page
+            await navBar.clickNewCalculatorButton();
+            await browser.sleep(200);
+
+            // open prébarrages calculator
+            await listPage.clickMenuEntryForCalcType(30);
+            await browser.sleep(200);
+
+            // "add basin" button
+            const addBasinBtn = element(by.id("add-basin"));
+            await addBasinBtn.click();
+            await browser.sleep(200);
+
+            // upstream item
+            const upstream = element(by.id("amont"));
+            await upstream.click();
+            await browser.sleep(200);
+
+            // invalid data in Q input
+            const inputQ = element(by.id("Q"));
+            await inputQ.clear();
+            await browser.sleep(200);
+            await inputQ.sendKeys("-1");
+            await browser.sleep(200);
+
+            // calculate button disabled ?
+            calcPage.checkCalcButtonEnabled(false);
+
+            // upstream item displayed in error ?
+            expect(await upstream.getAttribute('class')).toContain("node-error");
+
+            // valid data in Q input
+            await inputQ.clear();
+            await browser.sleep(200);
+            await inputQ.sendKeys("1");
+            await browser.sleep(200);
+
+            // calculate button still disabled ? (the basin is not connected to anything)
+            calcPage.checkCalcButtonEnabled(false);
+
+            // upstream item displayed not in error ?
+            expect(await upstream.getAttribute('class')).not.toContain("node-error");
+        });
+    });
 });
diff --git a/e2e/calculator.po.ts b/e2e/calculator.po.ts
index 4be637886..757a2fa70 100644
--- a/e2e/calculator.po.ts
+++ b/e2e/calculator.po.ts
@@ -221,6 +221,13 @@ export class CalculatorPage {
         return await cloneButton.click();
     }
 
+    // check that "compute" button is in given enabled/disabled state
+    checkCalcButtonEnabled(enabled: boolean) {
+        const calcButton = this.getCalculateButton();
+        expect(calcButton.isEnabled()).toBe(enabled);
+        return calcButton;
+    }
+
     async changeSelectValue(elt: ElementFinder, index: number) {
         await elt.click();
         const optionId = ".cdk-overlay-container mat-option:nth-of-type(" + (index + 1) + ")";
-- 
GitLab


From 110dc1528806d950c375b4c6c9d91c141919bec2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr>
Date: Wed, 25 May 2022 15:16:24 +0200
Subject: [PATCH 11/11] fix: pre-dams item not immediatly displayed in error
 when entering input invalid data after adding a basin

refs #544
---
 src/app/components/generic-calculator/calculator.component.ts | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 31aa8ae8e..8b7f21b2a 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -532,9 +532,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe
         this._isUIValid.value = res;
 
         // update prébarrage schema validity
-        if (this._isUIValid.changed) {
-            this._pbSchemaComponent.updateItemsValidity();
-        }
+        this._pbSchemaComponent?.updateItemsValidity();
     }
 
     public getElementStyleDisplay(id: string) {
-- 
GitLab