angularangular-materialangular-filtersviewchildmat-select

Angular Dropdown Filter - Button


I require dropdown filters, that use Mat-Select directives, to be invoked and displayed by a button instead of the input field. Also i want to hide the input field so that only the button is visible and therefore invokes the filter, much like the below filter that can be found in Azure DevOps portal:

Azure DevOps Example

It has to be a CDK overlay panel, as i dont want the content of my screen to be shifted whenever the filters are opened.

I have tried using the Mat-Select in conjunction with programmatic code to simulate open and closing the Mat-Select dropdown. This doesnt work as when the input field is hidden, even though its opening and closing it, it still remains hidden.

I've also tried only hiding some aspects of the Mat-Select directive, but for some reason the css styling doesnt take effect.

I have gotten it to the point where the below works, all thats left to do is hide the right hand side input, and when the button is clicked, the dropdown needs to appear below the button:

Current Filter State.

Just a side note, all of these filter options are dynamic and as part of a re-usable component that can be injected into any other HTML, and as long as it is fed a specific config, it will generate and emit the filters.

Below is an example of both the HTML and TS code for this component:

HTML:

<mat-card class="reusable-filter-mat-card">
    <mat-card-content *ngIf="dropDownFilterConfig || checkBoxFilterConfig" class="parent-filter-style">
        <ng-container *ngFor="let checkbox of checkBoxFilterConfig.config">
          <button mat-button (click)="toggleCheckBox()">
            <mat-icon class="material-symbols-outlined">instant_mix</mat-icon>
            {{checkbox.title}}
          </button>
          <mat-form-field>
            <mat-select mat-button [(value)]="selectedCheckbox" multiple hidden="true" #checkBoxFilter>
              <ng-container *ngFor="let option of checkbox.data">
                <mat-option [value]="option" (click)="onSelectCheckbox(option)">
                  <div *ngIf="checkbox.displayProperty; else stringProperty">
                    {{option[checkbox.displayProperty]}}
                  </div>
                  <ng-template #stringProperty>
                    {{option}}
                  </ng-template>
                </mat-option>
              </ng-container>
            </mat-select>
          </mat-form-field>
        </ng-container>
    </mat-card-content>
</mat-card>

TS:

import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { CheckBoxFilterConfig, DropDownFilterConfig, FilterOutput } from './reusable-filter.interface';
import { MatSelect } from '@angular/material/select';

@Component({
  selector: 'reusable-filter',
  templateUrl: './reusable-filter.component.html',
  styleUrls: ['./reusable-filter.component.scss']
})
export class ReusableFilterComponent implements OnInit {
  // Input and Output
  @Input() dropDownFilterConfig!        : DropDownFilterConfig
  @Input() checkBoxFilterConfig!        : CheckBoxFilterConfig
  @ViewChild(MatSelect) checkBoxFilter! : MatSelect;
  @Output() FilterOutput                = new EventEmitter<any>();

  // Class Variables
  isCheckBoxFilterOpen = false
  isDropDownFilterOpen = false
  selectedDropDown!: any
  selectedCheckbox: any[] = []
  appliedFilters: FilterOutput = {
    dropDownFilter: [],
    checkBoxFilter: []
  }

  ngOnInit(): void {
    this.dropDownFilterConfig = {
      enabled : true,
      config  : [
      {
        title : 'Personnel Filter',
        data  : [
        {
          name: 'John Kinsington',
          title: 'Infrastructure Engineer'
        },
        {
          name: 'Susan Burlsworth',
          title: 'Infrastructure Engineer'
        },
        {
          name: 'Macy McCready',
          title: 'Infrastructure Engineer'
        },
        {
          name: 'Mike Rinsworth',
          title: 'Infrastructure Engineer'
        }],
        displayProperty: 'name'
      }]
    }

    this.checkBoxFilterConfig = {
      enabled : true,
      config  : [
      {
        title : 'Video Games',
        data  : [
        {
          name: 'Call of Duty',
          rating: 5
        },
        {
          name: 'Satisfactoy',
          rating: 8.5
        },
        {
          name: 'Dota 2',
          rating: 9
        },
        {
          name: 'Medal of Honour',
          rating: 7.2
        }],
        displayProperty: 'name'
      }]
    }

  }

  onSelectDropdown(event: any){
    console.log(event)
    this.appliedFilters.dropDownFilter = event
    console.log(this.appliedFilters)
    this.isDropDownFilterOpen = !this.isDropDownFilterOpen
  }

  onSelectCheckbox(event: any){
    this.appliedFilters.checkBoxFilter = event
    console.log(this.appliedFilters)
  }

  toggleDropDown(){
    this.isDropDownFilterOpen = !this.isDropDownFilterOpen;
  }

  toggleCheckBox(){
    this.checkBoxFilter.toggle()
  }

  toggleOption(option: any) {
    if (this.isSelected(option)) {
      this.selectedCheckbox = this.selectedCheckbox.filter(selected => selected !== option);
    } else {
      this.selectedCheckbox.push(option);
    }
  }

  isSelected(option: any): boolean {
    return this.selectedCheckbox.includes(option);
  }

  emitSelectedFilters(){
    this.FilterOutput.emit(this.appliedFilters)
  }

}

Any Ideas or Suggestions would be greatly appreciated!


Solution

  • For anyone looking for a way to make a custom dropdown,i found a fantastic article by Ertunga Bezirgan that already solved this issue:

    https://medium.com/codeshakeio/build-a-dropdown-component-using-angular-cdk-fa45455e6a73