angular material 当输入字段中有值时,optionSelected 不会触发

angular material optionSelected does not fire when value in input field

我正在使用 angular material 具有自动完成功能的输入芯片,我的设置有点复杂,如果用户尝试 select 一个尚不存在的选项,则会弹出一个对话框向上,他们可以创建一个新选项并为其创建一个筹码。

问题是当用户输入选项名称的一部分时,例如'we' ,在搜索 'western' 时,向下移动鼠标并单击 'western',即使我认为应该触发 optionSelected,对话框仍然从 matChipInputTokenEnd 内部弹出。

当我使用箭头键 select 'western' 并按回车键而不是使用鼠标时,它按预期工作。 'western' 筹码已创建。

如何使鼠标单击下拉菜单中的选项,其作用与使用箭头突出显示选项并按回车键相同?

编辑: 从 add()

中删除对话框后,问题就消失了

编辑 2:我决定暂时放弃这个功能,我花了整整一周的时间尝试一切,这真的很遗憾,因为我太接近了,如果有某种方法可以检测 matChipInputTokenEnd 是否是通过单击而不是输入触发我可以解决这个问题,如果有人想出一种从 add() 中打开对话框的方法,或者如果有更合适的方法来执行此操作,请告诉我。我能想到的唯一选择是使用示例中的基本 add() 并将 dialogData 添加到数组,然后为父表单 onSubmit 弹出一个对话框以填写其余的项目详细信息,但我不喜欢用户体验,所以我不确定它是否值得。

