Reputation: 4174
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
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];
})
);
}
}
Upvotes: 0
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>
Upvotes: 1
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
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
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:
Upvotes: 1
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 delay
s
Upvotes: 2