0

I am trying to implement a custom validator that checks if the keyed username exists. However, I am running into a problem where the control is always invalid. I have confirmed the webApi is returning the correct values and the validator function is stepping into the proper return statements.

My custom validator is as follows:

import { Directive, forwardRef } from '@angular/core';
import { AbstractControl, ValidatorFn, NG_VALIDATORS, Validator, FormControl } from '@angular/forms';
import { UsersService } from '../_services/index';
import { Users } from '../admin/models/users';

function validateUserNameAvailableFactory(userService: UsersService): ValidatorFn {
    return (async (c: AbstractControl) => {
        var user: Users;
        userService.getUserByName(c.value)
            .do(u => user = u)            
            .subscribe(
            data => {
                console.log("User    " + user)
                if (user) {
                    console.log("Username was found");
                    return {
                        usernameAvailable: {
                            valid: false
                        }
                    }
                }
                else {
                    console.log("Username was not found");
                    return null;
                }
            },
            error => {
                console.log("Username was not found");
                return null;
            })            

    })
}

@Directive({
    selector: '[usernameAvailable][ngModel]',
    providers: [
        { provide: NG_VALIDATORS, useExisting: forwardRef(() => UserNameAvailableValidator), multi: true }
    ]
})

export class UserNameAvailableValidator implements Validator {
    validator: ValidatorFn;

    constructor(private usersService: UsersService) {   

    }    

    ngOnInit() {        
        this.validator = validateUserNameAvailableFactory(this.usersService);        
    }

    validate(c: FormControl) {
        console.log(this.validator(c));
        return this.validator(c);
    }
}

And the form looks like:

<form #userForm="ngForm" (ngSubmit)="onSubmit()" novalidate>
      <div class="form form-group col-md-12">
        <label for="UserName">User Name</label>
        <input type="text" class="form-control" id="UserName"
               required usernameAvailable maxlength="50" 
               name="UserName" [(ngModel)]="user.userName"
               #UserName="ngModel" />
        <div [hidden]="UserName.valid || UserName.pristine" class="alert alert-danger">
          <p *ngIf="UserName.errors.required">Username is required</p>
          <p *ngIf="UserName.errors.usernameAvailable">Username is not available</p>
          <p *ngIf="UserName.errors.maxlength">Username is too long</p>
        </div>
        <!--<div [hidden]="UserName.valid || UserName.pristine" class="alert alert-danger">Username is Required</div>-->        
      </div> 
</form>

I have followed several tutorials proving this structure works, so I am assuming that I am possibly messing up the return from within the validator function.

Also, is there a way I can present the list of errors (I have tried using li with ngFor, but I get nothing from that)?

1

2 Answers 2

1

So, there was something wrong with using .subscribe the way I was. I found a solution by using the following 2 links:

https://netbasal.com/angular-2-forms-create-async-validator-directive-dd3fd026cb45 http://cuppalabs.github.io/tutorials/how-to-implement-angular2-form-validations/

Now, my validator looks like this:

import { Directive, forwardRef } from '@angular/core';
import { AbstractControl, AsyncValidatorFn, ValidatorFn, NG_VALIDATORS, NG_ASYNC_VALIDATORS, Validator, FormControl } from '@angular/forms';
import { Observable } from "rxjs/Rx";
import { UsersService } from '../_services/index';
import { Users } from '../admin/models/users';

@Directive({
    selector: "[usernameAvailable][ngModel], [usernameAvailable][formControlName]",
    providers: [
        {
            provide: NG_ASYNC_VALIDATORS,
            useExisting: forwardRef(() => UserNameAvailableValidator), multi: true
        }
    ]
})

export class UserNameAvailableValidator implements Validator {

    constructor(private usersService: UsersService) {

    } 

    validate(c: AbstractControl): Promise<{ [key: string]: any }> | Observable<{ [key: string]: any }> {
        return this.validateUserNameAvailableFactory(c.value);
    }

    validateUserNameAvailableFactory(username: string) {
        return new Promise(resolve => {           
            this.usersService.getUserByName(username)
                .subscribe(
                data => {
                    resolve({
                        usernameAvailable: true
                    })
                },
                error => {
                    resolve(null);
                })
        })        
    }
}

This is now working. However, I now have an issue where the the control temporarily invalidates itself while the async validator is running.

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

1 Comment

Maybe keep the field invalidated until the async call completes?
0

Return for true should be:

{usernameAvailable: true}

or null for false.

1 Comment

I was playing around to see if I had messed something up with the returns and realized it has something to do with trying to return a value from within the subscribe. If I hard code the return statement and comment the service call, the error is populated properly. I just don't have the understanding to know what I am doing wrong in the subscribe event.

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.