【问题标题】:Angular ngx-mat-select-search Custom ComponentAngular ngx-mat-select-search 自定义组件
【发布时间】:2024-03-09 11:55:01
【问题描述】:

我正在尝试使用 ngx-mat-select-search 组件在我的应用程序中放置一个带有搜索栏的 mat-select 样式下拉菜单。 https://www.npmjs.com/package/ngx-mat-select-search

我的下拉菜单工作正常,但我正在尝试将其转换为自定义指令,然后我可以在整个应用程序的多个页面上调用和重用该指令。

到目前为止,我有这个:site-dropdown-component.ts

import {AfterViewInit, Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {FormControl} from '@angular/forms';
import {ReplaySubject, Subject} from 'rxjs';
import {MatSelect} from '@angular/material';
import {take, takeUntil} from 'rxjs/operators';

@Component({
  selector: 'app-site-dropdown',
  template: `
    <mat-form-field class="w-100">
      <mat-select [formControl]="siteCtrl" placeholder="Site" #singleSelect>
        <mat-option>
          <ngx-mat-select-search [formControl]="siteFilterCtrl" [placeholderLabel]="'Search Sites...'"></ngx-mat-select-search>
        </mat-option>
        <mat-option *ngFor="let site of filteredSites | async" [value]="site">{{site.name}}</mat-option>
      </mat-select>
    </mat-form-field>
  `
})
export class SiteDropdownComponent implements OnInit, OnDestroy, AfterViewInit {
  /** list of sites */
  protected sites: Site[] = SITES;

  /** control for the selected site */
  public siteCtrl: FormControl = new FormControl();

  /** control for the MatSelect filter keyword */
  public siteFilterCtrl: FormControl = new FormControl();

  /** list of sites filtered by search keyword */
  public filteredSites: ReplaySubject<Site[]> = new ReplaySubject<Site[]>(1);

  @ViewChild('singleSelect') singleSelect: MatSelect;

  /** Subject that emits when the component has been destroyed. */
  protected onDestroy = new Subject<void>();
  constructor() { }

  ngOnInit(): void {
    // set initial selection
    this.siteCtrl.setValue(this.sites);
    // load the initial site list
    this.filteredSites.next(this.sites.slice());
    // listen for search field value changes
    this.siteFilterCtrl.valueChanges
      .pipe(takeUntil(this.onDestroy))
      .subscribe(() => {
        this.filterSites();
      });
  }

  ngAfterViewInit(): void {
    this.setInitialValue();
  }

  ngOnDestroy(): void {
    this.onDestroy.next();
    this.onDestroy.complete();
  }

  /**
   * Sets the initial value after the filteredBanks are loaded initially
   */
  protected setInitialValue() {
    this.filteredSites
      .pipe(take(1), takeUntil(this.onDestroy))
      .subscribe(() => {
        // setting the compareWith property to a comparison function
        // triggers initializing the selection according to the initial value of
        // the form control (i.e. _initializeSelection())
        // this needs to be done after the filteredBanks are loaded initially
        // and after the mat-option elements are available
        this.singleSelect.compareWith = (a: Site, b: Site) => a && b && a.id === b.id;
      });
  }

  protected filterSites() {
    if (!this.sites) {
      return;
    }
    // get the search keyword
    let search = this.siteFilterCtrl.value;
    if (!search) {
      this.filteredSites.next(this.sites.slice());
      return;
    } else {
      search = search.toLowerCase();
    }
    // filter the sites
    this.filteredSites.next(
      this.sites.filter(site => site.name.toLowerCase().indexOf(search) > -1)
    );
  }
}


export interface Site {
  id: string;
  name: string;
}

export const SITES: Site[] = [
  {id: 'site1', name: 'Site 1'},
  {id: 'site2', name: 'Site 2'},
  {id: 'site3', name: 'Site 3'},
];

对于我尝试使用它的组件,我有:

<app-site-dropdown formControlName="site"></app-site-dropdown>

在组件类里面我有一个表单:

this.mySearchForm = this.formBuilder.group( {
  site: []
}); 

我可以很好地查看下拉列表并与之交互,但是当我提交表单时,我无法获得所选选项的值。当我尝试mySearchForm.controls['site'].value时,它总是返回null

我缺少什么能够注入我的自定义下拉组件并在提交表单时检索其值?

更新:

我能够通过执行以下操作使其工作:

site-dropdown.component.ts里面,我变了

protected siteCtrl: FormControl;

@Input() siteCtrl: FormControl;

在我的 html 中使用自定义下拉菜单,我添加了:

<app-site-dropdown [siteCtrl]="myForm.get('site')"></app-site-dropdown>

这使我可以在提交时将所选值保存到我的表单中。

【问题讨论】:

  • 这正是我一直在寻找的,非常感谢!我实现了服务器端搜索,所以我在 init 之后取出了 setInitialValue() 调用,只是传入了初始值,所以我可以让它显示,但是在你点击它之后会搜索,但它工作得很好。跨度>

标签: javascript angular typescript components


【解决方案1】:

您可以通过让您的SiteDropdownComponent 实现ControlValueAccessor 接口来获取所选选项的值,如下所示,导致您的SiteDropdownComponent 表现为表单控件并允许使用例如访问该值&lt;app-site-dropdown formControlName="site"&gt;&lt;/app-site-dropdown&gt;:

...
import { forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'app-site-dropdown',
  template: ...
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => SiteDropdownComponent),
      multi: true
    }
  ],
})
export class SiteDropdownComponent implements OnInit, OnDestroy, AfterViewInit, ControlValueAccessor {
  ...

  onChange: Function = (_: any) => {};
  onTouched: Function = (_: any) => {};

  constructor() { }

  ngOnInit() {
    ...
    // call this.onChange to notify the parent component that the value has changed
    this.siteCtrl.valueChanges
      .pipe(takeUntil(this.onDestroy))
      .subscribe(value => this.onChange(value))
  }

  writeValue(value: string) {
    // set the value of siteCtrl when the value is set from outside the component
    this.siteCtrl.setValue(value);
  }

  registerOnChange(fn: Function) {
    this.onChange = fn;
  }

  registerOnTouched(fn: Function) {
    this.onTouched = fn;
  }

}

参见例如https://github.com/bithost-gmbh/ngx-mat-select-search/blob/d7ea78d511bbec45143c58c855f013a44d0d5055/src/app/mat-select-search/mat-select-search.component.ts#L134

【讨论】:

  • 我已经从 ng-mat-select-search 实现了问题部分给出的解决方案,它正在工作。但是我怎样才能使它对几个选择框通用?为此,我需要像 SITES 一样每次发送具有不同值的数组:[]。我可以为我的选择框使用带有不同数组的 site-dropdown-component.ts 吗?
  • 地区:地区[],国家:国家[]
  • @RahimjonRustamov 您可以传递任何数组 (items: any[]) 甚至更好,使用泛型类型 class SiteDropdownComponent&lt;T&gt; 并在 SiteDropdownComponent 中使用类型 T 而不是 Sites