3

I'm setting up form validators where the min & max of multiple fields are fetched via a single API. The app crashes when trying to pass one of those async values to a custom validator parameter, but it'll work when the value's hard-coded.

The error received when passing an async value is the following:

ERROR Error: formGroup expects a FormGroup instance. Please pass one in.

file.ts:

export class PackageDimensionValidator {
   static validSize(min: number, max: number): ValidatorFn {
      return (control: AbstractControl): { [key: string]: boolean } | null => {
      if (control.value !== undefined && (isNaN(control.value) || control.value < min || control.value > max)) {
        return { 'exceedRange': true };
      }
      return null;
    };
   }
}

// ... 

ngOnInit() {
   // Retrieve maximum package dimensions for input data validation.
   this.shipmentService.getMaxPackageSize()
      .subscribe(result => {
         this.packageRestrictions = new PackageRestrictions(result);
         console.dir(this.packageRestrictions);
         this.setupPackageValidators();
   });
}

setupPackageValidators() {
   const maxWidth = this.packageRestrictions.maxWidth; // Crashes on Async value.
   // const maxWidth = 5; // Works on hard-coded value.

   // Setup package dimension validators.
   this.packageSizeForm = this.formBuilder.group({
      maxWidth: new FormControl('', Validators.compose([
         PackageDimensionValidator.validSize(null, maxWidth),
         // Other validators ...
      ]))
   });
}

file.html:

<form [formGroup]="packageSizeForm">
   <input formControlName="maxWidth" type="text" required />
</form>

Ideally, there will be multiple formControls each with unique min & max's, all fetched at the same time by a single API. The thought process was to call that API, store its result locally, then build the form validators. I'm now questioning the order of this sequence.

2 Answers 2

2

I'm not completely sure that the error message you're getting reflects the issue you described. I'd start with Initiating packageSizeForm in your ngOnInit without validators:

ngOnInit() {
   this.packageSizeForm = new FormGroup({
          maxWidth: new FormControl('');
   });
   this.shipmentService.getMaxPackageSize()
      .subscribe(result => {
         this.packageRestrictions = new PackageRestrictions(result);
         console.dir(this.packageRestrictions);
         this.setupPackageValidators();
   });
}

Then set the validators in this.setupPackageValidators();:

setupPackageValidators() {
   this.packageSizeForm.get('maxWidth').clearValidators();
   this.packageSizeForm.get('maxWidth').setValidators(Validators.compose([
     PackageDimensionValidator.validSize(null, maxWidth)]);
   this.packageSizeForm.get('maxWidth').updateValueAndValidity();
}
Sign up to request clarification or add additional context in comments.

1 Comment

Beautiful. I understood that initializing the FormGroup within the ngOnInit was likely required for rendering and that I could not assign the maxWidth at that same moment. I just didn't know how to separate the two processes. This is exactly what I needed.
0

You may want to use async validators

https://angular.io/api/forms/AsyncValidator

asyncValidator is passed as an optional third parameter to FormControl constructor:

class FormControl {
  ...
  constructor(
    formState: any = null,
    validatorOrOpts?: ValidatorFn | AbstractControlOptions | ValidatorFn[],
    asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]
  )
  ...
}

2 Comments

My validator itself is not performing asynchronous tasks, so I feel that using an async-validator would be improper usage. The issue is that I happen to be passing it an asynchronous parameter. I would think that a better approach would be to wait for the API to resolve before building my validators. Is my approach faulty or the logic?
Your [formGroup] directive in html expects created FormGroup instance. You haven't created it yet when the view inits cause you're waiting for this.shipmentService.getMaxPackageSize() to resolve. I propose to move this call to async validator and create the form synchronously in ngInit

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.