4
votes

Please consider the following custom validator function that calls an API on the backend to fetch some data, check them against the current form input and returns an error if the current form input is a duplicate value:

//check to see if the new category name entered is already in the system
categoryNameUnique(c: FormControl) {
    let categoryNameAlreadyExists: boolean = false;

    //get list of current categories from the server.
    return this.inventoryCategoryService.getAll().map(items => {
        for (let item of items) {
            if (item.Name == c.value) {
                categoryNameAlreadyExists = true;                    
                break;
            }
        }

        if (categoryNameAlreadyExists) {
            //name is not unique
            return { categoryNameUnique: false }
        }
        //not a duplicate, all good
        return null;
    });
}

You'll see that this function is returning an observable (as I have to go back to the backend server to fetch some data to do the validation). The validator works like a charm, no issues. However, I don't know how to display an appropriate message to the user when this validator comes back invalid in my corresponding HTML view. If this function was not returning an observable, I'd do something like:

<div *ngIf="form.controls['categoryName'].errors?.categoryNameUnique">
    This category name is already taken
</div>

However, since this is an observable that gets returned, the errors Object has properties exposed by the Observable, namely operator and source, not the custom object that I'm returning ({categoryNameUnique: false}).

Any assistance would be appreciated.

Update 1:

Hello all, after trying a few other things, I was able to figure out my own issue.

I was setting up my form validation like so:

constructor(private fb: FormBuilder, private router: Router, private inventoryCategoryService: InventoryCategoryService) {
    this.form = fb.group({
        'categoryName': [null, Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(50), this.categoryNameUnique.bind(this)])]
    });
}

The required, minLength and maxLength validators are synchronus but my custom validator categoryNameUnique is async and thus should have been composed separately. I changed the setup to this below and now all is well:

constructor(private fb: FormBuilder, private router: Router, private inventoryCategoryService: InventoryCategoryService) {
    this.form = fb.group({
        'categoryName': [null, Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(50)]),
            Validators.composeAsync([this.categoryNameUnique.bind(this)])]
    });
}
1
this <div *ngIf="form.controls['categoryName'].errors?.categoryNameUnique"> should work just fine, how does it behave for you?Max Koretskyi
@Maximus, categoryNameUnique doesn't appear in my errors object. Instead, errors object contains the properties of an Observable: operator and source. If I inspect those two properties, it is not showing me anything related to my categoryNameUnique custom error object that I'm passing back from within the observable.Tom Vaidyan
weird, can you setup a plunker?Max Koretskyi
also, how does your validate method look?Max Koretskyi
Thanks @Maximus, I've updated my original post describing what the problem was and how I fixed it.Tom Vaidyan

1 Answers

2
votes

I think your problem might be how you're applying the async validator to the control. Based on your usage of form.controls.categoryName I assume you're using the formbuilder to create the form. In that case you need to supply async validators as a 3rd parameter in the group params array, like this:

this.form = this.formBuilder.group({
  categoryName: ["", [], [categoryNameUnique]]
});

After setting it up like that, you should be able to see it in the errors as you expect.

My preferred way of displaying custom validation errors is like this:

<div *ngIf="form.controls.categoryName.hasError('categoryNameUnique')">
    The category name is not unique!
</div>