i am trying to build a small framework that renders forms based on provided JSON data which is describing that form's structure. Each form element specified in the data is represented by an angular component at runtime.
Elements in the form can be nested using 'container' form elements. The corresponding component is using an *ngFor loop on an array.
I chose to use OnPush Change Detection which works well - except for the container components which will always recreate all child elements when an array item changes, so when change detection is done on the @inputs, there is nothing to compare to.
I tried to make a reduced example here, hope you get the point.
https://stackblitz.com/github/rpnoll1977/TestNgArray
To be able to tell which parts get recreated, i added some input fields. You can type in some random text. When you hit the button, a new element is added to the list. All elements in the list get recreated - not the others, especially not the input which is in parallel to the *ngfor.
I was able to debug this a bit after providing a tracking function in the *ngFor. In Angular's DefaultIterableDiffer.check it always sets mayBeDirty=true as the record to compare to is equal to null.
Actually i made another example which, instead of *ngfor uses a series of *ngIfs (with conditions about the length of the array being smaller than the position of the *ngif). That shows correct behavior, only re-rendering the added item.
CodePudding user response:
You should pay attention on how you're updating your model and how you use trackBy
feature.
You're expecting that items in model for ngFor stay the same.
tracker(index:number, item:any):any{
return item;
}
That's true for deeply nested items:
controls: [
...this.model.controls[0].controls, <=== they stay the same
{id:"labelX", type:"l"},
]
this.model.controls[0].controls[0] === newModel.controls[0].controls[0]); => true
But that's not true for one container above:
var newModel = {
...this.model,
controls: [ <=== here you create a completely new array with new objects
{
...
this.model.controls[0] === newModel.controls[0] // false
So the solution is to use some identificator for trackBy
which is id
in your case:
tracker(index:number, item:any):any{
return item.id;
}
CodePudding user response:
Your tracker
function used in trackBy
function is wrong
it is
tracker(index:number, item:any):any{
return item;/// wrong
}
while it should be
https://stackblitz.com/edit/github-qhynwq?file=src/app/app.component.ts
tracker(index:number, item:any):any{
return item.id;
}