angularformsprimeng-datatableformarrayangular2-form-validation

How to validate formarray in angular?


I'm using editable with formarray. My model:

class Book {
  id: number;
  name: string;
  active: boolean;
}

allBooks:

[
 {id: 1, name: 'book1', active: true},
 {id: 2, name: 'book2', active: true},
 {id: 3, name: 'book3', active: true},
]

code snippet:

allBooks: Book[];
bookFg: FormGroup;

ngOnInit() {
  this.bookFg = this.fb.group({
    arrayForm: this.fb.array(allBooks.map(book => {
      id: [book.id],
      name: [book.name],
      active: [book.active]
    }))
  });
}

I have to validate the book name, name is required and unique. The html snippet:

<div class="data-container" [formGroup]="bookFg">
        <p-table id="resultTable" [columns]="cols" [value]="labelForm.get('arrayForm').controls" formArrayName="arrayForm" dataKey="value.id" scrollable="true" [resizableColumns]="true" scrollHeight="415px" selectionMode="single"
        [selection]="selected" (onRowSelect)="onRowSelect($event.data)">
          <ng-template pTemplate="header" let-columns>
            ...
            ...
            ...
          </ng-template>
          <ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex">

            <tr [pSelectableRow]="rowData" [formGroupName]="rowIndex">
              <td>
                  <div class="text-center">
                    <input pInputText type="checkbox" formControlName="active">
                  </div>
              </td>
              <td pEditableColumn>
                  <p-cellEditor>
                      <ng-template pTemplate="input">
                          <input (focus)="onFocusEvent(rowIndex)" (blur)="onBlurEvent()" pInputText type="text" formControlName="name">
                      </ng-template>
                      <ng-template pTemplate="output">
                          {{rowData.get('name').value}}
                      </ng-template>
                  </p-cellEditor>
              </td>

            </tr>
          </ng-template>
        </p-table>
    </div>

In this editable table, each row is formgroup. When after editing the name columns, this row will be saved. Question is how to validate? In my case, only save one row in one time. So should I validate all the formarray or just one formgroup in that formarray? and how?


Solution

  • just a customValidator over the array. Anyway there're a type error in your code, take a look to stackblitz

    ngOnInit() {
        this.bookFg = this.fb.group({
          arrayForm: this.fb.array(
            this.allBooks.map(book =>
              this.fb.group({
                id: [book.id],
                name: [book.name],
                active: [book.active]
              })
            )
          )
        });
      }
    
      myCustomValidator() {
        return (formArray: FormArray) => {
          let valid: boolean = true;
          formArray.value.forEach((x, index) => {
            if (formArray.value.findIndex(y => y.name == x.name) != index)
              valid = false;
          });
          return valid ? null : { error: "Names must be unique" };
        };
      }
    

    Update You can also create a validator only for the fields "name"

    myCustomValidatorName(index) {
        return (formControl: FormControl) => {
          let valid: boolean = true;
          if (index) {
            const formArray =
              formControl.parent && formControl.parent.parent
                ? (formControl.parent.parent as FormArray)
                : null;
            if (formArray) {
              console.log(formControl.value);
              formArray.value.forEach((x, i) => {
                if (x.name == formControl.value && index>i) valid = false;
              });
            }
          }
          return valid ? null : { error: "Names must be inique" };
        };
      }
    

    And you create the form like

    ngOnInit() {
        this.bookFg = this.fb.group({
          arrayForm: this.fb.array(
            this.allBooks.map((book, i) =>
              this.fb.group({
                id: new FormControl(book.id),
                name: new FormControl(book.name, this.myCustomValidatorName(i)),
                active: new FormControl(book.active)
              })
            ),
            this.myCustomValidator()
          )
        });
      }
    

    But the problem is that when change the name, not check the others name. so you must create a function

    checkArray(index) {
        (this.bookFg.get("arrayForm") as FormArray).controls.forEach((x, i) => {
          if (i != index) x.get("name").updateValueAndValidity();
        });
      }
    

    And call in the (input) of the edit -or subscribe to valuesChange-

      <input formControlName="name" (input)="checkArray(i)">
    

    Update how validate on submit or on blur

    For validate on submit or blur, we need create the form with the constructor of formGroup and FormControl, not with FormBuilder adding {updateOn:'blur'} or {updateOn:'submit'} at the end of new FormGroup (*) in this case use (blur) in the input

    this.bookFg = new FormGroup({
          arrayForm: new FormArray(
            this.allBooks.map((book, i) =>
              this.fb.group({
                id: new FormControl(book.id),
                name: new FormControl(book.name,this.myCustomValidatorName(i)),
                active: new FormControl(book.active)
              })
            ),
            this.myCustomValidator()
          )
        },{updateOn: 'blur'}); //<--this