diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 9c8da9ef4ba33e073c2426f0966c5b0d71f2dadd..8b33c6b0929c3398bc015dc8ae43007fa5d38f32 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -20,7 +20,8 @@ import { ParamFieldLineComponent } from './components/param-field-line/param-fie
 import { NgParamMinComponent } from './components/param-values/ngparam-min.component';
 import { NgParamMaxComponent } from './components/param-values/ngparam-max.component';
 import { NgParamStepComponent } from './components/param-values/ngparam-step.component';
-import { ParamValuesComponent, ValueListComponent } from './components/param-values/param-values.component';
+import { ParamValuesComponent } from './components/param-values/param-values.component';
+import { ValueListComponent } from './components/param-values/value-list.component';
 import { SelectFieldLineComponent } from './components/select-field-line/select-field-line.component';
 import { CheckFieldLineComponent } from './components/check-field-line/check-field-line.component';
 // import { AlertDialog } from './components/alert-dialog/alert-dialog.component';
diff --git a/src/app/components/field-set/field-set.component.html b/src/app/components/field-set/field-set.component.html
index 9539cd8f0b121a026d4fea761b36bb0af49ff754..463113c331f09ce54075c15c8a14eee1b2137f61 100644
--- a/src/app/components/field-set/field-set.component.html
+++ b/src/app/components/field-set/field-set.component.html
@@ -15,7 +15,7 @@
 -->
 
 <ng-template ngFor let-p [ngForOf]="fields">
-    <param-field-line *ngIf="p.isInput" [param]=p (onRadio)=onRadioClick($event)>
+    <param-field-line *ngIf="p.isInput" [param]=p (onRadio)=onRadioClick($event) (onValid)=onParamLineValid()>
     </param-field-line>
 
     <select-field-line *ngIf="p.isSelect" [param]=p (onSelectChange)=onSelectChanged($event)>
diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts
index 817875d6e58a34aff9b80f81febe1cac327168a4..108f4724d2a38c635e31cc584682136ed161ecdf 100644
--- a/src/app/components/field-set/field-set.component.ts
+++ b/src/app/components/field-set/field-set.component.ts
@@ -1,9 +1,11 @@
-import { Component, Input, Output, EventEmitter } from "@angular/core";
+import { Component, Input, Output, EventEmitter, ViewChildren, QueryList } from "@angular/core";
 
 import { ParamDefinition } from "jalhyd";
 
 import { NgParameter, ParamRadioConfig } from "../../formulaire/ngparam";
 import { FieldSet } from "../../formulaire/fieldset";
