尝试在叠加层中使用 mat-select-list 制作自定义多 select 搜索组件
Trying to make a custom multi select search component with mat-select-list in overlay
我正在尝试制作自定义 Angular Material multiSelect 可过滤组件,例如这个:
我用这个代码做了一个多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 创建的组件无法在 Array 上使用 ngFor?
再生产
使用 StackBlitz 来演示您正在尝试做什么:
完整的错误和代码在这里:
https://components-issue-55qrra.stackblitz.io/
环境
- Angular: 8.1.1
- CDK/Material: 8.0.2
- 浏览器:Chrome
- 操作系统Windows、
我把 data
对象的 console.log
传过去了,我可以看到它确实是一个数组:
前往 stackblitz 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.
从 angular material 支持得到这个答案:
The root cause of the issue is that the overlay component is being given an injector that does not contain differs
for ngFor
. This is because you created the overlay as a component without any parent injector that had these differs
.
In multi-select-search.component.ts
, when you create the ComponentPortal
, be sure to include the current injector using a PortalInjector
:
import { PortalInjector } from '@angular/cdk/portal';
...
const tokens = new WeakMap();
tokens.set(MULTI_SELECT_OVERLAY_DATA, {
options: this.listOptions,
});
const injector = new PortalInjector(this.injector, tokens);
...
const multiSelectPortal = new ComponentPortal(MultiSelectOverlayComponent, null, injector);
希望这可以帮助到别人!
我正在尝试制作自定义 Angular Material multiSelect 可过滤组件,例如这个:
我用这个代码做了一个多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 创建的组件无法在 Array 上使用 ngFor?
再生产使用 StackBlitz 来演示您正在尝试做什么: 完整的错误和代码在这里: https://components-issue-55qrra.stackblitz.io/
环境- Angular: 8.1.1
- CDK/Material: 8.0.2
- 浏览器:Chrome
- 操作系统Windows、
我把 data
对象的 console.log
传过去了,我可以看到它确实是一个数组:
前往 stackblitz 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.
从 angular material 支持得到这个答案:
The root cause of the issue is that the overlay component is being given an injector that does not contain
differs
forngFor
. This is because you created the overlay as a component without any parent injector that had thesediffers
. Inmulti-select-search.component.ts
, when you create theComponentPortal
, be sure to include the current injector using aPortalInjector
:
import { PortalInjector } from '@angular/cdk/portal';
...
const tokens = new WeakMap();
tokens.set(MULTI_SELECT_OVERLAY_DATA, {
options: this.listOptions,
});
const injector = new PortalInjector(this.injector, tokens);
...
const multiSelectPortal = new ComponentPortal(MultiSelectOverlayComponent, null, injector);
希望这可以帮助到别人!