[英]mat autocomplete not filtering properly in angular
I'm customizing angular material select/autocomplete to allow nested dropdowns.我正在自定义 angular 材料选择/自动完成以允许嵌套下拉菜单。
Here, I was trying to search both parent or child values, but it doesn't filtered out.在这里,我试图搜索父值或子值,但它没有被过滤掉。
Also, it should display like Alaska (+2 others).此外,它应该像阿拉斯加(+2 其他)一样显示。
<mat-form-field appearance="fill">
<mat-label>Toppings</mat-label>
<!-- <input type="text" matInput placeholder="States Group" formControlName="stateGroup" required [matAutocomplete]="autoGroup">
<mat-autocomplete #autoGroup="matAutocomplete"> -->
<input type="text" matInput placeholder="Select Users" aria-label="Select Users" matInput [matAutocomplete]="auto" [formControl]="states">
<mat-autocomplete #auto="matAutocomplete">
<!-- <mat-select [formControl]="states" multiple> -->
<mat-select-trigger>
{{states.value ? states.value[0] : ''}}
<span *ngIf="states.value?.length > 1" class="example-additional-selection">
(+{{states.value.length - 1}} {{states.value?.length === 2 ? 'other' : 'others'}})
</span>
</mat-select-trigger>
<mat-optgroup *ngFor="let group of stateList">
<div>
<mat-checkbox [checked]="group.selected" (change)="toggleParent($event, group)"
(click)="$event.stopPropagation()">
{{group.letter}}
</mat-checkbox>
<button mat-button (click)="expandDocumentTypes(group)">
<mat-icon>keyboard_arrow_down</mat-icon>
</button>
</div>
<mat-option *ngFor="let name of group.names" [value]="name"
[ngClass]="isExpandCategory[group.letter] ? 'list-show' : 'list-hide'">
<mat-checkbox [checked]="group.checked" (change)="toggleSelection($event, name, group)"
(click)="$event.stopPropagation()">
{{name.type}}
</mat-checkbox>
</mat-option>
</mat-optgroup>
<!-- </mat-select> -->
</mat-autocomplete>
<!-- </mat-autocomplete> -->
</mat-form-field>
export class SelectCustomTriggerExample {
constructor(private _formBuilder: FormBuilder){}
// stateForm: FormGroup = this._formBuilder.group({
// stateGroup: '',
// });
// toppings = new FormControl();
isExpandCategory: boolean[] = [];
toppingList: string[] = ['Extra cheese', 'Mushroom', 'Onion', 'Pepperoni', 'Sausage', 'Tomato'];
stateRecord:any = [];
states = new FormControl();
expandDocumentTypes(group: any) {
console.log("expanding dropdown", group);
this.isExpandCategory[group.letter] = !this.isExpandCategory[group.letter];
// expand only selected parent dropdown category with that childs
}
toggleSelection(event:any, name: any, group: any) {
debugger;
console.log("toggleSelection", name, event.checked, group);
if(event.checked) {
console.log("stastateRecordtelist", this.stateRecord);
this.stateRecord.push(name.type);
this.states.setValue(this.stateRecord);
console.log("toggleselection ", this.states.value);
}
else {
this.stateRecord = this.stateRecord.filter((x:any) => x!== name.type);
console.log("else toggleselection", name, group, this.states.value);
this.states.setValue(this.states.value.filter((x:any) => x!== name.type));
console.log("after filter ", this.states.value);
//this.states.setValue([]);
}
}
toggleParent(event: any, group: any) {
debugger;
group.checked = event.checked;
console.log("event", event.checked, "group", group, "states value", this.states.value);
let states = this.states.value;
states = states ? states : [];
if(event.checked) {
states.push(...group.names.filter((x: any) => !states.includes(x.type)).map((x: any) => x.type))
} else {
console.log("else", states);
group.names.forEach((x: any) => {
if(states.indexOf(x.type) > -1) {
states.splice(states.indexOf(x.type), 1)
}
});
}
this.states.setValue(states);
console.log("statesvalue", this.states.value);
if(!event.checked) {
this.states.setValue(this.states.value.filter((x:any) => !x.includes(group.names)))
//this.states.setValue([]);
}
console.log("final statesvalue", this.states.value);
this.stateRecord = this.states.value;
}
stateList = [
{
"letter":"A",
"checked":false,
"names":[
{
"id":1,
"type":"Alabama"
},
{
"id":2,
"type":"Alaska"
},
{
"id":3,
"type":"Arizona"
},
{
"id":4,
"type":"Arkansas"
}
]
},
{
"letter":"C",
"checked":false,
"names":[
{
"id":8,
"type":"California"
},
{
"id":9,
"type":"Colorado"
},
{
"id":10,
"type":"Connecticut"
}
]
},
{
"letter":"D",
"checked":false,
"names":[
{
"id":18,
"type":"Delaware"
},
{
"id":19,
"type":"Denwer"
}
]
}
];
}
References:参考:
https://stackblitz.com/edit/angular-evacck-qubgyy https://stackblitz.com/edit/angular-evacck-qubgyy
https://stackblitz.com/angular/eboprqqnooy https://stackblitz.com/angular/eboprqqnooy
Can someone help on this?有人可以帮忙吗?
This task is quite complex and requires more clarifications and maybe can be solved by some dedicated library like https://www.npmjs.com/package/mat-select-autocomplete这个任务非常复杂,需要更多的说明,也许可以通过一些专用库来解决,比如https://www.npmjs.com/package/mat-select-autocomplete
But I like that you're moving forward with this custom control step by step and want to have something better than you already implemented.但我喜欢你一步一步地使用这个自定义控件,并希望拥有比你已经实现的更好的东西。
Let's try to solve two your issues which you pointed out in this question:让我们尝试解决您在此问题中指出的两个问题:
Angular material allows us to define custom filter . Angular 材料允许我们定义自定义过滤器。 You can use similar logic in your example.
您可以在示例中使用类似的逻辑。 Just create an
Observable
which is listener to FormControl
changes:只需创建一个
Observable
来监听FormControl
的变化:
this.stateGroupOptions$ = this.states.valueChanges.pipe(
startWith(EMPTY_STRING),
...
map(value => this._filterGroup(value))
);
...
private _filterGroup(value: any) {
if (!this.searching || !value.trim()) {
return this.stateList;
}
value = value.trim().toLowerCase();
return this.stateList
.map(group => ({
...group,
names: group.names.filter(item => item.type.toLowerCase().includes(value))
}))
.filter(
group => group.letter.toLowerCase().includes(value) || group.names.length > 0
);
}
and then use it in your options list然后在您的选项列表中使用它
<mat-optgroup *ngFor="let group of stateGroupOptions$ | async">
Alaska (+2 others)
.Alaska (+2 others)
一样显示。 Again, there is dedicated functionality for this case - displayWith
input property同样,这种情况有专门的功能 -
displayWith
input 属性
@Input() displayWith: ((value: any) => string) | null
@Input() displayWith: ((value: any) => string) | null
Function that maps an option's control value to its display value in the trigger.@Input() displayWith: ((value: any) => string) | null
Function 将选项的控制值映射到触发器中的显示值。
html html
<mat-autocomplete ... [displayWith]="displayWith"
ts ts
displayWith(names: StateName[]) {
if (!names || !names.length) {
return "";
}
return (
names[0].type +
(names.length > 1
? ` (+${names.length - 1} ${names.length === 2 ? "other" : "others"})`
: "")
);
}
Stackblitz Example Stackblitz 示例
The main trick in this custom implementation is to keep model consistent across different types of data: string
(when user starts typying), Array
(when you click on checkbox and update control manually) and Object
(when you select option from dropdown).此自定义实现中的主要技巧是使 model 在不同类型的数据中保持一致:
string
(当用户开始输入时)、 Array
(当您单击复选框并手动更新控件时)和Object
(当您从选项下拉列表中选择 Z99938Z4182F04016EFCF429 时)。
NOTE: you can still optimize it and there are maybe many issue you haven't discovered yet .注意:您仍然可以对其进行优化,并且可能还有很多您尚未发现的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.