尝试使用叠加层中的垫选择列表制作自定义多选搜索组件

时间:2019-07-18 14:20:24

标签: angular angular-material overlay angular8 angular-cdk

我正在尝试制作一个自定义Angular Material multiSelect可过滤组件,例如以下组件: image

我用以下代码制作了一个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元素时,出现以下错误: image

我不明白为什么ComponentPortal创建的组件不能在数组上使用ngFor?

再生产

使用StackBlitz演示您要执行的操作: 完整的错误和代码在这里: https://components-issue-55qrra.stackblitz.io/

环境
  • 角度:8.1.1
  • CDK /材料:8.0.2
  • 浏览器:Chrome
  • 操作系统Windows

我在传递的console.log对象中放入了data,我可以看到它确实是一个Array: image

进入堆叠突击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.

1 个答案:

答案 0 :(得分:0)

从角度材料支持中得到了这个答案:

  

此问题的根本原因是为叠加组件提供了一个不包含differs的{​​{1}}的注入器。这是因为您将叠加层创建为一个组件,而没有任何具有这些ngFor的父注入器。   在differs中,创建multi-select-search.component.ts时,请确保使用ComponentPortal包含当前的进样器:

PortalInjector

希望这可以帮助某人!