[英]Is there any dropdown available with filter feature in angular material? NB: Using mat-select not mat-option
I have searched for a filtered dropdown option in angular material and could not find anything with mat-select multiselect.我在角度材料中搜索了一个过滤的下拉选项,但在 mat-select 多选中找不到任何东西。 I don't think an implementation is available in angular material for mat-select.
我认为在用于 mat-select 的角度材料中没有可用的实现。 Is there a way to implement this using angular material?
有没有办法使用角材料来实现这一点?
There is no default component in Angular Material which has both dropdown and filter built into single component. Angular Material 中没有默认组件,它在单个组件中内置了下拉菜单和过滤器。 But there are three different ways to use drop down with filter.
但是有三种不同的方式可以使用带有过滤器的下拉菜单。
Create custom dropdown with filter by using mat-select and input as shown here创建自定义用毡选择并输入如图所示与过滤器下拉菜单这里
Use third party components which provide the same functionality like mat-select-search使用提供相同功能的第三方组件,如mat-select-search
Well, We can create a material input form control that was a multiselect with filter.好吧,我们可以创建一个带有过滤器的多选的材质输入表单控件。
As the answer is a bit larger, you can see the result in the stackblitz由于答案有点大,您可以在stackblitz中看到结果
The idea is that we has a component that has a @input that can be an array of strings or an array of object.这个想法是我们有一个组件,它有一个@input,它可以是一个字符串数组或一个对象数组。 We has three auxiliars variables
我们有三个辅助变量
_list: any[]; //<--an array of object
keys: string[]; //an array with two values, the "key" and the "text"
listFiltered: any[]; //<--the list above filtered
And two formControls, one to show the value and one to filter the list还有两个formControls,一个显示值,一个过滤列表
control = new FormControl();
search = new FormControl();
When we received the list in a input we create the _list and give value to keys当我们在输入中收到列表时,我们创建 _list 并为键赋值
@Input() set list(value) {
this._list =
typeof value[0] == "string"
? value.map(x => ({ key: x, value: x }))
: [...value];
this.keys = Object.keys(this._list[0]);
So, eg所以,例如
list: any[] = [
{id:1,name:"Extra cheese"},
{id:2,name:"Mushroom"},
{id:3,name:"Onion"},
{id:4,name:"Pepperoni"},
{id:5,name:"Sausage"},
{id:6,name:"Tomato"}
];
_list=[...list]
keys[0]="id"; keys[1]="name"
if如果
list=["Extra cheese","Mushroom","Onion","Pepperoni",name:"Sausage","Tomato"}
_list will be
{key:"Extra cheese",value:"Extra cheese"},
{key:"Mushroom",value:"Mushroom"},
{key:"Onion",value:"Onion"},
{key:"Pepperoni",value:"Pepperoni"},
{key:"Sausage",value:"Sausage"},
{key:"Tomato",value:"Tomato"}
and
keys[0]="key"; keys[1]="value"
This allow us create a component that has a formControl and a "menu"这允许我们创建一个具有 formControl 和“菜单”的组件
<div class="multi-select">
<input (click)="trigger.openMenu()" readonly [formControl]="control" />
<span #menu class="mat-select-wrapper" [matMenuTriggerFor]="appMenu" (menuOpened)="searchID.focus()">
<span class="mat-select-arrow"> </span>
</span>
</div>
<mat-menu #appMenu="matMenu" xPosition="before">
<div class="menu" (click)="$event.stopPropagation()">
<mat-form-field>
<mat-label>Search</mat-label>
<input #searchID matInput placeholder="search" [formControl]="search" />
</mat-form-field>
<div class="mat-menu-item" *ngFor="let item of listFiltered">
<mat-checkbox
[checked]="item.checked"
(change)="change($event.checked, item[keys[0]])"
>
{{ item[keys[1]] }}</mat-checkbox
>
</div>
</div>
</mat-menu>
We need use a ViewChild to open the menu when the control is focused当控件获得焦点时,我们需要使用 ViewChild 打开菜单
@ViewChild(MatMenuTrigger, { static: false }) trigger: MatMenuTrigger;
If we use in ngOnInit如果我们在 ngOnInit 中使用
this.search.valueChanges
.pipe(
startWith(null),
delay(200)
)
.subscribe(res => {
const search = res ? res.toLowerCase() : "";
this.listFiltered = this._list.filter(
x => !res || x.checked ||
x[this.keys[1]].toLowerCase().indexOf(search) >= 0
);
});
}
An a function (change)一个函数(变化)
change(value, key) {
const item = this._list.find(x => x[this.keys[0]] == key);
item.checked = value;
}
when we change the search, the listFiltered contains the elements of the control and the elements that containst the value, and this._list will be an array with elements that has a property "checked" that becomes true if selected.当我们更改搜索时,listFiltered 包含控件的元素和包含值的元素,而 this._list 将是一个包含元素的数组,该数组的元素具有“已检查”属性,如果选中该属性,则该元素将变为真。
Well, Now the difficult part is convert to a mat custom form control.好吧,现在困难的部分是转换为 mat 自定义表单控件。 We need follow the guide in the docs
我们需要遵循文档中的指南
In brief, we need add a provider and host some classes简而言之,我们需要添加一个提供者并托管一些类
providers: [
{ provide: MatFormFieldControl, useExisting: MultiSelectFilterComponent }
],
host: {
"[class.example-floating]": "shouldLabelFloat",
"[id]": "id",
"[attr.aria-describedby]": "describedBy"
}
In constructor inject FocusMonitor,ElementRef and ngControl在构造函数中注入 FocusMonitor、ElementRef 和 ngControl
constructor(
private _focusMonitor: FocusMonitor,
private _elementRef: ElementRef<HTMLElement>,
@Optional() @Self() public ngControl: NgControl
) {
_focusMonitor.monitor(_elementRef, true).subscribe(origin => {
if (this.focused && !origin) {
this.onTouched();
}
this.focused = !!origin;
this.stateChanges.next();
});
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
}
}
Add somes variables and Inputs添加一些变量和输入
controlType = "multi-select-filter";
static nextId = 0;
static ngAcceptInputType_disabled: boolean | string | null | undefined;
id = `multi-select-filter-${MultiSelectFilterComponent.nextId++}`;
describedBy = "";
onChange = (_: any) => {};
onTouched = () => {};
stateChanges = new Subject<void>();
focused = false;
get errorState() //<----This is IMPORTANT, give us if the control is valid or nor
{
return this.ngControl?this.ngControl.invalid && this.ngControl.touched:false;
}
get empty() {
return !this.control.value;
}
get shouldLabelFloat() {
return this.focused || !this.empty;
}
@Input()
get placeholder(): string {
return this._placeholder;
}
set placeholder(value: string) {
this._placeholder = value;
this.stateChanges.next();
}
private _placeholder: string;
@Input()
get required(): boolean {
return this._required;
}
set required(value: boolean) {
this._required = coerceBooleanProperty(value);
this.stateChanges.next();
}
private _required = false;
@Input()
get disabled(): boolean {
return this._disabled;
}
set disabled(value: boolean) {
this._disabled = coerceBooleanProperty(value);
this._disabled ? this.control.disable() : this.control.enable();
this.stateChanges.next();
}
private _disabled = false;
@Input()
get value(): any[] | null { //<--this is the value of our control
if (!this._list) return null; //In my case we return an array based in
//this._list
const result = this._list.filter((x: any) => x.checked);
return result && result.length > 0
? result.filter(x => x.checked).map(x => x[this.keys[0]])
: null;
}
set value(value: any[] | null) {
if (this._list && value) {
this._list.forEach(x => {
x.checked = value.indexOf(x[this.keys[0]]) >= 0;
})
const result = this._list.filter((x: any) => x.checked);
this.control.setValue(
result.map((x: any) => x[this.keys[1]]).join(",")
);
this.onChange(result.map((x: any) => x[this.keys[0]]));
} else
{
this.onChange(null);
this.control.setValue(null);
}
this.stateChanges.next();
}
And the methods和方法
ngOnDestroy() {
this.stateChanges.complete();
this._focusMonitor.stopMonitoring(this._elementRef);
}
setDescribedByIds(ids: string[]) {
this.describedBy = ids.join(" ");
}
onContainerClick(event: MouseEvent) {
if ((event.target as Element).tagName.toLowerCase() != "input") {
this._elementRef.nativeElement.querySelector("input")!.focus();
}
}
writeValue(value: any[] | null): void {
this._value = value;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
Yes.是的。 You can use mat-select-filter from angular material
您可以使用角材料中的 mat-select-filter
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.