我正在尝试制作一个自定义Angular Material multiSelect可过滤组件,例如以下组件:
我用以下代码制作了一个multi-select-search.component:
export interface MultiSelectSearchOption {
label: string;
value: any;
}
export interface MultiSelectOverlayData {
options: MultiSelectSearchOption[];
}
export const MULTI_SELECT_OVERLAY_DATA = new InjectionToken<
MultiSelectOverlayData
>('MULTI_SELECT_OVERLAY_DATA');
//TEXT INPUT COMPONENTS
@Component({
selector: 'multiSelectSearch',
templateUrl: 'multiSelectSearch.component.html',
styleUrls: ['./multiSelectSearch.component.scss'],
})
export class MultiSelectSearchComponent implements AfterViewInit {
@Input() list: any[] = [];
@Input() selection: any;
/**
* @param filterKey
* @description the key of the object to filter out with the text input
*/
@Input() filterKey: string;
/**
* @param labelKey
* @description the key of the object to be used as label of option
*/
@Input() labelKey: string;
@Input() placeholder: string;
@Output() valueChange = new EventEmitter<any[]>();
@ViewChild('input', { static: false }) inputViewRef: ElementRef;
public search = new FormControl('');
private listOptions: MultiSelectSearchOption[] = [];
private overlayRef: OverlayRef;
constructor(private overlay: Overlay) {}
ngAfterViewInit() {
if (!this.list.length || !this.labelKey || !this.filterKey) {
console.error(
'Component usage require input of list, labelKey, filterKey component'
);
throw new Error();
} else {
this.search.valueChanges
.pipe(debounceTime(1000))
.subscribe(search => {
this.listOptions = (!search.length
? this.list
: this.list.filter(e =>
e[this.filterKey]
.toString()
.toUpperCase()
.startsWith(search.toUpperCase())
)
).map(
(e: any): MultiSelectSearchOption => ({
label: e[this.labelKey],
value: e,
})
);
const tokens = new WeakMap();
tokens.set(MULTI_SELECT_OVERLAY_DATA, {
options: this.listOptions,
});
this.overlayRef = this.overlay.create({
hasBackdrop: false,
minWidth: '10vw',
minHeight: '10vh',
positionStrategy: this.overlay
.position()
.flexibleConnectedTo(this.inputViewRef)
.withPositions([
{
offsetX: 0,
offsetY: 0,
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'top',
panelClass: [],
weight: 1,
},
]),
});
const multiSelectPortal = new ComponentPortal(
MultiSelectOverlayComponent,
null,
tokens
);
this.overlayRef.attach(multiSelectPortal);
});
}
}
}
// OVERLAY COMPONENT
@Component({
selector: 'multi-select-overlay',
template: `
<mat-card>
<mat-selection-list #list>
<mat-list-option
*ngFor="let option of listOptions"
[value]="option.value"
>{{ option.label }}</mat-list-option
>
</mat-selection-list>
</mat-card>
`,
})
export class MultiSelectOverlayComponent implements AfterViewInit {
@ViewChild('list', { static: false }) list: MatSelectionList;
public get listOptions() {
return this.data.options as MultiSelectSearchOption[];
}
constructor(
@Inject(MULTI_SELECT_OVERLAY_DATA)
private data: MultiSelectOverlayData
) {
console.log('data', data);
}
ngAfterViewInit() {
this.list.selectionChange.pipe(
//emit value
tap(x => console.log(x))
);
}
}
一切似乎都正常,但是当我尝试遍历data.options
元素时,出现以下错误:
我不明白为什么ComponentPortal创建的组件不能在数组上使用ngFor?
再生产使用StackBlitz演示您要执行的操作: 完整的错误和代码在这里: https://components-issue-55qrra.stackblitz.io/
环境我在传递的console.log
对象中放入了data
,我可以看到它确实是一个Array:
进入堆叠突击https://components-issue-55qrra.stackblitz.io/
我
收到错误消息(如Stackblitz所示):
ERROR Error: Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
答案 0 :(得分:0)
从角度材料支持中得到了这个答案:
此问题的根本原因是为叠加组件提供了一个不包含
differs
的{{1}}的注入器。这是因为您将叠加层创建为一个组件,而没有任何具有这些ngFor
的父注入器。 在differs
中,创建multi-select-search.component.ts
时,请确保使用ComponentPortal
包含当前的进样器:
PortalInjector
希望这可以帮助某人!