角材料中是否有带有过滤功能的下拉菜单?注意:使用 mat-select 而不是 mat-option [英] Is there any dropdown available with filter feature in angular material? NB: Using mat-select not mat-option

查看:25
本文介绍了角材料中是否有带有过滤功能的下拉菜单?注意:使用 mat-select 而不是 mat-option的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在角度材料中搜索了过滤下拉选项,但在 mat-select 多选中找不到任何内容.我认为在用于 mat-select 的角度材料中没有可用的实现.有没有办法使用角材料来实现这一点?

解决方案

好吧,我们可以创建一个带有过滤器的多选的材质输入表单控件.

由于答案有点大,你可以在 stackblitz

这个想法是我们有一个组件,它有一个@input,它可以是一个字符串数组或一个对象数组.我们有三个辅助变量

 _list: any[];//<--一个对象数组键:字符串[];//一个有两个值的数组,key"和text"列表过滤:任何[];//<--过滤掉上面的列表

还有两个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 })): [...价值];this.keys = Object.keys(this._list[0]);

所以,例如

list: any[] = [{id:1,name:"额外的奶酪"},{id:2,name:"蘑菇"},{id:3,name:"洋葱"},{id:4,name:"意大利辣香肠"},{id:5,name:"香肠"},{id:6,name:"番茄"}];_list=[...列表]键[0] =id";键[1] =名称"

如果

list=["Extra cheese","Mushroom","Onion","Pepperoni",name:"Sausage","Tomato"}_list 将是{key:"Extra cheese",value:"Extra cheese"},{key:"蘑菇",value:"蘑菇"},{key:"Onion",value:"Onion"},{key:"Pepperoni",value:"Pepperoni"},{key:"香肠",value:"香肠"},{键:番茄",值:番茄"}和键[0] =键";键[1] =值"

这允许我们创建一个具有 formControl 和菜单"的组件

<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>

<mat-menu #appMenu="matMenu" xPosition="before"><div class="menu" (click)="$event.stopPropagation()"><mat-form-field><mat-label>搜索</mat-label><input #searchID matInput placeholder="search" [formControl]="search"/></mat-form-field><div class="mat-menu-item" *ngFor="let item of listFiltered"><mat-checkbox[检查]="item.checked"(change)="change($event.checked, item[keys[0]])">{{ item[keys[1]] }}</mat-checkbox>

</mat-menu>

当控件获得焦点时,我们需要使用 ViewChild 来打开菜单

@ViewChild(MatMenuTrigger, { static: false }) 触发器:MatMenuTrigger;

如果我们在 ngOnInit 中使用

 this.search.valueChanges.管道(开始(空),延迟(200)).subscribe(res => {常量搜索 = 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 = 值;}

当我们更改搜索时,listFiltered 包含控件的元素和包含值的元素,而 this._list 将是一个包含元素的数组,该数组的元素具有已检查"属性,如果被选中,该属性将变为真.

>

好吧,现在困难的部分是转换为 mat 自定义表单控件.我们需要遵循文档中的指南

简而言之,我们需要添加一个提供者并托管一些类

 提供者:[{ 提供:MatFormFieldControl,useExisting:MultiSelectFilterComponent }],主持人: {"[class.example-floating]": "shouldLabelFloat","[id]": "id",[attr.aria-描述的]":描述的"}

在构造函数中注入 FocusMonitor、ElementRef 和 ngControl

 构造函数(私人 _focusMonitor:FocusMonitor,私有 _elementRef: ElementRef,@Optional() @Self() 公共 ngControl: NgControl){_focusMonitor.monitor(_elementRef, true).subscribe(origin => {如果 (this.focused && !origin) {this.onTouched();}this.focused = !!origin;this.stateChanges.next();});如果(this.ngControl != null){this.ngControl.valueAccessor = this;}}

添加一些变量和输入

 controlType = "multi-select-filter";静态 nextId = 0;静态 ngAcceptInputType_disabled: boolean |字符串 |空|不明确的;id = `multi-select-filter-${MultiSelectFilterComponent.nextId++}`;描述者 = "";onChange = (_: any) =>{};onTouched = () =>{};stateChanges = new Subject();重点=假;get errorState()//<----这很重要,请告诉我们控件是否有效或无效{返回 this.ngControl?this.ngControl.invalid &&this.ngControl.touched:false;}得到空(){返回 !this.control.value;}得到 shouldLabelFloat() {返回 this.focused ||!this.empty;}@输入()获取占位符():字符串{返回 this._placeholder;}设置占位符(值:字符串){this._placeholder = 值;this.stateChanges.next();}私有_占位符:字符串;@输入()获取必需():布尔{返回 this._required;}设置必需的(值:布尔值){this._required = coerceBooleanProperty(value);this.stateChanges.next();}私人 _required = false;@输入()禁用():布尔{返回 this._disabled;}设置禁用(值:布尔值){this._disabled = coerceBooleanProperty(value);this._disabled ?this.control.disable() : this.control.enable();this.stateChanges.next();}私人 _disabled = 假;@输入()获取值(): any[] |null {//<--这是我们控件的值if (!this._list) 返回空值;//在我的例子中,我们返回一个基于//this._listconst 结果 = this._list.filter((x: any) => x.checked);返回结果 &&结果.长度>0?result.filter(x => x.checked).map(x => x[this.keys[0]]): 空值;}设置值(值:任何[] | null){如果(this._list && 值){this._list.forEach(x => {x.checked = value.indexOf(x[this.keys[0]]) >= 0;})const 结果 = 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]]));} 别的{this.onChange(null);this.control.setValue(null);}this.stateChanges.next();}

和方法

 ngOnDestroy() {this.stateChanges.complete();this._focusMonitor.stopMonitoring(this._elementRef);}setDescribedByIds(ids: string[]) {this.describesBy = ids.join(" ");}onContainerClick(事件:MouseEvent){if ((event.target as Element).tagName.toLowerCase() != "input") {this._elementRef.nativeElement.querySelector("input")!.focus();}}writeValue(value: any[] | null): void {this._value = 值;}registerOnChange(fn: any): void {this.onChange = fn;}registerOnTouched(fn: any): void {this.onTouched = fn;}setDisabledState(isDisabled: boolean): void {this.disabled = isDisabled;}

I have searched for a filtered dropdown option in angular material and could not find anything with mat-select multiselect. I don't think an implementation is available in angular material for mat-select. Is there a way to implement this using angular material?

解决方案

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

The idea is that we has a component that has a @input that can be an array of strings or an array of object. 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

  control = new FormControl();
  search = new FormControl();

When we received the list in a input we create the _list and give value to keys

  @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, e.g.

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"

<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(MatMenuTrigger, { static: false }) trigger: MatMenuTrigger;

If we use in 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.

Well, Now the difficult part is convert to a mat custom form control. 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

  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;
  }

这篇关于角材料中是否有带有过滤功能的下拉菜单?注意:使用 mat-select 而不是 mat-option的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