2

So I have a component which loads divs with animations. The divs are shown based on a datasource.

*note: In the example I'm using a lot of <any>, that's because I've not decided the model yet.

apiService.ts

dataArray: Observable<Array<any>>;

constructor(){
   this.getUpdateData();
   setInterval(()=> this.getUpdateData(), 1000);
}

getUpdateData(){
    this.httpClientCall()
    .subscribe((response: any) => {
      this.dataArray = response;
    });
  }

component.html

<div class="panels" *ngFor="let data of apiService.dataArray">
    <div class="panel-that-has-animation">
        {{ data.some_value_that_can_be_updated }}
    </div>
</div>

Simple breakdown: I pull data from a remote location every minute, and let the response be the new data within dataArray.

The problem that I'm having is that on each update, the whole panels div's content gets replaced. Where I'd rather just want to have the values updated instead of the entire div being re-rendered.

The array itself consists of objects, what I really like to do is update an object property within the array instead of replacing the entire array of objects. More precise; compare the 2 arrays and check if there are differences in the properties of the objects.

Example:

[ 
 0: {id: 1, name: a, group: 'a'},
 1: {id: 2, name: b, group: 'b'},
 2: {id: 3, name: c, group: 'c'},
]

New data (from httpClient):

[ 
 0: {id: 1, name: a, group: 'b'},
 1: {id: 2, name: b, group: 'b'},
 2: {id: 3, name: c, group: 'c'},
]

In the example, I'd like to update the first object in the array because the group string has changed, without replacing the entire array of objects.

I'm forever grateful with a why/how for the solution than just code that resolves this. Thank you in advance!

GRX
  • 479
  • 1
  • 5
  • 22
  • `this.dataArray = [...new Set([ ...(this.dataArray || []), ...response]) ]` this might fix your issue. – Adarsh Mohan Mar 10 '21 at 21:17
  • 1
    The `trackBy` function of the `ngFor` structural directive could be what you are looking for. https://stackoverflow.com/questions/42108217/how-to-use-trackby-with-ngfor – Ploppy Mar 10 '21 at 21:18
  • @AdarshMohan this gives multiple errors: `Type 'any[]' is missing the following properties from type 'Observable':` and `Type 'Observable | undefined[]' must have a '[Symbol.iterator]()' method that returns an iterator.` Could you perhaps explain what this does? If I understand what it does I might be able to adjust it so it works for my purpose. – GRX Mar 10 '21 at 21:26
  • What it does is it will create the flat array of both the current and the previous arrays, and then will create a unique set – Adarsh Mohan Mar 10 '21 at 21:27

1 Answers1

1

*ngFor can utilize a trackBy function to associate array elements to DOM elements. This allows angular to more efficiently update the DOM, as it will no longer destroy and recreate all items, but only create new ones.

Just create a little function in your controller:

trackById = (index: number, item: Item) => item.id;

Then add it to your *ngFor like this:

<div class="panels" *ngFor="let data of apiService.dataArray; trackBy: trackById">
    <div class="panel-that-has-animation">
        {{ data.some_value_that_can_be_updated }}
    </div>
</div>

Now you should see that as the source data changes, only new items will be created (animated) and existing items will remain.


Also, not part of your question, but I wanted to mention that you could simplify your service code from:

constructor(){
   this.getUpdateData();
   setInterval(()=> this.getUpdateData(), 1000);
}

getUpdateData(){
    this.httpClientCall()
    .subscribe((response: any) => {
      this.dataArray = response;
    });
  }

To:

dataArray$: Observable<Array<any>> = timer(0, 1000).pipe(
    switchMapTo(this.httpClientCall())
);

This has the added benefit of being lazy; i.e. - if there are no subscribers to dataArray$, you won't be polling the server.

BizzyBob
  • 12,309
  • 4
  • 27
  • 51
  • 1
    `trackBy:` was definitely what I needed! Thank you for the detailed explanation! – GRX Mar 12 '21 at 09:03