Reputation: 11
I'm trying to create a custom multi select component, based on the angular-material select component (v10), with virtual-scroll and a custom search input inside the component's overlay.
I also added a mat-select-trigger
element to display the number of selected items, instead of the default list of selected items.
The (almost fully working and without styling) example is available in this stackblitz.
The issue I'm having is related with the displayed value of the selected items.
To replicate the issue you have to:
55
) and select an option (e.g. item-55
)1 selected
and the item will be listed in the Selected items list belowclear
button and then click outside the overlay, to close it againThe component will now display the Items
placeholder, when it should be displaying 1 selected
, since the item is still in the Selected items list (and it's this list that it's used in the mat-select-trigger
element).
Nonetheless, if you re-open the list and scroll to the selected item (item-55
), it will be selected. If you then click outside the overlay to close it again, the correct value will be displayed (1 selected
).
I understand it's a virtual-scroll related issue, but I've been unable to find a solution or workaround for this.
Any suggestions would be greatly appreciated!
Upvotes: 1
Views: 612
Reputation: 667
For those who still have this problem, I found a workaround for this issue.
Github Issue: https://github.com/angular/components/issues/30559
import {
Component,
viewChildren,
Signal,
computed,
signal,
viewChild,
} from '@angular/core';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatOption, MatSelectModule } from '@angular/material/select';
import {
CdkVirtualScrollViewport,
ScrollingModule,
} from '@angular/cdk/scrolling';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-working',
template: `
<mat-form-field>
<mat-label>Working</mat-label>
<mat-select
multiple
[(ngModel)]="selected"
(openedChange)="handleOpenedChange($event)"
>
<cdk-virtual-scroll-viewport
itemSize="48"
minBufferPx="240"
maxBufferPx="240"
[style.height.px]="240"
>
@for (option of hiddenOptions(); track option) {
<!-- IMPORTANT! Keep the content of 'mat-option' the same -->
<mat-option [value]="option" [style.display]="'none'">
Option {{ option }}
</mat-option>
}
<mat-option
#virtualOption
*cdkVirtualFor="let option of options"
[value]="option"
>
Option {{ option }}
</mat-option>
</cdk-virtual-scroll-viewport>
</mat-select>
</mat-form-field>
`,
standalone: true,
imports: [
MatFormFieldModule,
MatSelectModule,
ScrollingModule,
FormsModule,
],
})
export class WorkingComponent {
virtualOptions = viewChildren<MatOption>('virtualOption');
virtualScroll = viewChild(CdkVirtualScrollViewport);
selected = signal<number[]>([]);
options = [...Array(100)].map((value, index) => index + 1);
hiddenOptions: Signal<number[]>;
constructor() {
this.hiddenOptions = computed(() => {
// The Set data structure allows for O(1) lookup time
const virtualOptions = new Set(
this.virtualOptions().map((matOption) => matOption.value),
);
return this.selected().filter(
(value) => !virtualOptions.has(value),
);
});
}
handleOpenedChange(isOpen: boolean): void {
const virtualScroll = this.virtualScroll();
if (!isOpen && virtualScroll) {
virtualScroll.scrollToOffset(0);
virtualScroll.checkViewportSize();
}
}
}
Read more about it: https://en.george-hulpoi.dev/blog/how-to-make-angular-material-select-work-with-virtual-scroll
Upvotes: 0