Maximum value validator with dynamic value

14.8k views Asked by At

I have a service which returns a value representing the money user has, and I want the user to be able to do transactions for a value equal or less than the total amount of money. I've tried to use max validation like this:

valid() {
        const form: FormGroup = this._fb.group({
          fond: ['', [Validators.required]],
          serie: ['', [Validators.required]],
          account: ['', [Validators.required, Validators.min(1)]],
          value: ['', [Validators.required, Validators.min(1)]],
          subAccount: ['', [Validators.required, Validators.min(1)]]
        });
        return form;
      }

But it won't work, it seems like the value in max() has to be set from the beginning, so it will just assume totalAmount as undefined.

4

There are 4 answers

3
Harry Ninh On BEST ANSWER

Update for your edited question:

Use this:

value: ['', [
  Validators.required, 
  Validators.min(1), 
  (control: AbstractControl) => Validators.max(this.totalAmount)(control)
]]

You can achieve that by using this syntax:

(control: AbstractControl) => Validators.max(this.totalAmount)(control)

Why?

  • When you supply Validators.max(this.totalAmount), it creates a validation function with the given parameter (current value of this.totalAmount) then assign it to the form control.
  • When you use fat arrow function, what is assigned to the form control is the fat arrow definition itself. Everytime the fat arrow function is invoked (during validity checking), it revaluates the Validators.max(this.totalAmount) and create new validation function with the current value of this.totalAmount, thus makes it dynamic.
0
Kiss Koppány On

Just a little addition to the previous answers, when you need to set the value of the validator of a formControl of another formControl of the same form, you can init the form without validators, and register them after, just like this:

const form: FormGroup = this._fb.group({
      from: [''],
      to: ['']
});

form.controls.from.setValidators([
    Validators.required,
    (control: AbstractControl) => Validators.max(this.form.controls.to.value)(control)
]);

And the same goes for to.

1
Tony Brasunas On

A powerful and clear solution is to use setValidators()

The code is a bit clearer for others to understand when you use setValidators(), and this solution also works when the dynamic value you want to use might change based on user input with your form.

For instance, with something like this in your component:

  setInputValidation() {
    this.totalAmount = this.countryOfOrigin?.value === 'US' ? 40 : 30;
    this.value?.setValidators(Validators.max(this.totalAmount));
  }

You can display a custom error in your template based on this dynamic value:

 <mat-error *ngIf="value?.hasError('max')">
    Must be less than {{ totalAmount }}.
 </mat-error>

In the case I was dealing with, I had a dropdown in the form that needed to change the max validation on another input. So I used the above code, and then simply added this to the mat-select for the dropdown:

<mat-select formControlName="countryOfOrigin"
    (selectionChange)="setInputValidation()"
    name="country">
  <mat-option *ngFor="let country of countries" value="country">
    {{ country }}
  </mat-option>
</mat-select>

And voila, every time the user makes a choice from the dropdown, the valid max value on the input changes and the error will display based on the new value.

Note: If there are other validators on your input, you will have to add them when you use setValidators().

0
Abdus Salam Azad On

If you are using reactive form , then do the follwing:

Add this on your formBuilder.group

 offerPrice: [this.offer.offerPrice, [Validators.required, Validators.pattern('^\\d+(\\.\\d{1,2})?$'), Validators.pattern('^[1-9]+[0-9]*$'),
        (control: AbstractControl) => Validators.max(this.maxAmount)(control),
        (control: AbstractControl) => Validators.min(this.minAmount)(control)]],

And on your html, use like following:

<div class=" form-group">
              <mat-form-field>
                <input class="form-control" matInput formControlName="offerPrice" placeholder="Gift Amount" [ngClass]="{'is-invalid': submitted && f.offerPrice.errors}"  *ngIf="!useSlab"/>
                <div *ngIf="submitted && f.offerPrice.errors">
                  <mat-error *ngIf="f.offerPrice.errors.required">This is required</mat-error>
                  <mat-error *ngIf="f.offerPrice.errors.min">Minimum offer price should be {{minAmount}}</mat-error>                  
                  <mat-error *ngIf="f.offerPrice.errors.max">Maximum offer price should be {{maxAmount}}</mat-error>
                  <mat-error *ngIf="f.offerPrice.errors.pattern">Price should be greater than zero</mat-error>
                </div>
              </mat-form-field>
            </div>