模板

   <mat-form-field class="full-width">
    <mat-chip-list #modifierChipList>
      <mat-chip
        *ngFor="let modifier of selectedModifiers"
        [selectable]="selectable"
        [removable]="removable"
        (removed)="remove(modifier,'selectedModifiers','modifierList')">
        {{modifier}}
        <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
      </mat-chip>
      <input
        (click)="focusOut()"
        placeholder="Modifiers"
        #modifierInput
        [formControl]="modifierCtrl"
        [matAutocomplete]="autoModifier"
        [matChipInputFor]="modifierChipList"
        [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
        [matChipInputAddOnBlur]="addOnBlur"
        (matChipInputTokenEnd)="add($event, 'selectedModifiers', 'modifierList', 'modifierCtrl', 'modifier', filteredModifiers)">
    </mat-chip-list>
    <mat-autocomplete #autoModifier="matAutocomplete" (optionSelected)="selected($event, 'selectedModifiers', 'modifierInput', 'modifierCtrl', 'modifier')">
      <mat-option *ngFor="let modifier of filteredModifiers | async" [value]="modifier">
        {{modifier}}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>

ts:

add(event: MatChipInputEvent, selectedItems, allItemsList, ctrl, type, filteredList): void { 

    const options = {};

      const input = event.input;
      const value = event.value;

      if(!this.autoModifier.isOpen || this[allItemsList].indexOf(value) < 0 ){


      // Add our new item to both the DB and local selected items
      if ((value || '').trim()) {

        if(!this[allItemsList].includes(value.trim())){

          switch(type){ 
            case 'category': 
              break;

            case 'option': 
              options['Type'] = this.typeList;
              options['Categories'] = this.categoryList;
              break;

            case 'component': 
              options['Options'] = this.optionList;
              options['Categories'] = this.categoryList;
              break;

            case 'modifier':
              options['Additions'] = this.optionList;
              options['Removals'] = this.optionList;
              options['Options'] = this.optionList;
              options['Categories'] = this.categoryList;
              options['Type'] = this.typeList;
              break;

            case 'tax':
              break;

            default:
              break;
          }

          let dialogQuestions = this.service.getQuestions(type, options);
          //open a modal to create that item
          const dialogRef = this.dialog.open(DialogComponent, {
            width: '70%',
            data: {
              dialog: this,
              type: type,
              details:{
                name:value,
                questions: dialogQuestions
              }
            },
          });


          dialogRef.afterClosed().subscribe(result => {
            this[allItemsList].push(result.name);
            this[selectedItems].push(result.name);
            this.autocomplete.closePanel();//this doesnt work for some reason

            //based on the result add it to the allItemsList and the selectedItems
            //also create a chip for it
          });

        }
      }
      // Reset the input value
      if (input) {
        input.value = '';
      }

      this[ctrl].setValue(null);
    }
  }

  selected(event: MatAutocompleteSelectedEvent, selectedItems, inputType, ctrl): void {
    this[selectedItems].push(event.option.viewValue);
    this[inputType].nativeElement.value = '';
    this[ctrl].setValue(null);
  }


整个 ts 文件


import { Component, OnInit, ElementRef, ViewChild, Inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators, NgForm, FormControl } from '@angular/forms';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { ApiService } from '../../api.service';
import { forkJoin, Observable } from 'rxjs';
import { map, startWith, filter } from 'rxjs/operators';
import {  MatChipsModule, MatAutocompleteSelectedEvent, MatChipInputEvent, MatAutocomplete, MatAutocompleteTrigger  } from '@angular/material'
import {COMMA, ENTER} from '@angular/cdk/keycodes';
import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material';
import { DialogComponent } from '../../shared/dialog/dialog.component';
import { DialogQuestionService } from '../../shared/dialog/dialog-question.service';
import { a } from '@angular/core/src/render3';

@Component({
  selector: 'app-item-create',
  templateUrl: './item-create.component.html',
  styleUrls: ['./item-create.component.css'],
  providers:[DialogQuestionService]
})

export class ItemCreateComponent implements OnInit {

  visible = true;
  selectable = true;
  removable = true;
  addOnBlur = true;

  readonly separatorKeysCodes: number[] = [ENTER, COMMA];

  itemCreateForm: FormGroup;

  modifierCtrl = new FormControl();
  optionCtrl = new FormControl();

  filteredModifiers: Observable<string[]>;
  filteredOptions: Observable<string[]>;

  categoryList: any[] = [];
  optionList: any[] = [];       //string[] = ['White Bread', 'Wheat Bread', 'Extra Mayo', 'Mustard', 'Grilled Onions', 'Toasted'];
  modifierList: string[] = [];  //string[] = ['Add Bacon', 'Add Avocado', 'Double Meat', 'Double Cheese'];
  taxList: any;                 //string[] = ['Defaut Tax', 'Dine-in Tax', 'Hot Food Tax'];
  componentList: any;           //string[] = ['White Bread', 'Wheat Bread', 'Mayo', 'Turkey', 'Ham', 'Lettuce', 'Tomato', 'Onions'];
  typeList: any;
  //item = {};
  selectedModifiers: string[] = [];
  selectedOptions: any[] = [];

  pageType = 'Create';
  id = this.route.snapshot.params['id'];
  formData:any;
  //dialogQuestions:any;

  @ViewChild(MatAutocompleteTrigger) autocomplete: MatAutocompleteTrigger;

  @ViewChild('modifierInput') modifierInput: ElementRef<HTMLInputElement>;
  @ViewChild('optionInput') optionInput: ElementRef<HTMLInputElement>;

  // @ViewChild('auto') matAutocomplete: MatAutocomplete; //remove this
  @ViewChild('autoModifier') autoModifier:MatAutocomplete;
  // @ViewChild('autoOption') optionMatAutoComplete: MatAutocomplete;

  constructor(
    public service: DialogQuestionService,
    private router: Router,
    private route: ActivatedRoute,
    private api: ApiService,
    private fb: FormBuilder,
    public dialog: MatDialog
    ){
      //if the appropriate filters list is empty THEN open the modal in add()
    this.filteredModifiers = this.modifierCtrl.valueChanges.pipe( 
      startWith(null),
      map((modifier: string | null) => modifier ? this._filter(modifier, 'modifierList') : this.modifierList.slice() )
    );

    this.filteredOptions = this.optionCtrl.valueChanges.pipe( 
      startWith(null),
      map((option: string | null) => option ? this._filter(option, 'optionList') : this.optionList.slice() )
    );

    this.createForm();
  }

  ngOnInit(){
    this.setupForm(this.id);
  }

  setupForm(id) {
    forkJoin(
      this.api.getAll('Category'),
      this.api.getAll('Modifier'),
      this.api.getAll('Option'),
      this.api.getAll('Tax'),
      this.api.getAll('Component'),
      this.api.getAll('Type')
      ).subscribe(([Categories, Modifiers, Options, Taxes, Components, Types]) => { 
        this.categoryList = Categories.map(c => c.name);
        this.modifierList = Modifiers.map(c => c.name);
        this.optionList = Options.map(c => c.name);
        this.taxList = Taxes.map(c => c.name );
        this.componentList = Components.map(c => c.name);
        this.typeList = Types.map(c => c.name ); 
    });

    if(this.route.snapshot.data.update){
      this.api.get('Item',id).subscribe( item => this.itemCreateForm.patchValue(item) );
      this.pageType = 'Update';
    }
  }

  createForm(){
    this.itemCreateForm = this.fb.group({
      name: '',
      categories: [],
      price: 0, 
      options: [],
      components: [], 
      modifiers: [],
      removals: [], 
      taxes: [],
      description: '', 
      author: '', 
    });
  }

  add(event: MatChipInputEvent, selectedItems, allItemsList, ctrl, type, filteredList): void { 

    const options = {};

      const input = event.input;
      const value = event.value;

      if(!this.autoModifier.isOpen || this[allItemsList].indexOf(value) < 0 ){ // just trying this out for modifiers so far, if it works make a switch or something
      console.log('listy',filteredList, this.autoModifier);



      // Add our new item to both the DB and local selected items
      if ((value || '').trim()) {

        if(!this[allItemsList].includes(value.trim())){
          //in this case it is new

          switch(type){ 

            case 'category': 
              break;

            case 'option': 
              options['Type'] = this.typeList;
              options['Categories'] = this.categoryList;
              break;

            case 'component': 
              options['Options'] = this.optionList;
              options['Categories'] = this.categoryList;
              break;

            case 'modifier':
              options['Additions'] = this.optionList;
              options['Removals'] = this.optionList;
              options['Options'] = this.optionList;
              options['Categories'] = this.categoryList;
              options['Type'] = this.typeList;
              break;

            case 'tax':
              break;

            default:
              break;
          }

          let dialogQuestions = this.service.getQuestions(type, options);
          //open a modal to create that item
          const dialogRef = this.dialog.open(DialogComponent, {
            width: '70%',
            data: {
              dialog: this,
              type: type,
              details:{
                name:value,
                questions: dialogQuestions
              }
            },
          });


          dialogRef.afterClosed().subscribe(result => {
            this[allItemsList].push(result.name);
            this[selectedItems].push(result.name);
            this.autocomplete.closePanel();//this doesnt work for some reason

            //based on the result add it to the allItemsList and the selectedItems
            //also create a chip for it
          });

        }
      }
      // Reset the input value
      if (input) {
        input.value = '';
      }

      this[ctrl].setValue(null);
  }

  remove(item: string, type): void {
    console.log('removing from selected', this, item , type, this[type]);
    const index = this[type].indexOf(item);

    if (index >= 0) {
      this[type].splice(index, 1);
    }
  }

  selected(event: MatAutocompleteSelectedEvent, selectedItems, inputType, ctrl): void {
    console.log('SELECTED HAS FIRED')
    this[selectedItems].push(event.option.viewValue);
    this[inputType].nativeElement.value = '';
    this[ctrl].setValue(null);
  }

  focusOut() {
    //change this to pass in the control as argument
    this.modifierCtrl.disable();
    this.modifierCtrl.enable();
    // this seems to help but now im having issues with focus moving
    // to the first chip when trying to select the input box.
    // its good enough for now
  }

  private _filter(value: string, type): string[] {
    const filterValue = value.toLowerCase();
    return this[type].filter(item => item.toLowerCase().indexOf(filterValue) === 0);
  }

  onFormSubmit(form: NgForm) {
    if (this.pageType === 'Create') {
      console.log('Creating', form)
      this.api.post('Item', form)
        .subscribe(res => {
          console.log('resres',res)
          let id = res['_id'];
          this.router.navigate(['menu/details', id]);
        }, (err) => {
          console.log(err);
        });
    } else if (this.pageType === 'Update') {
      console.log('Updating', form)
      this.api.update('Item', this.id, form)
        .subscribe(res => {
          console.log('resres',res)
          let id = res['_id'];
          this.router.navigate(['menu/details', id]);
        }, (err) => {
          console.log(err);
        });
    }
  }

}


这似乎是该组件的一个非常常见的问题,主要是因为当您按下 ENTER 键时,matChipInputTokenEnd 和 optionSelected 会触发。然后芯片将已经被添加并且输入将没有任何可添加的值。也许这就是为什么您没有获得该方法的回调的原因。无论如何,检查这个答案,它可能会有所帮助:

[

希望对你有帮助,干杯。

我遇到了同样的问题,我通过将 addOnBlur 选项设置为 false 解决了这个问题。 希望这对你有用!