Issue
I have installed ngx-mat-select-search and followed the example from StackBlitz.
The example worked fine, like in stackblitz. Then, I changed the code to use my dynamic data. I loaded the data normally and the values are listed in the UI (the searchble dropdown), but the search bar do nothing and there is no error.
HTML:
<mat-form-field class="selectList-full-width">
<mat-select [formControl]="selectTechnicalRoomCtrl" [placeholder]="'SelectTechnicalRoom' | localize" #singleSelect>
<mat-option>
<ngx-mat-select-search [formControl]="selectTechnicalRoomFilterCtrl" [placeholderLabel]="'Search' | localize"></ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let technicalRoom of technicalRooms" [value]="technicalRoom">
{{technicalRoom.nameRoom}}
</mat-option>
</mat-select>
</mat-form-field>
<div class="p-r-25 btn-toolbar">
<button style="margin-left: 10px; padding: 0 1em;" mat-raised-button color="primary" >{{ 'ListPoints' | localize }}</button>
</div>
TS:
export class HomeComponent extends AppComponentBase implements OnInit, AfterViewInit, OnDestroy {
technicalRooms: IdNameTechnicalRoomDto[] = [];
public selectTechnicalRoomCtrl: FormControl = new FormControl();
public selectTechnicalRoomFilterCtrl: FormControl = new FormControl();
/** list of itens filtered by search keyword */
public filteredTechnicalRooms: ReplaySubject<IdNameTechnicalRoomDto[]> = new ReplaySubject<IdNameTechnicalRoomDto[]>(1);
@ViewChild('singleSelect', { static: true }) singleSelect: MatSelect;
protected _onDestroy = new Subject<void>();
constructor(
injector: Injector,
private _technicalRoomsService: TechnicalRoomServiceProxy,
) {
super(injector);
}
ngOnInit() {
this.list();
// set initial selection
this.selectTechnicalRoomCtrl.setValue(this.technicalRooms[10]);
// load the initial itens list
this.filteredTechnicalRooms.next(this.technicalRooms.slice());
// listen for search field value changes
this.selectTechnicalRoomFilterCtrl.valueChanges
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
this.filterTechnicalRooms();
});
}
list(): void {
this._technicalRoomsService
.getList()
.pipe()
.subscribe(
data => this.technicalRooms = data["result"]
);
}
ngAfterViewInit() {
this.setInitialValue();
}
ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
}
/**
* Sets the initial value after the filteredTechnicalRooms are loaded initially
*/
protected setInitialValue() {
this.filteredTechnicalRooms
.pipe(take(1), takeUntil(this._onDestroy))
.subscribe(() => {
this.singleSelect.compareWith = (a: IdNameTechnicalRoomDto, b: IdNameTechnicalRoomDto) => a && b && a.id === b.id;
});
}
protected filterTechnicalRooms() {
if (!this.technicalRooms) {
return;
}
// get the search keyword
let search = this.selectTechnicalRoomFilterCtrl.value;
if (!search) {
this.filteredTechnicalRooms.next(this.technicalRooms.slice());
return;
} else {
search = search.toLowerCase();
}
// filter the technicalRooms
this.filteredTechnicalRooms.next(
this.technicalRooms.filter(technicalRoom => technicalRoom.nameRoom.toLowerCase().indexOf(search) > -1)
);
}
}
EDIT:
I added *ngIf="technicalRooms"
to a div above mat-form-field
but now I receive the error:
Cannot set property 'compareWith' of undefined
Solution
I managed to solve my problem by following the instructions of [macjohnny] 1, [of that issue on GitHub.] 2
In short, the problem was that I needed to load my filtered list before setting it in the input
.
The solution given in the issue is this:
@Input() set data(data: any[]) {
this._data = data;
// load the initial entity list
this.filteredEntities.next(this.data.slice());
}
get data(): any[] {
return this._data;
}
private _data: any[];
But first I created a component in the angular, according to the idea of the author of issue, whenever I need to use it. I called this component select-search
.
The complete solution looks like this:
select-search.component.html
<mat-form-field *ngIf="entityFilterCtrl">
<mat-select [formControl]="entityCtrl" #singleSelect (selectionChange)="onChange($event)">
<ngx-mat-select-search
[formControl]="entityFilterCtrl"
[placeholderLabel]="'Search'"
[noEntriesFoundLabel]="'Not found'">
</ngx-mat-select-search>
<mat-option *ngFor="let entity of filteredEntities | async" [value]="entity.url">{{entity.name}}</mat-option>
</mat-select>
</mat-form-field>
select-search.component.ts
import { Component, OnInit, ViewChild, Input, EventEmitter, Output } from '@angular/core';
import { MatSelect } from '@angular/material';
import { ReplaySubject, Subject } from 'rxjs';
import { FormControl } from '@angular/forms';
import { takeUntil, take } from 'rxjs/operators';
@Component({
selector: 'app-select-search',
templateUrl: './select-search.component.html',
styleUrls: ['./select-search.component.css']
})
export class SelectSearchComponent implements OnInit {
/** control for the selected entity */
public entityCtrl: FormControl = new FormControl();
/** control for the MatSelect filter keyword */
public entityFilterCtrl: FormControl = new FormControl();
/** list of entities filtered by search keyword */
public filteredEntities: ReplaySubject<any[]> = new ReplaySubject<any[]>(1);
@ViewChild('singleSelect', { static: true }) singleSelect: MatSelect;
/** Subject that emits when the component has been destroyed. */
private _onDestroy = new Subject<void>();
@Input() set data(data: any[]) {
this._data = data;
// load the initial entity list
this.filteredEntities.next(this.data.slice());
}
get data(): any[] {
return this._data;
}
private _data: any[];
@Output() onSelectionChange: EventEmitter<any> = new EventEmitter<any>();
constructor() { }
ngOnInit(): void {
// listen for search field value changes
this.entityFilterCtrl.valueChanges
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
this.filterEntities();
});
}
ngAfterViewInit(): void {
this.setInitialValue();
}
ngOnDestroy(): void {
this._onDestroy.next();
this._onDestroy.complete();
}
onChange($event) {
this.onSelectionChange.emit($event);
}
private setInitialValue() {
this.filteredEntities
.pipe(take(1), takeUntil(this._onDestroy))
.subscribe(() => {
// setting the compareWith property to a comparison function
// triggers initializing the selection according to the initial value of
// the form control (i.e. _initializeSelection())
// this needs to be done after the filteredEntities are loaded initially
// and after the mat-option elements are available
if(this.singleSelect)
this.singleSelect.compareWith = (a: any, b: any) => a.url === b.url;
});
}
private filterEntities() {
if (!this.data) {
return;
}
// get the search keyword
let search = this.entityFilterCtrl.value;
if (!search) {
this.filteredEntities.next(this.data.slice());
return;
} else {
search = search.toLowerCase();
}
// filter the entitys
this.filteredEntities.next(
this.data.filter(entity => entity.name.toLowerCase().indexOf(search) > -1)
);
}
}
To use the component:
<app-select-search class="selectList-full-width" [data]="pokemons"></app-select-search>
[I put it on my GitHub as an example] 3 and also [I imported it into StackBlitz so I could see it working] 4.
Answered By - George Wurthmann
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.