2

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?

3
  • why you want to save when you edit the name? Personally, I think it's more "natural" save in a submit button. If you has not access to the API (or not want change it) call three times to the function in the submit Commented Oct 14, 2019 at 8:56
  • @Eliseo. Yes, it should have a submit event to activate the form validation, but it's not a submit button, but a focus lost event. I have customed my validation for required and unique. But I don't actually know how to bind the submit and the validation. And please note that, the validation must be triggered after submit Commented Oct 15, 2019 at 1:53
  • I updated the answer to validate only in blur/submit. You need use the "constructor" of FormGroup. Really you can save the data using (blur)="saveData(i)" -when lost the focus- but I don't know what you want to save only the row? all the form? what happens when you save a row and change the same row? Commented Oct 15, 2019 at 6:36

2 Answers 2

3

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
Sign up to request clarification or add additional context in comments.

2 Comments

I didn't mention that the validator is async, and I need to get the validation result in component not html, so I can decide if this value should be saved to database.
then, when defined the FormControl or the formArray, you need indicate new FormControl('',{asyncValidator:this.myCustomValidator()}), or, if you has sync and async validators you can use FormControl('',Validators.Required,this.myCustomValidator()) see an example in stackoverflow.com/questions/58387436/…, and the docs angular.io/api/forms/FormControl
0

You can choose to not have one big form and pass the form group for the row when you save and check if the row is valid before you save. This will make your code easier to understand. Once you save, you will want to reset that form using this.form.markAsPristine().

You could achieve this with the big form by passing the row's form group into the save method as well.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.