简体   繁体   English

角材料中是否有带有过滤功能的下拉菜单? 注意:使用 mat-select 而不是 mat-option

[英]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.但是有三种不同的方式可以使用带有过滤器的下拉菜单。

  1. Angular Material autocomplete角材料自动完成
  2. Create custom dropdown with filter by using mat-select and input as shown here创建自定义用毡选择并输入如图所示与过滤器下拉菜单这里

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

相关问题 Angular 材料垫选择重复使用垫选项 - Angular Material mat-select reuse mat-option Angular Material窗体控制mat-select - mat-option,设置更新项目的默认选项? - Angular Material form controls mat-select — mat-option, setting the default option for updating items? 如何在角度5的mat-select中将mat-option用作属性? - How to use mat-option as attribute in mat-select in angular 5? Angular mat-select 和 mat-option 不显示 - Angular mat-select and mat-option not showing Angular 7:使用 mat-select:动态获取 mat-option 的选项会创建一个永无止境的循环 - Angular 7: Using a mat-select: Getting the options for mat-option dynamically creates a never ending loop 在mat-select或mat-selection-list中使用mat-option AND mat-radio-button - Using mat-option AND mat-radio-button in a mat-select or mat-selection-list 将 mat-divider 添加到 mat-select/mat-option - Adding mat-divider to mat-select/mat-option mat-select mat-option angular 下拉菜单在您 select 下拉菜单中的其他值之后不会改变值 - mat-select mat-option angular drop down doesn't change value after you select other value from dropdown 为什么 select 不能作为 mat-select 的 mat-option? - Why can't select a mat-option of mat-select? Mat-select:让它选择第一个mat-option - Mat-select: have it select the first mat-option
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM