Maximum value validator with dynamic value
Asked Answered
M

4

15

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.

Mastersinger answered 30/8, 2017 at 5:23 Comment(4)
Make use of Pipe. Pass this dynamic value to pipe and validate the max valueIlluse
No, please don't use Pipes for anything but transforming the values.Shamikashamma
Why did you edit your question sample? I don't see any attempts to use max() in it. By editing and removing that you are making your post very confusing to understand. Would strongly recommend you add back how you were attempting to use max before.Balkanize
It seems this question was edited by someone else on Feb 6, 2018... I can't recall how I was using max but IIRC it was something like Validators.max(this.totalAmount)Mastersinger
S
51

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.
Shamikashamma answered 30/8, 2017 at 5:51 Comment(3)
can you give some headlights about this syntax? I have updated the question with the code I use for validations, how should I implement it? 'value' is the one I want to validate.Mastersinger
And if I need in min and max in one variable (or input)? How can I do @Harry Ninh?Z
@Z just replace the Validators.min(1) with (control: AbstractControl) => Validators.min(this.minAmount)(control). Harry's solution supports a variable amount of both fixed and dynamic validators per input control. In fact, if you needed to execute more complex validation logic (to identify the max value for example) just add a function call .. (control: AbstractControl) => Validators.max( getMaxValue() )(control)Vinculum
A
3

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.

Apul answered 25/5, 2020 at 11:28 Comment(0)
U
0

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>
Upgrade answered 8/7, 2019 at 5:14 Comment(0)
H
0

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().

Hydroxyl answered 1/4, 2021 at 20:27 Comment(1)
After setValidators, You will have to also call updateValueAndValidity() for the new validation to take effect.Blase

© 2022 - 2024 — McMap. All rights reserved.