【发布时间】:2020-06-16 23:13:46
【问题描述】:
我正在使用带有自动完成功能的角度材料输入芯片,我有一些复杂的设置,如果用户尝试选择一个尚不存在的选项,则会弹出一个对话框,他们可以创建一个新选项并为它创建了一个芯片。
问题是当用户输入选项名称的一部分时,例如'we' 在搜索 'western' 时,向下移动鼠标并单击 'western',即使我认为应该触发 optionSelected,该对话框仍然会从 matChipInputTokenEnd 内部弹出。
当我使用箭头键选择“西方”并按 Enter 而不是使用鼠标时,它按预期工作。创建了一个“西方”芯片。
我如何让鼠标点击下拉列表中的选项,与使用箭头突出显示选项并按 Enter 相同?
编辑: 当从 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);
});
}
}
}
【问题讨论】:
-
你能把你所有的 ts 文件都质疑一下吗?
-
喜欢发布整个 ts 文件吗?当然可以,但是所有相关的东西都在这里,而 ts 文件就像 300 行凌乱的代码
-
filteredModifiers 的至少值
-
我试着为你清理一下,希望不会太糟糕