Sabri Aziri
Sabri Aziri

Reputation: 4174

Angular async lazy rendering with RxJS

I am looking for away to do "lazy rendering" with RxJS in Angular, what I want to achieve is the following:

<div *ngFor="let item of items$ | async">
  {{item.text}}
<div>

and in the component I have:

export class ItemsComponent implements OnInit {
  public items$: Observable<Item[]>;
  
  constructor(private setStore: SetStore){}

  ngOnInit() {
     const setId = 1;
     this.items$ = this.setStore.sets$.pipe(map(sets => sets.find(set => set.id = 1).items));
  }
}

And this works fine but when the set has +50 items, the rendering takes time and it freeze's for a second or more. I was looking for a way to do it lazy by somehow rendering first 30 items and then do load the next 30 after 500ms and so on until the list reach's its end.

Edit: I have tried this approach:


const _items$ = this.setStore.sets$.pipe(
  map(sets => sets.find(set => set.id == 1).items)
);
const loadedItems = [];
_items$.subscribe(data => {
  this.items$ = from(data).pipe(
    concatMap(item => {
        loadedItems.push(item);
        return of(loadedItems).pipe(delay(1));
      })
    );
  });
})

The above works fine in terms of lazy rendering but has some disadvantages like:

The above codes are not tested, if needed I can provide a sample

Upvotes: 5

Views: 5380

Answers (6)

Sabri Aziri
Sabri Aziri

Reputation: 4174

I found this to work well for me:

export class AppComponent implements OnInit {
  
  items$: Observable<number[]>;
  
  constructor() {}

  ngOnInit(){
    const source$ = from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
    
    const items = [];
    this.items$ = source$.pipe(
          bufferCount(3),
          concatMap((items, index) => of(items).pipe(delay(index == 0 ? 0 : 3000))),
          map(arr => {
            items.push(...arr);
            return [...items];
          })
        );
  }
}

Demo

Upvotes: 0

micronyks
micronyks

Reputation: 55453

I'd change your code a bit and remove |async implementation. To process response in bulk or batch, i'd rather prefer to use bufferCount operator as shown below,

NOTE: This is dummy code but this is what you can use to make your life easy. In my example, I'm getting an array as result, in your case it could be an object or array of objects (I don't know);

items = [];


  constructor(public httpClient: HttpClient) {
    range (1, 30)                                        // total 30 requests
      .pipe(
        tap(x => {
          if(x<=10){
            this.items.push(x)                           // processing 10 requests by default without delay
          }
        })
      ).pipe(
          skip(10),                                     // skipping already processed 10 requests
          bufferCount(10),                              // processing 10 request in batch
          concatMap(x => of(x).pipe(delay(3000))) 
      ).subscribe(result=>{
         console.log(result);
         this.items = [...this.items, ...result]; 
       })
  }   

.html

<div *ngFor="let item of items">
  {{item}}
</div>

Dummy Demo

Upvotes: 1

StPaulis
StPaulis

Reputation: 2925

I would also agree that implementing virtual scrolling would be a solution because if you have 1000 rows, the rendering would starting to becoming slow anyway, but another way to reduce the cost of the rendering is to provide a trackBy function to your @ngfor loop.

Improve-performance-with-trackby

Upvotes: 0

hanan
hanan

Reputation: 1910

You can use Virtual Scrolling with Different items sizes using ngx-ui-scroll

demo with variable height items it is quite simple to start with

<div class="viewport">
  <div *uiScroll="let item of datasource">
    <b>{{item.text}}</b>
  </div>
</div>

Upvotes: 4

kflo411
kflo411

Reputation: 124

If the rendering is what is taking so long, it sounds like the component UI is complex enough to affect rendering performance -- unlike a simple table. In such a case, you need to limit rendering (typically by using pagination or virtual scrolling).

Using Angular, your best bet is CDK Virtual Scroll: https://v9.material.angular.io/cdk/scrolling/overview (v9)

It's a very simple replacement of *ngFor, but the performance gains are instantly notable.

Example:

<div>
  <div *ngFor="let item of items" class="example-item">{{item}}</div>
</div>

becomes:

<cdk-virtual-scroll-viewport itemSize="100" class="example-viewport">
  <div *cdkVirtualFor="let item of items" class="example-item">{{item}}</div>
</cdk-virtual-scroll-viewport>

NOTE:

  • itemSize here is the fixed pixel-height of the component
  • look into templateCacheSize, trackBy for further performance considerations

Upvotes: 1

Nitsan Avni
Nitsan Avni

Reputation: 851

From what I understand all you are missing is an additional buffer operator

And regarding the first bullet (initial items), you can skip the first 30 delays

Upvotes: 2

Related Questions