angular - 角度材料中是否有任何带有过滤器功能的下拉菜单?注意:使用 mat-select 不是 mat-option
问题描述
我在角度材料中搜索了一个过滤的下拉选项,但找不到任何带有 mat-select 多选的东西。我认为 mat-select 的角度材料中没有可用的实现。有没有办法使用角度材料来实现这一点?
解决方案
好吧,我们可以创建一个材质输入表单控件,它是一个带过滤器的多选。
由于答案有点大,您可以在stackblitz中看到结果
这个想法是我们有一个组件,它有一个@input,它可以是一个字符串数组或一个对象数组。我们有三个辅助变量
_list: any[]; //<--an array of object
keys: string[]; //an array with two values, the "key" and the "text"
listFiltered: any[]; //<--the list above filtered
还有两个formControl,一个显示值,一个过滤列表
control = new FormControl();
search = new FormControl();
当我们在输入中收到列表时,我们创建 _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]);
所以,例如
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"
如果
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"
这允许我们创建一个具有 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>
当控件获得焦点时,我们需要使用 ViewChild 打开菜单
@ViewChild(MatMenuTrigger, { static: false }) trigger: MatMenuTrigger;
如果我们在 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
);
});
}
一个函数(变化)
change(value, key) {
const item = this._list.find(x => x[this.keys[0]] == key);
item.checked = value;
}
当我们更改搜索时,listFiltered 包含控件的元素和包含该值的元素,并且 this._list 将是一个数组,其中的元素具有“已检查”属性,如果选择该属性,则该属性变为真。
好吧,现在困难的部分是转换为 mat 自定义表单控件。我们需要遵循文档中的指南
简而言之,我们需要添加一个提供者并托管一些类
providers: [
{ provide: MatFormFieldControl, useExisting: MultiSelectFilterComponent }
],
host: {
"[class.example-floating]": "shouldLabelFloat",
"[id]": "id",
"[attr.aria-describedby]": "describedBy"
}
在构造函数中注入 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;
}
}
添加一些变量和输入
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();
}
以及方法
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;
}
推荐阅读
- lua - lua 脚本,当通过 wrk 执行时,版本不同
- java - 程序类型已经存在:retrofit2.Converter$Factory Retrofit 编译错误
- json - 在 Go 语言中使用深度嵌套的 JSON
- unity3d - 制作 NavMeshAgents 以避免某些代理
- c++ - 自定义 QLineEdit
- azure - Azure 上的物理 CPU 和超线程有什么区别?
- javascript - 使用 axios 删除请求 - React
- javascript - 如何通过在 webpack 4 中设置 eslint 来解决导入/未解决问题
- ansible - 在 Ansible 中生成证书链
- python - 我无法在 Python 中导入 termcolor 库