Dealing with large datasets in angular material multi select is tough especially when you do not have Virtual Scrollbar integrated. I will explain steps on how to use Virtual Scollbar in Angular Mat Multi Select.
Here is a quick info of what is Virtual Scrollbar:- It is a way to improve the performance of scrollable items like Dropdown, Table etc. Suppose you have to display more than 10k records in a dropdown or a table, then without using Virtual Scrollbar, it becomes performance issue to deal with. More info.
We will be integrating Virtual Scroll in a Mat Multi Select control.
Step 1:
Use a Mat Multi Select control. Check below code for reference:
1 2 3 4 5 6 7 8 9 |
<mat-form-field> <mat-select [formControl]="multiSelectControl" multiple [value]="selected" (openedChange)="openChange($event)" placeholder="Select"> <cdk-virtual-scroll-viewport itemSize="50" [style.height.px]=5*48> <mat-option *cdkVirtualFor="let topping of toppingList" [value]="topping" (onSelectionChange)="onSelectionChange($event)">{{topping.viewValue}}</mat-option> </cdk-virtual-scroll-viewport> </mat-select> </mat-form-field> |
Here you will notice two things:
- cdk-virtual-scroll-viewport
- *cdkVirtualFor
Those are the key tag and directive to work with Virtual Scroll bar in Angular.
Step 2:
Make required changes in Typescript code. Check below code for reference:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
import { Component, ViewChild, ViewChildren, QueryList, ChangeDetectorRef } from "@angular/core"; import { FormControl } from "@angular/forms"; import { CdkVirtualScrollViewport, ScrollDispatcher } from "@angular/cdk/scrolling"; import { MatOption } from "@angular/material/core"; import { filter } from "rxjs/operators"; @Component({ selector: "my-app", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"] }) export class AppComponent { title = "test-proj"; toppings = new FormControl(); toppingList: any[] = []; selected: any = []; @ViewChild(CdkVirtualScrollViewport, { static: true }) cdkVirtualScrollViewPort: CdkVirtualScrollViewport; multiSelectControl = new FormControl(); @ViewChildren(MatOption) options: QueryList<MatOption>; constructor(private cd: ChangeDetectorRef, readonly sd: ScrollDispatcher) { for (let i = 0; i < 100000; i++) { this.toppingList.push({ id: i, viewValue: "option-" + i }); } } ngAfterViewInit(): void { this.sd .scrolled() .pipe(filter(scrollable => this.cdkVirtualScrollViewPort === scrollable)) .subscribe(() => { let needUpdate = false; this.options.forEach(option => { const selected = this.selected.includes(option.value); if (selected && !option.selected) { option.select(); needUpdate = true; } else if (!selected && option.selected) { option.deselect(); needUpdate = true; } }); if (needUpdate) { this.cd.detectChanges(); } }); } foropen() { this.cdkVirtualScrollViewPort.scrollToIndex(5); } openChange($event: boolean) { if ($event) { this.foropen(); this.cdkVirtualScrollViewPort.scrollToIndex(0); this.cdkVirtualScrollViewPort.checkViewportSize(); } } onSelectionChange(change): void { if (!change.isUserInput) { return; } console.log(change.source.value); const value = change.source.value; const idx = this.selected.indexOf(change.source.value); if (idx > -1) { this.selected.splice(idx, 1); } else { this.selected.push(value); } } } |
Just for reference, here is a working code example running on Stackblitz.
Write me up if there is any error while integrating it.
That’s it!!