简体   繁体   中英

ngFor and trackBy with async data from API still re-rendering DOM?

I want to use ngFor with async data (loaded from API) and trackBy to improve performance and to update list without DOM flashing/flickering when data updates.

If data is static - everything works fine. But when i try to use data loaded from API - trackBy doesn't work.

LIVE

from API: https://stackblitz.com/edit/angular-hx4p39
static data: https://stackblitz.com/edit/angular-myb6vj

Component

export class AppComponent {

  constructor(private http: HttpClient){}

  comments$: Observable<Comment[]> = this.http.get<Comment[]>(`https://jsonplaceholder.typicode.com/comments?_start=0&_limit=5`);

  add() {
    this.comments$ = this.http.get<Comment[]>(`https://jsonplaceholder.typicode.com/comments?_start=0&_limit=6`);
  }

  edit() {
    this.comments$ = this.comments$.pipe(
      map(comments => {
        comments.map( comment => {
          if(comment.id === 5){ comment.name = 'edit'; }
          return comment;
        });
        return comments;
      })
    );
  }

  itemTrackBy(index: number, item: Comment) {
    return item.id;
  }
}

interface Comment {
  postId;
  id;
  email;
  name;
  body;
}

Template

<button (click)="add()">Make 6</button>
OR
<button (click)="edit()">edit 5-th element</button>

<ul>
  <li *ngFor="let comment of (comments$ | async); trackBy: itemTrackBy">
    {{comment.id}} - {{comment.name}}
  </li>
</ul>

This happens because you are changing your observable for another observable instance. ngFor detects that an re-render the dom when a new observable is set . The correct way to work is to not set a new observable, is to just push new items on the observable .

Here is an stackblitz example that applies this approach, you will see that the dom is not re-rendered: https://stackblitz.com/edit/angular-wslaz7

I don't know why the example with the static data doesn't re-render the dom, maybe is a side effect when the whole operation is 'sync', since if I add a delay() after the of() like:

this.episodes =  of([
      {id: 1, name: "name1"},
      {id: 2, name: "name2"}
    ]).pipe(delay(100));

you will see that it will re-render the items.

We could ask angular team why it doesn't re-render when no delay() is applied, I have no idea.

Hope this helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM