Josept
Josept

Reputation: 83

Angular 4 : How to use custom validator with data from back-end

In a form, the button submit becomes enable only when the form is valid. A particular input contains datalist with data from back-end. I want to return the form invalid if data filled by the user is not in the datalist. So, i need a custom validator which check if user data is equal to data from back end. Data from back-end is a list of objects listOfArticles which contains reference numbers and other data.

So, I try to create a function for custom validator in the same component file, but it doesn't work for 2 reasons :

Function for custom validator :

export function ValidateRefNumber(control: AbstractControl) {
  for (let refer of ArbologistiqueComponent.listOfArticles) {
     if (control.value == refer.refNumber.input) {
        return true;
     }
  }
  return null;
}

My entire component.ts :

import { Component, OnInit } from '@angular/core';
import 'rxjs/add/operator/switchMap';
import { ManagementArbologistiqueService } from "../management-arbologistique.service";
import { ActivatedRoute, Params } from '@angular/router';
import { FormGroup, FormControl, FormBuilder, FormArray, Validators } from '@angular/forms';

@Component({
  selector: 'app-arbologistique',
  templateUrl: './arbologistique.component.html',
  styleUrls: ['./arbologistique.component.css']
})

export class ArbologistiqueComponent implements OnInit {

  private reponseTest: String;
  private listOfArticles :Array<Object>
  private pathDownload: any;
  private myFormGroup: FormGroup;
  fileToUpload: File = null;
  private buttonSubmitEnabled: boolean = false;

  constructor(public fb: FormBuilder, private managementArbo: ManagementArbologistiqueService, private route: ActivatedRoute) { }

  ngOnInit() {
    this.myFormGroup = this.fb.group({
      itemRows: this.fb.array([this.initItemRows()])
    })

    this.myFormGroup.valueChanges.subscribe(x => this.buttonSubmitEnabled = false);
    this.getListBdd();
      }


  initItemRows() {
    return this.fb.group({
      ... //other fields
      refNb: ['',[Validators.required, ValidateRefNumber]],
      ... //other fields

    })
  }

  addRow(index: number) {
    console.log("functionAddRow called");
    const control = <FormArray>this.myFormGroup.controls['itemRows'];
    control.insert(index, this.initItemRows());

  }

  deleteRow(index: number) {
    console.log("functionDeleteRow called");
    const control = <FormArray>this.myFormGroup.controls['itemRows'];
    control.removeAt(index);
  }

  sendForm() {
    this.buttonSubmitEnabled=true;
    console.log("functionExportCalled");
    this.route.params.subscribe((params: Params) => {
      let subroute = "exportation";
      this.managementArbo.postProducts(subroute, JSON.stringify(this.myFormGroup.value))
        .subscribe(
          res => { this.reponseTest = res; console.log('reponse:' + res); }

          ,
          err => console.log(err),
          () => console.log('getProducts done'));

    });
  }

  getListBdd() {
    this.route.params.subscribe((params: Params) => {
      let subroute = "getRefNumber";
      this.managementArbo.getProducts(subroute)
        .subscribe(
          res => { this.listOfArticles = res; console.log('reponse:' + res); }

          ,
          err => console.log(err),
          () => console.log('getProducts done'));
    });
  }

  get refNb() {
    return this.myFormGroup.get('itemRows.refNb');
} 
}

export function ValidateRefNumber(control: AbstractControl) {
      for (let refer of ArbologistiqueComponent.listOfArticles) {
         if (control.value == refer.refNumber.input) {
            return true;
         }
      }
      return null;
   }

input with datalist (component.html) :

<input list="refNumbers" formControlName="refNb" type="text" name="article" maxlength="8" size="15" required title="8 characters" />

<datalist id="refNumbers">
     <option *ngFor="let ref of listOfArticles">{{ref.refNumber.input}}</option>
</datalist>

Upvotes: 0

Views: 2191

Answers (1)

dAxx_
dAxx_

Reputation: 2290

I will provide you an answer with a simple example, then you can transform it as you need.
Lets say we have this list of articles:

  articles = [
    {
      id: 1,
      content: "test 123"
    },
    {
      id: 2,
      content: "test 345"
    }
  ];

And we want to check if the user type in the input text one of the id's of the articles, otherwise, the form is invalid.

We have this little piece of HTML:

<div *ngFor="let article of articles">
    {{article | json}}
</div>
<form [formGroup]="cForm" (submit)="submitForm(cForm.value)" novalidate>
    <input formControlName="article" type="text" name="article" required />
    <input type="submit" value="submit" [disabled]="!cForm.valid">
</form>

So I just print the array for debug, and we have input which has formControlName of article. and we have a button which is disable if the form is invalid.

Init the formGroup:

this.cForm = this._fb.group({
  article: [
    null,
    Validators.compose([Validators.required, matchValues(this.articles)])
  ]
});

So we have a formGroup with simple validation of required, and our new validator named matchValues.

export const matchValues = (valuesToCheck: any[]): ValidatorFn => {
  return (control: AbstractControl): { [key: string]: boolean } => {
    const controlValue = control.value;

    let res = valuesToCheck.findIndex(el => el.id === +controlValue);
    console.log(res);
    return res !== -1 ? null : { matched: true };
  };
};

I simply wrap it in a function which received the array of articles, and I'm trying to find an article id which is matching the value of the input text. If I dont find any, I'm returning an object:

{ matched: true }

which is the error of the control, so you can access this error with 'matched' same as you access required or minlength or any other error.

I hope its clear enough and gives you a nice kick off to solve your a bit more complicated problem.

--UPDATE--
To make it more clear:

findIndex searching in the array for the first item that match the condition and return the index of the item or -1 if nothing found, in this case, I've check if the value of the input from the user match any ID from the list. so as long as my res is not equal to -1, thats mean that I found an item that match so the validation pass, but if res === -1 there is an error, so I send new object with the error name matched so you can handle it later. anyway in this case, I didn't find any item that matched the id, so its an error.

--UPDATE2--
You got some trouble, So I've included a working example. Check this out

Upvotes: 4

Related Questions