0

I'm trying to do an async validation on a model based form in angular. For simplicity I have removed all other fields of the form. In the actual example there are more fields.

buildForm(): void {
    this.registerForm = this.fb.group({
      'username': 
        [this.user.username, [Validators.required, Validators.minLength(4)],
            [this.validateUsernameUnique.bind(this)]]
    });
    this.registerForm.valueChanges
      .subscribe(data => this.onValueChanged(data));
    this.onValueChanged();  // set validation messages now
  }

the validateUsernameUnique() method should call a service and remotely check if a username already exists and return true or false. The service is working properly, its result appear on the console as expected.

validateUsernameUnique(control: AbstractControl) {
    clearTimeout(this.usernameCheckTimeout);
    return new Promise((resolve) => {
      this.usernameCheckTimeout = setTimeout(() => {
        if (control.value.length >= 4) {
          this.userService.doesUsernameExist(control.value)
            .then(res => {
              if (!res) {
                resolve(null);
              } else {
                resolve({nameTaken: true});
              }
            });
        } else {
          resolve(null);
        }
      }, 1000);
    });
  }

I'm trying to skip this validation until the value is >= 4, because I want the minLength(4)-Validation to show up until then.

The validation messages are defined like this:

formErrors = {
    'username': ''
};
validationMessages = {
    'username': {
      'required': 'username is required',
      'minlength': 'username must be at least 4 characters long',
      'nameTaken': 'username does already exist'
    }
};

The minLength()-Checks works at the beginning as expected and username must be at least 4 characters long shows up on the form too. But when I reach 4 characters, I get an exception in the console:

TypeError: can't convert null to object Stack trace: UserRegisterComponent.prototype.onValueChanged@webpack-internal:///./src/app/user/user-register/user-register.component.ts:111:39 UserRegisterComponent.prototype.buildForm/<@webpack-internal:///./src/app/user/user-register/user-register.component.ts:63:49

After the error occured, no validation message is displayed anymore, the minLength-Validation doesn't also work anymore.

The stackTrack reported the problem in onValueChanged():111, this method looks like this:

onValueChanged(data?: any) {
   if (!this.registerForm) {
     return;
   }
   const form = this.registerForm;
   for (const field of  Object.keys(this.formErrors)) {
     // clear previous error message (if any)
     this.formErrors[field] = '';
     const control = form.get(field);
     if (control && control.dirty && !control.valid) {
       const messages = this.validationMessages[field];
       for (const key of Object.keys(control.errors)) {
         this.formErrors[field] += messages[key] + ' ';
       }
     }
   }
 }
  • How can I get synchronous and async validations get to work side by side? I got the approach with a async-Validation-Method in the same component as the form also from SO.

  • How can I get the nameTaken-errorMessage to appear on the form? Is resolve({nameTaken: true}); in the validation Promise the wrong approach?

Any input appreciated. Don't know exactly how I can possibly debug the currenct behaviour to get a idea what the problem is.

1 Answer 1

1

There also appears to be a mistake in validateUsernameUnique(). You type return this.userService.doesUsernameExist(control.value).then()... which will return out of the function before the request is complete.

Try without the return before this.userService.doesUsernameExist(...).

If you still get the error, I'm guessing it is from trying to call Object.keys() on null. Check what the value of control.errors is before doing Object.keys(control.errors)

Sign up to request clarification or add additional context in comments.

6 Comments

removed the return from doesUsernameExist(), buy it doesn't change anything on the behavior. edited code above.
it's indeed control.errors, which is causing the null exception. I changed the for-of-loop back to a for-in-loop and changed the else block of validate..() now to this.formErrors['username'] += this.validationMessages['username']['nameTaken'] + ' '; resolve(null); and then the code works as expected and shows the error message. but I think this is just an ugly workaround. Is there a way I can pass that message to onValueChanged() like the sync-Validators? or what is the best practice for something like this?
I only used a for-of-loop because it appears the TSLint wants that since the newest version of Typescript. Should I ignore this TSLint-error in this case?
Why not simple add && this.control.errors to your if condition (if (control && control.dirty && !control.valid))?
ok, that fixed the problem will null, thanks. and how should I call onValueChanged() after the Promise finished? this method does only get called after the sync-validators finished. see my ugly workaround in the second comment. or should I leave it this way and resolve(null)?
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.