+import { ParamFieldLineComponent } from "../param-field-line/param-field-line.component";
+import { BaseComponent } from "../base/base.component";
 
 @Component({
     selector: "field-set",
@@ -19,7 +21,7 @@ import { FieldSet } from "../../formulaire/fieldset";
         }`
     ]
 })
-export class FieldSetComponent {
+export class FieldSetComponent extends BaseComponent {
     /**
     * field set attribute
     */
@@ -30,6 +32,20 @@ export class FieldSetComponent {
         this._fieldSet = fs;
     }
 
+    @ViewChildren(ParamFieldLineComponent)
+    private _paramComponents: QueryList<ParamFieldLineComponent>;
+
+    /**
+     * événément de changement de validité
+     */
+    @Output()
+    private onValid = new EventEmitter();
+
+    /**
+     * flag de validité de la saisie
+     */
+    private _isValid: boolean = false;
+
     private get fields() {
         return this._fieldSet.fields;
     }
@@ -85,11 +101,13 @@ export class FieldSetComponent {
    * cf. https://angular.io/guide/component-interaction#parent-listens-for-child-event
    */
     private onRadioClick(info: string) {
-        // console.log("FieldSetComponent " + info);
         // on renvoie l'info au parent
         this.onRadio.emit(info);
     }
 
+    /**
+     * événément de changement d'état d'un radio
+     */
     @Output()
     private onRadio = new EventEmitter<string>();
 
@@ -100,6 +118,48 @@ export class FieldSetComponent {
         this.onSelectChange.emit(val); // on transmet au parent
     }
 
+    public get isValid() {
+        return this._isValid;
+    }
+
+    /**
+     * calcul de la validité de tous les ParamFieldLineComponent de la vue
+     */
+    private updateValidity() {
+        if (this._paramComponents != undefined)
+            this._isValid = this._paramComponents.reduce(
+                // callback
+                (
+                    // accumulator (valeur précédente du résultat)
+                    acc,
+                    // currentValue (élément courant dans le tableau)
+                    param,
+                    // currentIndex (indice courant dans le tableau)
+                    currIndex,
+                    // array (tableau parcouru)
+                    array
+                ) => {
+                    return acc && param.isValid;
+                }
+                // valeur initiale
+                , true);
+    }
+
+    protected afterFirstViewChecked() {
+        this.updateValidity();
+    }
+
+    /**
+     * réception d'un événement de validité de ParamFieldLineComponent
+     */
+    private onParamLineValid(event: boolean) {
+        this.updateValidity();
+        this.onValid.emit();
+    }
+
+    /**
+     * événément de changement d'état d'un select
+     */
     @Output()
     private onSelectChange = new EventEmitter<string>();
 }
diff --git a/src/app/components/generic-calculator/calculator.component.html b/src/app/components/generic-calculator/calculator.component.html
index a1f621c7e5dee4beb1df02333a85d30cb696fdf2..70d12c9e82441ec2bc56c6179319ed17cb05ae12 100644
--- a/src/app/components/generic-calculator/calculator.component.html
+++ b/src/app/components/generic-calculator/calculator.component.html
@@ -16,13 +16,14 @@
         <div class="container-fluid">
             <!-- chapitres -->
             <field-set *ngFor="let fs of fieldSets" [style.display]="getFieldsetStyleDisplay(fs.id)" [fieldSet]=fs (onRadio)=onRadioClick($event)
-                (onSelectChange)=onSelectChanged($event)></field-set>
+                (onSelectChange)=onSelectChanged($event) (onValid)=OnFieldsetValid()></field-set>
         </div>
 
         <!-- bouton calculer -->
         <div class="row">
             <div class="col-12 text-center">
-                <button type="button" class="button_compute" name="Calculer" (click)="doCompute()">{{uitextCalculer}}</button>
+                <button type="button" [ngClass]="(isCalculateDisabled) ? 'button_compute_err' : 'button_compute_ok'" name="Calculer" (click)="doCompute()"
+                    [disabled]="isCalculateDisabled">{{uitextCalculer}}</button>
                 <p></p>
                 <p></p>
             </div>
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index ef8d98b0506d959e27f1334b58a86a834120c7ec..d3b4ea580b7905a0e356a9e396e4afa08d7dcdf6 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnInit, DoCheck, OnDestroy, ViewChild } from "@angular/core";
+import { Component, OnInit, DoCheck, OnDestroy, ViewChild, ViewChildren, QueryList } from "@angular/core";
 import { ActivatedRoute } from '@angular/router';
 
 import { FormulaireService } from "../../services/formulaire/formulaire.service";
@@ -9,24 +9,38 @@ import { CalculatorType } from "../../formulaire/formulaire-definition";
 import { CalculatorResultsComponent } from "../../components/calculator-results/calculator-results.component";
 import { Observer } from "../../services/observer";
 import { Subscription } from "rxjs/Subscription";
+import { FieldSetComponent } from "../field-set/field-set.component";
+import { BaseComponent } from "../base/base.component";
 
 
 @Component({
     selector: 'hydrocalc',
     templateUrl: "./calculator.component.html",
     styles: [`
-    .button_compute {
+    .button_compute_ok {
         height: 3em;
         width: 30%;
     }
 
+    .button_compute_err {
+        height: 3em;
+        width: 30%;
+        color: red
+    }
+
     #close-button {
         font-size: 1.5em;
     }
     `
     ]
 })
-export class GenericCalculatorComponent implements OnInit, DoCheck, OnDestroy, Observer {
+export class GenericCalculatorComponent extends BaseComponent implements OnInit, DoCheck, OnDestroy, Observer {
+    /**
+     * liste des FieldSetComponent
+     */
+    @ViewChildren(FieldSetComponent)
+    private _fieldsetComponents: QueryList<FieldSetComponent>;
+
     /**
      * composant d'affichage des résultats
      */
@@ -45,9 +59,23 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, OnDestroy, O
      */
     private _computeClicked: boolean;
 
+    /**
+     * flag de validité gloable de la saisie
+     * NB : la validité du bouton "calculer" dépend de la validité de la saisie dans l'UI et de celle indiquée par le formulaire.
+     * 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: boolean = false;
+
+    /**
+     * flag disabled du bouton "calculer"
+     */
+    private isCalculateDisabled: boolean = true;
+
     constructor(private intlService: InternationalisationService,
         private formulaireService: FormulaireService,
         private route: ActivatedRoute) {
+        super();
     }
 
     private get fieldSets(): FieldSet[] {
@@ -125,13 +153,8 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, OnDestroy, O
     }
 
     ngDoCheck() {
-        // let q = this.getParamFromSymbol("Q");  // A VIRER !!!!
-        // if (q != undefined) {
-        //     q.radioState = ParamRadioConfig.VAR;
-        //     q.minValue = 1.5;
-        //     q.maxValue = 6;
-        //     q.stepValue = 0.3;
-        // }
+        if (this._formulaire != undefined)
+            this.isCalculateDisabled = !(this._formulaire.isValid && this._isUIValid);
     }
 
     ngOnDestroy() {
@@ -244,4 +267,41 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, OnDestroy, O
         this._formulaire.updateNodeType();
         this._formulaire.applyDependencies();
     }
+
+    /**
+     * appelé après le 1er affichage du composant
+     */
+    protected afterFirstViewChecked() {
+        this.updateUIValidity();
+    }
+
+    /**
+     * calcul de la validité globale de la vue
+     */
+    private updateUIValidity() {
+        if (this._fieldsetComponents != undefined)
+            this._isUIValid = this._fieldsetComponents.reduce(
+                // callback
+                (
+                    // accumulator (valeur précédente du résultat)
+                    acc,
+                    // currentValue (élément courant dans le tableau)
+                    fieldset,
+                    // currentIndex (indice courant dans le tableau)
+                    currIndex,
+                    // array (tableau parcouru)
+                    array
+                ) => {
+                    return acc && fieldset.isValid;
+                }
+                // valeur initiale
+                , true);
+    }
+
+    /**
+     * réception d'un événement de validité d'un FieldSetComponent
+     */
+    private OnFieldsetValid() {
+        this.updateUIValidity();
+    }
 }
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 e99500c62ff0d4c964e41384055462f4d9a8d098..815da06bd4a4d83380c1a6764895e4005b08d01a 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
@@ -1,7 +1,7 @@
 <div class="row">
     <!-- input de saisie de la valeur -->
     <div class="col-12 col-sm-9 pt-3">
-        <ngparam-input [inputDisabled]="isInputDisabled" [param]="_param" [title]="title"></ngparam-input>
+        <ngparam-input [inputDisabled]="isInputDisabled" [param]="_param" [title]="title" (onChange)="onInputChange($event)"></ngparam-input>
     </div>
 
     <div class="btn-group col" role="group">
@@ -30,4 +30,4 @@
     </div>
 </div>
 
-<param-values *ngIf="isRadioVarChecked" [param]="_param"></param-values>
\ No newline at end of file
+<param-values *ngIf="isRadioVarChecked" [param]="_param" (onValid)=onParamValuesValid($event)></param-values>
\ No newline at end of file
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 6e7a402ee82849529eb75bbed4ac269306e6730e..3a9c63c26204d449b5226c4447d78efdcc9a176b 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
@@ -1,8 +1,8 @@
-import { Component, ViewChild, Input, Output, EventEmitter } from "@angular/core";
+import { Component, ViewChild, Input, Output, EventEmitter, AfterViewChecked } from "@angular/core";
 
 import { InternationalisationService } from "../../services/internationalisation/internationalisation.service";
 import { NgParameter, ParamRadioConfig } from "../../formulaire/ngparam";
-import { GenericInputComponent } from "../generic-input/generic-input.component";
+import { NgParamInputComponent } from "../ngparam-input/ngparam-input.component";
 
 @Component({
     selector: "param-field-line",
@@ -24,11 +24,28 @@ import { GenericInputComponent } from "../generic-input/generic-input.component"
         }`
     ]
 })
