代码之家  ›  专栏  ›  技术社区  ›  Bitan Basak

使垫子自动完成功能类似于Bootstrap选择

  •  1
  • Bitan Basak  · 技术社区  · 7 月前

    我正在用Angular开发一个应用程序,我主要使用它 <ng-select> 对于可搜索的下拉菜单,它就像一个魅力。现在我正在将整个应用程序迁移到Angular Material,我遇到了一个问题,试图让Angular Material mat自动完成,就像一个 <ng选择> .

    早些时候,当使用 <ng选择> 编辑现有数据时,我只需要做一个 patchValue 并将选项值设置到控制器中,使其相应的标签出现在其相应的 <ng选择> 。这是select的默认行为。迁移到时 <mat-autocomplete> 它不是这样工作的。我明白我可以使用 <mat-select> 相反,搜索功能对我来说是关键。

    我已经阅读了Angular Material文档,并使用了 displayWith 我可以创造的财产 <垫子自动补全> 工作。尽管如此,我必须将整个选项对象修补到表单控件中,这不是我想要的,因为我的后端不是这样开发的。

    为了让你了解我在说什么,下面是场景的示例代码:

    HTML:

    <form [formGroup]="fruitFormBootstrap">
      <div class="row">
        <div class="col-4">
          <label for="fruits" class="form-label">Fruits</label>
          <select id="fruits" class="form-select" formControlName="fruit">
            @for (fruit of fruits; track $index) {
              <option [value]="fruit.value">{{ fruit.label }}</option>
            }
          </select>
        </div>
      </div>
      <div>
        <button class="btn btn-primary" (click)="submitBootstrapForm()">Submit</button>
      </div>
    </form>
    
    <hr>
    
    <form [formGroup]="fruitFormMaterial">
      <mat-form-field class="example-full-width">
        <mat-label>Fruits</mat-label>
        <input type="text"
               placeholder="Pick one"
               aria-label="Fruits"
               matInput
               formControlName="fruit"
               [matAutocomplete]="auto">
        <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
          @for (fruit of fruits; track fruit.value) {
            <mat-option [value]="fruit">{{fruit.label}}</mat-option>
          }
        </mat-autocomplete>
      </mat-form-field>
      <button mat-raised-button (click)="submitAngularMaterialForm()">Submit</button>
    </form>
    

    TypeScript:

    import {Component, OnInit} from '@angular/core';
    import {MatInputModule} from "@angular/material/input";
    import {MatAutocompleteModule} from "@angular/material/autocomplete";
    import {FormBuilder, FormControl, FormGroup, ReactiveFormsModule} from "@angular/forms";
    import {MatFormFieldModule} from "@angular/material/form-field";
    import {MatButtonModule} from "@angular/material/button";
    import {MatListModule} from "@angular/material/list";
    import {MatSelectModule} from "@angular/material/select";
    
    @Component({
      selector: 'app-root',
      standalone: true,
      imports: [
        MatFormFieldModule,
        MatInputModule,
        MatAutocompleteModule,
        MatSelectModule,
        ReactiveFormsModule,
        MatButtonModule,
        MatListModule
      ],
      templateUrl: './app.component.html',
      styleUrl: './app.component.css'
    })
    export class AppComponent implements OnInit {
      fruits: Array<{ value: number, label: string }>;
      fruitFormBootstrap: FormGroup;
      fruitFormMaterial: FormGroup;
    
      constructor(private fb: FormBuilder) {
        this.fruits = [
          { value: 1, label: 'Watermelon' },
          { value: 2, label: 'Apple' },
          { value: 3, label: 'Banana' }
        ];
    
        this.fruitFormBootstrap = this.fb.group({
          fruit: new FormControl()
        });
    
        this.fruitFormMaterial = this.fb.group({
          fruit: new FormControl()
        });
      }
    
      // Patching a value in bootstrap automatically selects its corresponding label for display
      ngOnInit() {
        this.fruitFormBootstrap.patchValue({
          fruit: 2
        });
    
        //angular material autocompelte patching only updates the controller without updating the display to show its corresponding label
    
        // this.fruitFormMaterial.patchValue({
        //   fruit: 2
        // });
    
        const searchFruit = this.fruits.find(fruit => fruit.value === 2);
        //although this works but it sets the entire object as form control value which is not what I want
        this.fruitFormMaterial.patchValue({
          fruit: searchFruit
        })
      }
    
      //submitting a select in bootstrap set the form control with the value of the selected option
      submitBootstrapForm() {
        console.log(this.fruitFormBootstrap);
      }
    
      //Based on angular material documentation this is how to have a separate display value than the control
      displayFn(option: { value: number, label: string }) {
        return option ? option.label : '';
      }
    
      submitAngularMaterialForm() {
        console.log(this.fruitFormMaterial);
      }
    }
    

    在上面的代码中,您可以看到Bootstrap的选择。我只需要修补选项的值,选择就会自动选择相应的标签并显示出来。但事实并非如此 <mat-autocompelete> 。我必须设置整个对象才能正常工作。如果我选择一个选项并单击提交按钮,也会发生同样的事情。Bootstrap select正在将控件设置为值,但 <垫子自动补全> 将其设置为整个选定的水果对象(我想这是意料之中的,因为这是提供给 mat-option [value] 财产约束)。

    我玩过一段时间 垫子选项 [价值] 财产约束和 displayFn 实施,但没有任何效果。当我使用 fruit.value 对于 [价值] 绑定表单控件的属性会使用所选的值而不是整个所选对象进行正确更新,但标签不会显示。

    注意:只需注意,我可以有多个 <垫子自动补全> (s) 在相同的形式中,所有引用不同的数据(但选项结构对所有都是相同的)


    编辑:

    感谢YongSun的回答,它奏效了!但我想我应该提供更多关于我面临的挑战的细节 <垫子自动补全> (s) 以同样的形式。道歉。所以,我有多个 <垫子自动补全> (s) 在后端返回的JSON动态生成的表单中,表单如下:

      <form [formGroup]="dataForm">
        <div formArrayName="forms">
          @for (form of getForms(); track $index) {
            <div [formGroupName]="$index">
              @for (field of fieldsArray; track $index) {
                @if (field.display) {
                  @switch (field.type) {
                    @case ('input') {
                      @switch (field.inputType) {
                        @case ('text') {
                          <mat-form-field appearance="outline" class="w-50 pb-2 pe-2">
                            <mat-label>{{ field.displayName }}</mat-label>
                            <input type="text" matInput [formControlName]="field.controllerName">
                          </mat-form-field>
                        }
                        @case ('number') {
                          <mat-form-field appearance="outline" class="w-50 pb-2">
                            <mat-label>{{ field.displayName }}</mat-label>
                            <input type="number" matInput [formControlName]="field.controllerName">
                          </mat-form-field>
                        }
                        @case ('date') {
                          <mat-form-field appearance="outline" class="w-50 pb-2 pe-2">
                            <mat-label>{{ field.displayName }}</mat-label>
                            <input matInput [matDatepicker]="picker" [formControlName]="field.controllerName">
                            <mat-hint>MM/DD/YYYY</mat-hint>
                            <mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
                            <mat-datepicker #picker></mat-datepicker>
                          </mat-form-field>
                        }
                        @case ('checkbox') {
                          <mat-checkbox [formControlName]="field.controllerName">{{ field.displayName }}</mat-checkbox>
                        }
                      }
                    }
                    @case ('textarea') {
                      <mat-form-field appearance="outline" class="w-100 pb-2 pe-2">
                        <mat-label>{{ field.displayName }}</mat-label>
                        <textarea matInput [formControlName]="field.controllerName"></textarea>
                      </mat-form-field>
                    }
                    @case ('select') {
                      <mat-form-field class="w-50 pb-2 pe-2" appearance="outline">
                        <mat-label>{{ field.displayName }}</mat-label>
                        <input type="text"
                               placeholder="Pick one"
                               matInput
                               [formControlName]="field.controllerName"
                               [matAutocomplete]="auto">
                        <mat-autocomplete requireSelection #auto="matAutocomplete" [displayWith]="displayFn">
                          @for (option of filteredOptions.get(field.controllerName)! | async; track option) {
                            <mat-option [value]="option">{{ option.label }}</mat-option>
                          }
                        </mat-autocomplete>
                      </mat-form-field>
                    }
                  }
                }
              }
            </div>
          }
        </div>
      </form>
    

    因此,尽管 显示器Fn Yong-Shun在回答中提供的实现是正确的,但我很难弄清楚如何通过 formControlName 更改了该选项,以便我可以在从中获取选项值列表值后执行查找操作 filteredOptions 地图与 表单控制名称 :

    filteredOptions: Map<string, Observable<Option[]>>;
    
    1 回复  |  直到 7 月前
        1
  •  1
  •   Yong Shun    7 月前

    您可以修改 displayFn 通过基于以下内容进行搜索来显示所选选项的标签 value fruits 阵列。请注意,您需要将其更改为箭头函数来访问组件变量。

    displayFn = (value: string) => {
      if (Number.isNaN(value)) return value;
    
      return (
        this.fruits?.find((fruit) => fruit.value === Number(value))?.label ||
        value
      );
    };
    

    在你看来,通过 fruit.value 而不是 fruit 反对 [value] 属性。这样,您就可以用以下命令修补表单控件值 水果价值 而不是 水果 对象。

    <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
      @for (fruit of fruits; track fruit.value) {
        <mat-option [value]="fruit.value">{{ fruit.label }}</mat-option>
      }
    </mat-autocomplete>
    

    要从视图传递选项,请执行以下操作:

    displayFn(options: any[]): (value: any) => string {
      return (value: any) => {
        if (Number.isNaN(value)) return value;
    
        return (
          options.find((option) => option.value === Number(value))?.label || value
        );
      };
    }
    
    <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn(fruits)">
      @for (fruit of fruits; track fruit.value) {
        <mat-option [value]="fruit.value">{{ fruit.label }}</mat-option>
      }
    </mat-autocomplete>
    

    Demo @ StackBlitz

    推荐文章