Angular-Material在有form表单的弹窗中实现MatAutoComplete组件

283 阅读2分钟

最近产品经理提了一个需求,想要一个有自动匹配输入框的弹窗,否则只有下拉框,添加用户的时候需要翻一百多个选项太痛苦了。
由于我使用的是一个动态弹窗,可以根据不同的场景进行DIY弹窗的内容和样式(例如input,select,checkebox等)所以我想把autoComplete也加进去,相当于新增一种可选择的组件。
但我发现autoComplete是必须使用一个带有默认字段的formControl,而弹窗则是使用一个form表单数组进行回传数据的,无法设置默认值。
所以我的思路是用一个单独的formControl进行取值,最后将选中的选项值放进form表单中回传数据。
并且还需要注意的是中间需要新加一些判断,防止已经调用弹窗的页面报错。

以下是实现代码:
html部分:

<div *ngIf="otherColumn" class="p-24 pb-0 m-0" fusePerfectScrollbar>
    <ng-container *ngFor="let item of otherColumn">
        <!-- 使用matAutocomplete前,请确保你传的数组是object数组且只有两个键:type(下拉框显示的文本)和value(下拉框选中的值) -->
        <div *ngSwitchCase="'autoComplete'">
          <mat-form-field appearance="outline">
            <mat-label>{{ item.label }}</mat-label>
            <input type="text" placeholder="" aria-label="" matInput [formControl]="autoCompleteResult"
              [matAutocomplete]="auto">
            <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
              <mat-option *ngFor="let option of filteredOptions | async" [value]="option">
                {{ option.type }}
              </mat-option>
            </mat-autocomplete>
          </mat-form-field>
        </div>
      </ng-container>
    </ng-container>
  </div>
  
  <div mat-dialog-content class="p-24 pb-0 m-0" fusePerfectScrollbar #DialogContent>
    <form [formGroup]="formData">
      <div *ngFor="let form of formColumn; let i=index" fxLayout="column" fxLayoutAlign="start start">
        ……
      </div>
    </form>
  </div>

ts部分:

先定义需要的数组或者字段

formColumn: any[] = []; // 表单项
autoCompleteResult = new FormControl({ type: null, value: null }); // 自动匹配框选中的值
autoCompleteArray: any[] = []; // 用于接收下拉框数组
autoCompleteValue: any = ''; // 自动匹配框回传数据的键

创建form表单时提前设置对应的字段(没有字段的话无法将数据放进去)

/**
   * 创建表单
   * @returns {FormGroup}
   */
  createNewForm(): FormGroup {
    const formObj = {};
    this.formColumn.forEach(item => {
      formObj[item.value] = [this.formInit[item.value]];
    });
    // 创建表单时判断otherColumn是否为空(避免otherColumn为空时报错)
    if (isNotNullOrUndefined(this.otherColumn)) {
      // 循环otherColumn判断是否有autoComplete
      this.otherColumn.forEach(index => {
        if (index['type'] === 'autoComplete') {
          // 如果有autoComplete,在创建表单的时候提前赋予一个键
          formObj[index['value']] = [];
          // 赋值给一个提前准备好的字段,避免后续回传弹窗数据的时候需要再进行一次forEach
          this.autoCompleteValue = index['value'];
        }
      });
    }
    // 使表单的formControl值等于默认值defaults
    this.formColumn.forEach((item, index, value) => {
      // 判断defaults值是否为空,如果为空则不设置默认值
      if (value[index].defaults !== null && value[index].defaults !== '' && value[index].defaults !== undefined) {
        formObj[item.value] = [value[index].defaults];
      }
    });
    return this._formBuilder.group(formObj);
  }

在点击确定的时候,将对应的值放进form表单中再回传数据

// 传值:可通过afterClosed获取值
  saveDialog(): void {
    const form = this.formData.getRawValue();
    const curSrc = this.curImageArr[0];
    this.formColumn.forEach(item => {
      if (item.type === 'time') {
        // form[item.value] = moment(form[item.value]).format('YYYY-MM-DD'); // 时间格式化
        form[item.value] = this.general.formatTimeToDate(form[item.value]);
      } else if (item.detailType === 'file') {
        if (curSrc) {
          // 文件名 curSrc.title
          form['file_name'] = curSrc.title;
          form[item.value] = curSrc.src; // 上传的单个文件
        }
      }
    });

    const autoCompleteResultValue = this.autoCompleteResult.value;
    // autoCompleteValue有赋值则说明otherColumn不为空且有autoComplete组件
    if (isNotNullOrUndefined(this.otherColumn) && this.autoCompleteValue !== '') {
      form[this.autoCompleteValue] = autoCompleteResultValue;
    };
    this.matDialogRef.close({ form });
  }

最后就是一些其他部分的代码,构建autoComplete需要的数组等

ngOnInit(): void {
    // 构建自动匹配框所用的数组
    if (isNotNullOrUndefined(this.otherColumn)) {
      this.otherColumn.forEach(index => {
        if (index['type'] === 'autoComplete') {
          this.autoCompleteArray = index['selectArr'];
          this.getList(this.autoCompleteArray);
        }
      });
    }
  }

  getList(List): void {
    this.filteredOptions = this.autoCompleteResult.valueChanges.pipe(
      startWith(''),
      map(value => (typeof value === 'string' ? value : value.type)),
      map(name => (name ? this._filter(name) : List.slice())),
    );

  }

  private _filter(name: string): AutoArray[] {
    const filterValue = name.toLowerCase();

    return this.autoCompleteArray.filter(option => option.type.toLowerCase().includes(filterValue));
  }

  displayFn(array: AutoArray): string {
    return array && array.type ? array.type : '';
  }