Irrelevant_Coder
Irrelevant_Coder

Reputation: 231

Angular 2 Custom Validator (Template Form) Validation

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)?

Upvotes: 0

Views: 568

Answers (2)

Irrelevant_Coder
Irrelevant_Coder

Reputation: 231

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.

Upvotes: 1

Sonu Kapoor
Sonu Kapoor

Reputation: 1637

Return for true should be:

{usernameAvailable: true}

or null for false.

Upvotes: 0

Related Questions