Kendo : Insert a FormGroup at a top into a FormArray
Asked Answered
O

4

14

Problem Statement :

In below stackblitz demo, I am able to insert the FormGroup dynamically at the starting of the grid on clicking the Add button and triggering the (blur) event from one control to update the value of another control sharing same rowIndex. Now the issue is if i inserted multiple formGroups and trigger on (blur) event on any of the newly added FormGroups it updates the value of other controls as well having different rowIndex.

Steps to reproduce the issue :

  • Open the below demo link.
  • Add multiple rows (2 rows) by clicking on the Add button.
  • Enter the value in name field of the 2nd newly added row and focus out.
  • It updates the age field in both the newly added row instead of only in second one.

Stackblitz Demo : https://stackblitz.com/edit/angular-oitz5j-4uezqr

Requirement :

Tab out on any name field of particular row, Age field on same row should display a random number without impacting other rows.

What i tried so far ?

  • Tried to use Array unshift() method but getting error

    Property 'unshift' does not exist on type 'FormArray'

  • Tried to use FormArray.insert() method.It will work if we want to add a FormGroup at specific index. But it fails to insert multiple FormGroups on the top of FormArray dynamically.

Update : Above issue is working fine without Kendo Grid. Please check the below demo.

Demo : https://stackblitz.com/edit/angular-oitz5j-2a31gs

Related issues in github/stackoverflow :

https://github.com/angular/angular/issues/16322

Angular - Form Array push specific index

Note : If we are going to add the controls at the end of the Grid, it is working like a champ. Please check the below demo which works fine with adding the controls at the last.

Demo : https://stackblitz.com/edit/angular-oitz5j-aszrjq

Oxidimetry answered 25/9, 2019 at 19:43 Comment(7)
For 1st demo, when I tried to replicate the steps, I can see error in the console. Is something got change in stackblitz?Courtly
@PankajParkar Sorry for that error. I corrected that. Can you please check nowOutbrave
Here's a fun part: Try calling the functions addNewRow() twice to add row and onFocusOut(1) to mock the expected behavior and everything works fine !Frink
Trying to close in on the problem: You are creating new formgroup everytime the data changes, everytime you press the add button, you are inserting new group in the addNewRow() function and the subscription also.Frink
@SachinGupta I am updating both data source as well as controls. You can check the demo without Kendo and its working fine.Outbrave
@RohitJindal if you can check the form. After you add the rows, the form has double the controls each time.Frink
@SachinGupta I did not get you. Can you provide the fix in stackblitz ?Outbrave
L
6

For those who wants to insert elements dynamically in FormArray initially then use Insert with index 0.

this.formName.controls.FieldNameArray.insert(0,[...])

https://angular.io/api/forms/FormArray#insert.

Logarithm answered 10/12, 2019 at 15:13 Comment(0)
I
2

Why isn't my code working?

This isn't a Kendo issue at all. Your entire form is already being created every time the view observable is triggered. So when you add a row, you are basically adding to both the items in createForm as well as triggering the view observable (by calling updateGridData), which in turn adds to the items in createForm. Telerik seems to be doing the latter as seen in this example so we don't need to insert into items at all in addNewRow. Instead, just update gridData and then call updateGridData.

Solution

There are just two issues in your code. The first one being in your view subscribe block. The code you are using will basically just push on top of the previous items and not replace them. So we need to reinitialize the items FormArray here.

this.view.subscribe(r => {
    this.createForm.setControl('items', new FormArray([]));

    r.data.forEach((i) => {
        const newRow = new FormGroup({
            name: new FormControl(i.name),
            age: new FormControl(i.age)
        });
        (this.createForm.get("items") as FormArray).push(newRow);
    });
});

You could rewrite this using map like below.

this.view.subscribe(r => {
    this.createForm.setControl('items', new FormArray(r.data.map(item =>
        new FormGroup({
            name: new FormControl(item.name),
            age: new FormControl(item.age),
        })
    )));
});

Now all you need to do is assign gridData when a new row is added. We need to get the data entered by the user from createForm since gridData is not bound anywhere and will not contain the data which is entered by the user. There will be no need to do anything to the form here since the view subscribe block is already taking care of declaring createForm.