-export class ParamFieldLineComponent {
+export class ParamFieldLineComponent implements AfterViewChecked {
     @Input("param")
     private _param: NgParameter;
 
+    @ViewChild(NgParamInputComponent)
+    private _ngParamInputComponent: NgParamInputComponent;
+
+    @Output()
+    private onValid: EventEmitter<void>;
+
+    /**
+     * true si la valeur saisie est valide
+     */
+    private _isInputValid: boolean = false;
+
+    /**
+     * true si le min-max/liste est valide
+     */
+    private _isRangeValid: boolean = true;
+
     constructor(private intlService: InternationalisationService) {
+        this.onValid = new EventEmitter();
     }
 
     private get title(): string {
@@ -103,19 +120,14 @@ export class ParamFieldLineComponent {
     * calcule l'état du radio "paramètre fixé"
     */
     private get radioFixCheck(): string {
-        if (this._param.radioState == ParamRadioConfig.FIX)
-            return "checked";
-        return undefined;
+        return this.isRadioFixChecked ? "checked" : undefined;
     }
 
     /**
     * calcule l'état du radio "paramètre à varier"
     */
     private get radioVarCheck(): string {
-        // console.log(this._param.symbol + " " + ParamRadioConfig[this._param.radioState]);
-        if (this._param.radioState == ParamRadioConfig.VAR)
-            return "checked";
-        return undefined;
+        return this.isRadioVarChecked ? "checked" : undefined;
     }
 
     /**
@@ -128,8 +140,15 @@ export class ParamFieldLineComponent {
     }
 
     /**
-   * retourne l'état du radio "paramètre à varier" sous forme booléenne
-   */
+     * retourne l'état du radio "paramètre fixé" sous forme booléenne
+     */
+    private get isRadioFixChecked(): boolean {
+        return this._param.radioState == ParamRadioConfig.FIX;
+    }
+
+    /**
+     * retourne l'état du radio "paramètre à varier" sous forme booléenne
+     */
     private get isRadioVarChecked(): boolean {
         return this._param.radioState == ParamRadioConfig.VAR;
     }
@@ -145,6 +164,9 @@ export class ParamFieldLineComponent {
 
     private onRadioClick(symbol: string, option: string) {
         this.onRadio.emit(symbol + "_" + option);
+
+        // MAJ validité
+        this.emitValidity();
     }
 
     /**
@@ -190,4 +212,49 @@ export class ParamFieldLineComponent {
             return this.radioCalCheck == undefined ? this.offClass : this.onClass;
         return "";
     }
+
+    /**
+     * validité des saisies du composant
+     */
+    public get isValid(): boolean {
+        switch (this._param.radioState) {
+            case ParamRadioConfig.FIX:
+                return this._isInputValid
+
+            case ParamRadioConfig.VAR:
+                return this._isRangeValid;
+
+            default:
+                return true;
+        }
+    }
+
+    /**
+     * émission d'un événement de validité
+     */
+    private emitValidity() {
+        this.onValid.emit()
+    }
+
+    /**
+     * réception d'un événement de NgParamInputComponent
+     */
+    private onInputChange(event: any) {
+        if (event.action == "valid") {
+            this._isInputValid = event.value;
+            this.emitValidity();
+        }
+    }
+
+    /**
+     * réception d'un événement de validité de ParamValuesComponent
+     */
+    private onParamValuesValid(event: boolean) {
+        this._isRangeValid = event;
+        this.emitValidity()
+    }
+
+    public ngAfterViewChecked() {
+        this._ngParamInputComponent.showError = this.isRadioFixChecked;
+    }
 }
diff --git a/src/app/components/param-values/ngparam-max.component.ts b/src/app/components/param-values/ngparam-max.component.ts
index d1725c478315ecebf62f4ad0d2092e16bbc55f57..9fd292bd87a05866116acdca4e2456dd6adedf60 100644
--- a/src/app/components/param-values/ngparam-max.component.ts
+++ b/src/app/components/param-values/ngparam-max.component.ts
@@ -1,48 +1,37 @@
-import { Component, Output, EventEmitter } from "@angular/core";
+import { Component, Input } from "@angular/core";
 
-import { NumericalString, Pair } from "jalhyd";
+import { NumericalString } from "jalhyd";
 
 import { GenericInputComponent } from "../generic-input/generic-input.component";
 import { InternationalisationService } from "../../services/internationalisation/internationalisation.service";
+import { NgParameter } from "../../formulaire/ngparam";
 
 @Component({
     selector: "ngparam-max",
     templateUrl: "../generic-input/generic-input.component.html"
 })
 export class NgParamMaxComponent extends GenericInputComponent {
-    /**
-       * valeur actuelle du maximum
-       */
-    private _currentValue: number;
-
-    /**
-     * reférence (valeur maxi pour le maximum)
-     */
-    private _refValue: Pair;
+    @Input("param")
+    private _param: NgParameter;
 
     constructor(private intlService: InternationalisationService) {
         super();
     }
 
-    public set refValue(v: Pair) {
-        this._refValue = v;
-        this.validateModel();
-    }
-
     protected getModelValue(): any {
-        return this._currentValue;
+        return this._param.maxValue;
     }
 
     protected setModelValue(v: any) {
-        this._currentValue = v;
+        this._param.maxValue = v;
     }
 
     protected validateModelValue(v: any): { isValid: boolean, message: string } {
         let msg = undefined;
         let valid = false;
 
-        if (!this._refValue.intervalHasValue(v))
-            msg = "La valeur n'est pas dans " + this._refValue.toString();
+        if (!this._param.checkMax(v))
+            msg = "La valeur n'est pas dans ]" + this._param.minValue + " , " + this._param.domain.maxValue + "]";
         else
             valid = true;
 
diff --git a/src/app/components/param-values/ngparam-min.component.ts b/src/app/components/param-values/ngparam-min.component.ts
index 8ee81a79cd15ef8255ef9f082d60ca5df6d72403..a135468af07f8f25767c2c0ca8ab739bf44434d1 100644
--- a/src/app/components/param-values/ngparam-min.component.ts
+++ b/src/app/components/param-values/ngparam-min.component.ts
@@ -1,50 +1,37 @@
-import { Component, Output, EventEmitter } from "@angular/core";
+import { Component, Input } from "@angular/core";
 
-import { NumericalString, Pair } from "jalhyd";
+import { NumericalString } from "jalhyd";
 
 import { GenericInputComponent } from "../generic-input/generic-input.component";
 import { InternationalisationService } from "../../services/internationalisation/internationalisation.service";
+import { NgParameter } from "../../formulaire/ngparam";
 
 @Component({
     selector: "ngparam-min",
     templateUrl: "../generic-input/generic-input.component.html"
 })
 export class NgParamMinComponent extends GenericInputComponent {
-    /**
-     * valeur actuelle du minimum
-     */0.49999999995
-    private _currentValue: number;
-
-    /**
-     * reférence (valeur mini pour le minimum)
-     */
-    private _refValue: Pair;
-
-    public isInit: boolean;
+    @Input("param")
+    private _param: NgParameter;
 
     constructor(private intlService: InternationalisationService) {
         super();
     }
 
-    public set refValue(v: Pair) {
-        this._refValue = v;
-        this.validateModel();
-    }
-
     protected getModelValue(): any {
-        return this._currentValue;
+        return this._param.minValue;
     }
 
     protected setModelValue(v: any) {
-        this._currentValue = v;
+        this._param.minValue = v;
     }
 
     protected validateModelValue(v: any): { isValid: boolean, message: string } {
         let msg = undefined;
         let valid = false;
 
-        if (!this._refValue.intervalHasValue(v))
-            msg = "La valeur n'est pas dans " + this._refValue.toString();
+        if (!this._param.checkMin(v))
+            msg = "La valeur n'est pas dans [" + this._param.domain.minValue + " , " + this._param.maxValue + "[";
         else
             valid = true;
 
diff --git a/src/app/components/param-values/ngparam-step.component.ts b/src/app/components/param-values/ngparam-step.component.ts
index 2f630c91782bf4a06bfba0643cb5a7404bf21f6a..1d25366bfdffb2831c9329909f650ab86545ce22 100644
--- a/src/app/components/param-values/ngparam-step.component.ts
+++ b/src/app/components/param-values/ngparam-step.component.ts
@@ -1,53 +1,47 @@
-import { Component, Output, EventEmitter } from "@angular/core";
+import { Component, Input } from "@angular/core";
 
-import { NumericalString, Pair } from "jalhyd";
+import { NumericalString } from "jalhyd";
 
 import { GenericInputComponent } from "../generic-input/generic-input.component";
 import { InternationalisationService } from "../../services/internationalisation/internationalisation.service";
+import { NgParameter } from "../../formulaire/ngparam";
 
 @Component({
     selector: "ngparam-step",
     templateUrl: "../generic-input/generic-input.component.html"
 })
 export class NgParamStepComponent extends GenericInputComponent {
-    /**
-     * valeur actuelle du pas
-     */
-    private _currentValue: number;
-
-    /**
-     * reférence (valeur maxi pour le pas)
-     */
-    private _refValue: Pair;
+    @Input("param")
+    private _param: NgParameter;
 
     constructor(private intlService: InternationalisationService) {
         super();
     }
 
-    public set refValue(v: Pair) {
-        this._refValue = v;
-        this.validateModel();
-    }
-
     protected getModelValue(): any {
-        return this._currentValue;
+        return this._param.stepValue;
     }
 
     protected setModelValue(v: any) {
-        this._currentValue = v;
+        this._param.stepValue = v;
     }
 
     protected validateModelValue(v: any): { isValid: boolean, message: string } {
         let msg = undefined;
         let valid = false;
 
-        if (!this._refValue.intervalHasValue(v))
-            msg = "La valeur n'est pas dans " + this._refValue.toString();
-        else {
-            valid = v > 0;
-            if (!valid)
-                msg = "La valeur ne peut pas être <= 0";
+        if (this._param.isMinMaxValid) {
+            if (!this._param.checkStep(v)) {
+                msg = "La valeur n'est pas dans " + this._param.stepRefValue.toString();
+            }
+            else {
+                valid = v > 0;
+                if (!valid)
+                    msg = "La valeur ne peut pas être <= 0";
+            }
         }
+        else
+            msg = "Veuillez corriger le min/max";
 
         return { isValid: valid, message: msg };
     }
diff --git a/src/app/components/param-values/param-values.component.html b/src/app/components/param-values/param-values.component.html
index e3adf9e933181dac3f7099bb05dc17f308cbf109..b576cf138c0bcea5d675242fdbf50cc517eded5d 100644
--- a/src/app/components/param-values/param-values.component.html
+++ b/src/app/components/param-values/param-values.component.html
@@ -1,7 +1,7 @@
 <div class="row">
     <div class="btn-group col-12 col-sm-3" dropdown (click)="onSelectValueMode($event)">
         <button dropdownToggle class="btn btn-primary dropdown-toggle waves-light my-1" type="button" mdbRippleRadius>
-            {{currentLabel}}
+            {{currentModeSelectLabel}}
         </button>
         <div class="dropdown-menu">
             <a class="dropdown-item" *ngFor="let e of _valueModes" [value]=e.value>{{e.label}}</a>
@@ -9,16 +9,16 @@
     </div>
 
     <div *ngIf="!isList" class="col-12 col-sm-3">
-        <ngparam-min [title]="uitextValeurMini" (onChange)="onMinChanged($event)"></ngparam-min>
+        <ngparam-min [title]="uitextValeurMini" [param]=_param (onChange)="onMinChanged($event)"></ngparam-min>
     </div>
     <div *ngIf="!isList" class="col-12 col-sm-3">
-        <ngparam-max [title]="uitextValeurMaxi" (onChange)="onMaxChanged($event)"></ngparam-max>
+        <ngparam-max [title]="uitextValeurMaxi" [param]=_param (onChange)="onMaxChanged($event)"></ngparam-max>
     </div>
     <div *ngIf="!isList" class="col-12 col-sm-3">
-        <ngparam-step [title]="uitextPasVariation" (onChange)="onStepChanged($event)"></ngparam-step>
+        <ngparam-step [title]="uitextPasVariation" [param]=_param (onChange)="onStepChanged($event)"></ngparam-step>
     </div>
 
     <div *ngIf="isList" class="col-12 col-sm-6">
-        <value-list title="valeurs séparées par ';'" (onChange)="onListChanged($event)"></value-list>
+        <value-list title="valeurs séparées par ';'" [param]=_param (onChange)="onListChanged($event)"></value-list>
     </div>
 </div>
\ No newline at end of file
diff --git a/src/app/components/param-values/param-values.component.ts b/src/app/components/param-values/param-values.component.ts
index feb09c709f3b48df4da120a5366d6dac045f64ae..7a579e5e2520238a91d0e79cc7d139c13d77518f 100644
--- a/src/app/components/param-values/param-values.component.ts
+++ b/src/app/components/param-values/param-values.component.ts
@@ -1,99 +1,12 @@
-import { Component, Input, Output, EventEmitter, ViewChild, DoCheck } from "@angular/core";
-
-import { ParamDomainValue, Pair } from "jalhyd";
+import { Component, Input, Output, EventEmitter, ViewChild, AfterViewChecked } from "@angular/core";
 
 import { InternationalisationService } from "../../services/internationalisation/internationalisation.service";
 import { NgParameter, ParamValueMode } from "../../formulaire/ngparam";
-import { GenericInputComponent } from "../generic-input/generic-input.component";
 import { NgParamMinComponent } from "./ngparam-min.component";
 import { NgParamMaxComponent } from "./ngparam-max.component";
 import { NgParamStepComponent } from "./ngparam-step.component";
-
-
-@Component({
-    selector: "value-list",
-    templateUrl: "../generic-input/generic-input.component.html"
-})
-export class ValueListComponent extends GenericInputComponent {
-    public _list: number[];
-
-    public isInit;
-
-    constructor(private intlService: InternationalisationService) {
-        super();
-        this._list = [];
-    }
-
-    protected getModelValue(): any {
-        return this._list;
-    }
-
-    protected setModelValue(l: any) {
-        if (typeof (l) == "number") {
-            this._list = [];
-            this._list.push(l);
-        }
-        else
-            this._list = l;
-    }
-
-    protected validateModelValue(v: any): { isValid: boolean, message: string } {
-        let msg = undefined;
-        let valid = false;
-
-        if (v instanceof Array) {
-            valid = true;
-            for (let e of v) {
-                valid = valid && (typeof (e) == "number")
-                if (!valid) {
-                    msg = "La valeur n'est pas une liste de nombres"
-                    break;
-                }
-            }
-        }
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected modelToUI(v: any): string {
-        let res = "";
-        for (let e of v) {
-            if (res != "")
-                res += ";";
-            res += String(e);
-        }
-        return res;
-    }
-
-    protected validateUIValue(ui: string): { isValid: boolean, message: string } {
-        let valid: boolean = false;
-        let msg: string = undefined;
-
-        let tmp: string[] = ui.split(";");
-        let res = true;
-        for (let v of tmp) {
-            let isnum = v != "" && (+v == +v);
-            res = res && isnum;
-            if (!res)
-                break;
-        }
-
-        if (!res)
-            msg = "Veuillez entrer une liste de nombres";
-        else
-            valid = true;
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected uiToModel(ui: string) {
-        let tmp: string[] = ui.split(";");
-        let res = [];
-        for (let v of tmp)
-            res.push(+v);
-        return res;
-    }
-}
+import { BaseComponent } from "../base/base.component";
+import { ValueListComponent } from "./value-list.component";
 
 @Component({
     selector: "param-values",
@@ -115,16 +28,41 @@ export class ValueListComponent extends GenericInputComponent {
         }`
     ]
 })
-export class ParamValuesComponent implements DoCheck {
+export class ParamValuesComponent extends BaseComponent implements AfterViewChecked {
     @Input("param")
     private _param: NgParameter;
 
+    private _valueModes = [];
+
     /**
-     * true si liste de valeur, false si min/max/pas
+     * true quand les champs du composant et de ses enfants sont initialisés
      */
-    private _list: boolean;
+    private _initCompleted = false;
 
-    private _valueModes = [];
+    /**
+     * true si la valeur min est valide
+     */
+    private _validMin: boolean = false;
+
+    /**
+     * true si la valeur max est valide
+     */
+    private _validMax: boolean = false;
+
+    /**
+     * true si la valeur du pas est valide
+     */
+    private _validStep: boolean = false;
+
+    /**
+     * true si la liste de valeurs est valide
+     */
+    private _validList: boolean = false;
+
+    /**
+     * flag signalant qu'on a sélectionné le mode "liste" et qu'il faut initialiser le composant ValueListComponent
+     */
+    private _doInitList: boolean = false;
 
     /**
      * composant de saisie du minimum
@@ -150,102 +88,171 @@ export class ParamValuesComponent implements DoCheck {
     @ViewChild(ValueListComponent)
     private _listComponent: ValueListComponent;
 
+    @Output()
+    private onValid: EventEmitter<boolean>;
+
     constructor(private intlService: InternationalisationService) {
+        super();
         this._valueModes.push({ "value": ParamValueMode.MINMAX, "label": "Min/max" });
         this._valueModes.push({ "value": ParamValueMode.LISTE, "label": "Liste" });
+        this.onValid = new EventEmitter();
     }
 
+    /**
+     * init des champs min/max/pas
+     */
     private initMinMaxStep() {
-        // valeur pour min (celle déjà définie ou celle déduite du domaine de définition)
-        let min: number = this._param.minValue;
-        let ok = min != undefined
-        if (ok) {
-            try {
-                // on la vérifie
-                this._param.checkValue(min);
-                ok = true;
-            }
-            catch (e) {
-                ok = false;
-            }
-        }
-        if (!ok)
-            min = this._param.getValue() / 2;
-
-        // valeur pour max (celle déjà définie ou celle déduite du domaine de définition)
-        let max: number = this._param.maxValue;
-        ok = max != undefined
-        if (ok) {
-            try {
-                // on la vérifie
-                this._param.checkValue(max);
-                ok = true;
-            }
-            catch (e) {
-                ok = false;
-            }
-        }
-        if (!ok)
-            max = this._param.getValue() * 2;
+        if (this.isMinMax) {
+            // valeur pour min : celle déjà définie ou celle déduite de la valeur saisie
+            let min: number = this._param.minValue;
+            if (min == undefined)
+                min = this._param.getValue() / 2;
 
-        this._minComponent.refValue = new Pair(this._param.domain.minValue, max);
-        this._minComponent.model = min;
+            // valeur pour max : celle déjà définie ou celle déduite de la valeur saisie
+            let max: number = this._param.maxValue;
+            if (max == undefined)
+                max = this._param.getValue() * 2;
 
-        this._maxComponent.refValue = new Pair(min, this._param.domain.maxValue);
-        this._maxComponent.model = max;
+            this._minComponent.model = min;
 
-        this.updateStepComponentRef();
+            this._maxComponent.model = max;
 
-        this._stepComponent.model = (this._maxComponent.model - this._minComponent.model) / 20;
+            // valeur du pas
+            let step = this._param.stepValue;
+            if (step == undefined)
+                step = (this._maxComponent.model - this._minComponent.model) / 20;
+            this._stepComponent.model = step;
+
+            this.validateAll();
+
+            this._validMin = this._minComponent.isValid;
+            this._validMax = this._maxComponent.isValid;
+            this._validStep = this._stepComponent.isValid;
+            this.emitValidity();
+
+            this._initCompleted = true;
+        }
     }
 
-    private onMinChanged(val: string) {
-        this.updateStepComponentRef();
+    /**
+     * initialisation de la liste de valeurs avec celle du paramètre géré
+     */
+    private initList() {
+        if (this._doInitList && this._listComponent != undefined) {
+            this._doInitList = false;
+            let l = this._param.valueList;
+            if (l == undefined) {
+                if (this._param.isDefined)
+                    l = [this._param.getValue()];
+                else
+                    l = [];
+            }
+            this._listComponent.model = l;
+        }
+    }
 
-        this._maxComponent.refValue = new Pair(this._minComponent.model, this._param.domain.maxValue);
-        if (this._minComponent.isValid)
-            this._param.minValue = this._minComponent.model;
+    /**
+     * revalidation de tous les composants enfants
+     */
+    private validateAll() {
+        if (this._minComponent != undefined)
+            this._minComponent.validate();
+        if (this._maxComponent != undefined)
+            this._maxComponent.validate();
+        if (this._stepComponent != undefined)
+            this._stepComponent.validate();
+        if (this._listComponent != undefined)
+            this._listComponent.validate();
     }
 
-    private onMaxChanged(val: string) {
-        this.updateStepComponentRef();
+    /**
+     * envoi d'un événement de validité
+     */
+    private emitValidity() {
+        switch (this._param.valueMode) {
+            case ParamValueMode.LISTE:
+                this.onValid.emit(this._validList);
+                break;
 
-        this._minComponent.refValue = new Pair(this._param.domain.minValue, this._maxComponent.model);
-        if (this._maxComponent.isValid)
-            this._param.maxValue = this._maxComponent.model;
+            case ParamValueMode.MINMAX:
+                this.onValid.emit(this._validMin && this._validMax && this._validStep);
+                break;
+        }
     }
 
-    private onStepChanged(val: string) {
-        if (this._stepComponent.isValid)
-            this._param.stepValue = this._stepComponent.model;
+    /**
+     * réception d'un événement de NgParamMinComponent
+     */
+    private onMinChanged(event: any) {
+        if (this._initCompleted)
+            switch (event.action) {
+                case "model":
+                    this.validateAll();
+                    break;
+
+                case "valid":
+                    this._validMin = event.value;
+                    this.emitValidity();
+                    break;
+            }
     }
 
-    private onListChanged(val: string) {
-        if (this._listComponent.isValid)
-            this._param.valueList = this._listComponent.model;
+    /**
+     * réception d'un événement de NgParamMaxComponent
+     */
+    private onMaxChanged(event: any) {
+        if (this._initCompleted)
+            switch (event.action) {
+                case "model":
+                    this.validateAll();
+                    break;
+
+                case "valid":
+                    this._validMax = event.value;
+                    this.emitValidity();
+                    break;
+            }
     }
 
-    public ngDoCheck() {
-        // initialisation des champs min/max/step à l'apparition de ces contrôles
-        if (this._minComponent != undefined && !this._minComponent.isInit) {
-            this._minComponent.isInit = true;
-            this.initMinMaxStep();
-        }
+    /**
+     * réception d'un événement de NgParamStepComponent
+     */
+    private onStepChanged(event: any) {
+        if (this._initCompleted)
+            switch (event.action) {
+                case "model":
+                    this.validateAll();
+                    break;
 
-        if (this._listComponent != undefined && !this._listComponent.isInit) {
-            this._listComponent.isInit = true;
+                case "valid":
+                    this._validStep = event.value;
+                    this.emitValidity();
+                    break;
+            }
+    }
 
-            this._listComponent.model = [];
-            if (this._param.isDefined)
-                this._listComponent.model.push(this._param.getValue());
-        }
+    /**
+     * réception d'un événement de ValueListComponent
+     */
+    private onListChanged(event: any) {
+        if (this._initCompleted)
+            switch (event.action) {
+                case "model":
+                    this.validateAll();
+                    break;
+
+                case "valid":
+                    this._validList = event.value;
+                    this.emitValidity();
+                    break;
+            }
     }
 
     /**
-     * met à jour la valeur de référence du composant gérant le pas de variation
+     * appelé au 1er affichage du composant
      */
-    private updateStepComponentRef() {
-        this._stepComponent.refValue = new Pair(1e-9, this._maxComponent.model - this._minComponent.model);
+    protected afterFirstViewChecked() {
+        this.initMinMaxStep();
     }
 
     private get uitextValeurMini() {
@@ -260,18 +267,43 @@ export class ParamValuesComponent implements DoCheck {
         return this.intlService.localizeText("INFO_PARAMFIELD_PASVARIATION");
     }
 
+    /**
+     * true si mode "liste de valeurs"
+     */
     private get isList(): boolean {
         return this._param.valueMode == ParamValueMode.LISTE;
     }
 
     /**
-     * valeur courante affichée dans le select
+     * true si mode "min/max/pas"
+     */
+    private get isMinMax(): boolean {
+        return this._param.valueMode == ParamValueMode.MINMAX;
+    }
+
+    /**
+     * valeur courante affichée dans le select min-max/list
      */
-    private get currentLabel(): string {
+    private get currentModeSelectLabel(): string {
         return ParamValueMode[this._param.valueMode];
     }
 
+    /**
+     * réception d'un événement du menu "min/max/liste"
+     */
     private onSelectValueMode(event: any) {
-        this._param.valueMode = event.target.value;
+        const next = event.target.value;
+
+        // on a sélectionné "liste" ?
+        if (this._param.valueMode != ParamValueMode.LISTE && next == ParamValueMode.LISTE)
+            this._doInitList = true;
+
+        this._param.valueMode = next;
+        this.validateAll();
+    }
+
+    ngAfterViewChecked() {
+        super.ngAfterViewChecked();
+        this.initList();
     }
 }
diff --git a/src/app/components/param-values/value-list.component.ts b/src/app/components/param-values/value-list.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e3f3adde6ecc35b2f4308359b0d523e01f5c8f59
--- /dev/null
+++ b/src/app/components/param-values/value-list.component.ts
@@ -0,0 +1,95 @@
+import { Component, Input } from "@angular/core";
+
+import { GenericInputComponent } from "../generic-input/generic-input.component";
+import { InternationalisationService } from "../../services/internationalisation/internationalisation.service";
+import { NgParameter } from "../../formulaire/ngparam";
+import { Message } from "jalhyd";
+
+@Component({
+    selector: "value-list",
+    templateUrl: "../generic-input/generic-input.component.html"
+})
+export class ValueListComponent extends GenericInputComponent {
+    @Input("param")
+    private _param: NgParameter;
+
+    constructor(private intlService: InternationalisationService) {
+        super();
+    }
+
+    protected getModelValue(): any {
+        return this._param.valueList;
+    }
+
+    protected setModelValue(l: any) {
+        if (typeof (l) == "number") {
+            this._param.valueList = [];
+            this._param.valueList.push(l);
+        }
+        else
+            this._param.valueList = l;
+    }
+
+    protected validateModelValue(v: any): { isValid: boolean, message: string } {
+        let msg = undefined;
+        let valid = false;
+
+        if (v instanceof Array) {
+            valid = true;
+            try {
+                this._param.checkList(v);
+            }
+            catch (ex) {
+                valid = false;
+                if (ex instanceof Message)
+                    msg = this.intlService.localizeMessage(ex);
+                else
+                    msg = "invalid value";
+            }
+        }
+        else
+            msg = "Veuillez entrer une liste de nombres";
+
+        return { isValid: valid, message: msg };
+    }
+
+    protected modelToUI(v: any): string {
+        let res = "";
+        if (v != undefined)
+            for (let e of v) {
+                if (res != "")
+                    res += ";";
+                res += String(e);
+            }
+        return res;
+    }
+
+    protected validateUIValue(ui: string): { isValid: boolean, message: string } {
+        let valid: boolean = false;
+        let msg: string = undefined;
+
+        let tmp: string[] = ui.split(";");
+        let res = true;
+        for (let v of tmp) {
+            let isnum = v != "" && (+v == +v);
+            res = res && isnum;
+            if (!res)
+                break;
+        }
+
+        if (!res)
+            msg = "Veuillez entrer une liste de nombres";
+        else
+            valid = true;
+
+        return { isValid: valid, message: msg };
+    }
+
+    protected uiToModel(ui: string) {
+        let tmp: string[] = ui.split(";");
+        let res = [];
+        for (let v of tmp)
+            res.push(+v);
+        return res;
+    }
+}