Custom MultiSelect With Searching - Error

24 views Asked by At

In a custom Angular component with a search feature added to a mat-select, the deselection of items is not working as expected. When searching for an item and selecting it, the previous selection becomes empty. Despite attempts to address the issue, the deselection problem persists.

Key Points:

The custom component combines a mat-select with a search feature. After searching and selecting an item, the previous selection is not cleared. Various approaches to fix the deselection issue have been attempted without success. Action Needed: Investigate and address the deselection problem in the custom Angular component with a focus on the logic related to item selection, deselection, and the interaction with the search functionality.



export class SearchableMultiselectComponent implements OnInit {
  @ViewChild('search') searchTextBox: ElementRef;
  @Input() items: any[] = [];
  @Input() placeHolder: String = '';
  @Output() selectedItemsChange = new EventEmitter<any[]>();
  @Input() displayMember: string = 'description';

  selectFormControl = new FormControl();
  searchTextboxControl = new FormControl();
  selectedValues: any[] = [];

  filteredOptions: Observable<any[]>;

  constructor(private zone: NgZone) { }
  ngOnInit() {
    console.log('Number of items:', this.items);
    this.initializeFilteredOptions();

  }

  ngAfterViewInit() {
    // Logic that involves async operations
    // Use NgZone to ensure change detection
    this.zone.runOutsideAngular(() => {
      // Perform async operations here
      setTimeout(() => {
        this.zone.run(() => {
          // Update component properties here
          this.initializeFilteredOptions();
        });
      }, 1000);
    });
  }

  ngOnChanges(changes: SimpleChanges) {
    if (changes.items && !changes.items.firstChange) {
      // Handle changes to the items input
      this.initializeFilteredOptions();
    }
  }
  private initializeFilteredOptions() {
    this.filteredOptions = this.searchTextboxControl.valueChanges.pipe(
      startWith<string>(''),
      map(name => this._filter(name))
    );
  }

  getDisplayText(option: any): string {
    if (option && option.hasOwnProperty(this.displayMember)) {
      return option[this.displayMember];
    }
    return '';
  }

  private _filter(name: string): any[] {
    const filterValue = name.toLowerCase();
    this.setSelectedValues();
    this.selectFormControl.patchValue(this.selectedValues);

    return this.items.filter(option => this.filterOption(option, filterValue));
  }

  private filterOption(option: any, filterValue: string): boolean {
    if (typeof option === 'string') {
     
      return option.toLowerCase().includes(filterValue);
    } else if (typeof option === 'object') {
     
      for (const key in option) {
        if (option.hasOwnProperty(key) && typeof option[key] === 'string' && option[key].toLowerCase().includes(filterValue)) {
          return true;
        }
      }
    }
    return false;
  }

  displaySelectedValue(): string {
    if (this.selectFormControl.value && this.selectFormControl.value.length > 0) {
      const firstValue = this.selectFormControl.value[0];
      if (firstValue && this.displayMember in firstValue) {
        return firstValue[this.displayMember];
      } else {
        return 'Default Display';
      }
    } else {
      return '';
    }
  }

  selectionChange(event) {
    console.log('this.selectedValues', event.value);
  }


  openedChange(e) {
    this.searchTextboxControl.patchValue('');
    if (e === true) {
      this.searchTextBox.nativeElement.focus();
    }
  }
  
  isOptionSelected(option: any): boolean {
    return this.selectedValues.some(selectedValue => selectedValue.number === option.number);
  }

  clearSearch(event) {
    event.stopPropagation();
    this.searchTextboxControl.patchValue('');
  }

  setSelectedValues() {
    if (this.selectFormControl.value && this.selectFormControl.value.length > 0) {
      this.selectedValues = this.selectFormControl.value;
    }
    console.log('selectedformControl: ', this.selectFormControl.value);
    this.emitSelectedItems();
  }

  emitSelectedItems() {
    this.selectedItemsChange.emit(this.selectedValues);
  }

}


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ component.html ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
`
<div class="col-md-3">
    <mat-form-field>
        <mat-select (openedChange)="openedChange($event)" [placeholder]="placeHolder" [formControl]="selectFormControl"
            (selectionChange)="selectionChange($event)" multiple>
            
            <div class="select-container">
                <mat-optgroup>
                    <mat-form-field style="width:100%;">
                        <input #search autocomplete="off" placeholder="Search" aria-label="Search" matInput
                            [formControl]="searchTextboxControl">
                        <button [disableRipple]="true" *ngIf="search.value" matSuffix mat-icon-button aria-label="Clear"
                            (click)="clearSearch($event)">
                            <mat-icon>close</mat-icon>
                        </button>
                    </mat-form-field>
                </mat-optgroup>

                <mat-option *ngFor="let option of filteredOptions | async" [value]="option">
                    <div class="option-container" [class.selected]="isOptionSelected(option)">
                        {{ getDisplayText(option) }}
                    </div>
                </mat-option>
            </div>
        </mat-select>
    </mat-form-field>
</div>
`
0

There are 0 answers