addNewRow() {
    const blankRow = {
        name: "",
        age: ""
    };

    this.gridData = [blankRow, ...this.createForm.get('items').value];
    const data: GridDataResult = { data: this.gridData, total: this.gridData.length };
    this.editService.updateGridData(data);
}

The grid should now work.

Forked StackBlitz

Note: The reason why the example where you are pushing a new row to the end of the grid works is that you are only declaring createForm once at the start in ngOnInit. Then you manually update the gridData and insert into items. There's no updateGridData here which triggers view to recreate createForm. If there was, even this wouldn't be working as expected.

Ichnography answered 3/10, 2019 at 23:58 Comment(1)
There is one issue with the solution which you suggest as per our requirement. Reinitiate the FormArray agian everytime we add a new form group - Suppose we already have a n numbers of FormGroup with some custom validations on the controls. Hence, if we initialize the whole FormArray again just to add one new FormGroup will reset all the existing validations and may cause the performance issue to recreate the whole FormArray again with n number of FormGroups. Please suggest.Outbrave
K
1

How I started debugging:

I am not saying it is not weird but when you call that method FormArray.insert it inserts a copy of the existing formArray and concats with the current state. I added console.log(formArray.controls) and checked it was growing exponentially that means a snapshot was getting added every insert.

This led me to find out that you are subscribed to the view Observable and whenever it changes you are pushing all items in grid with the existing list.


How to fix it:

Step 1: In your addRow method remove the creation of formGroup and inserting to array as it is already handled in your view.subscribe method

addNewRow() {
  ...
  /*
  <--- remove the creation of formgroup and adding to formarray taken care in next step --->
  if (this.createForm) {
     (this.createForm.get("items") as FormArray).insert(2, newRow);
  }
  */
}

Step 2: in your view.subscribe method change from push to insert. So the initial items will be inserted as well not pushed

this.view.subscribe(r => {
  r.data.forEach((data, i) => { //<-- Added index of the array
      const newRow = new FormGroup({
        name: new FormControl(data.name),
        age: new FormControl(data.age),
      });
      if (this.createForm) {
        (this.createForm.get("items") as FormArray).insert(i, newRow);//<--changed to insert
      }
  });
});

Step 3 now you want to unshift

addNewRow() {
  const blankRow = {
    name: "",
    age: "",
  };
  this.gridData.unshift(blankRow); //<-- you do it here.
  const data: GridDataResult = { data: this.gridData, total: this.gridData.length, }
  this.editService.updateGridData(data);
}

Updated Stackblitz for your reference: https://stackblitz.com/edit/angular-oitz5j-ulhs4k

Kingsbury answered 4/10, 2019 at 1:47 Comment(2)
You can't use unshift here. Notice in your StackBlitz if you add a row, enter data in it and then add another row, the data you just entered will disappear.Ichnography
There is one issue with the solution which you suggest as per our requirement. Reinitiate the FormArray agian everytime we add a new form group - Suppose we already have a n numbers of FormGroup with some custom validations on the controls. Hence, if we initialize the whole FormArray again just to add one new FormGroup will reset all the existing validations and may cause the performance issue to recreate the whole FormArray again with n number of FormGroups. Please suggest.Outbrave
E
0

the problem is Kendo-grid. you are stepping on the index 0 over and over of its data source and who knows what he will do internally to display the data. but what is true is that its not using the form array value. if you get out your from array of kendo grid will work fine or if you add in the end of your onFocusOut method a console log console.log(this.createForm.get("items").value); you will see that the value of the formArray is fine.

If you need use yes or yes kendo grid. read the following doc:

https://www.telerik.com/kendo-angular-ui/components/grid/editing/editing-reactive-forms/

the examples that i see show that you cant handle externally the form. you need use the methods that kendo grid provide. because kendo grid dont display the fromArray value really

Eunuchize answered 27/9, 2019 at 23:28 Comment(2)
Yes, the problem is Kendo Grid, but if we are pushing the FormGroup at the end of FormArray its working fine. Only issue is in adding at the top.Outbrave
Yes. Because when u add a element in a inexistent index and kendo grid dont have any item mapped with that index. When u step the index 0 break the internal mapping of kendo grid. See the examples. Kendo use a internal object to add new items to data source.Eunuchize

© 2022 - 2024 — McMap. All rights reserved.