Angular Dropdown Filter - Button

112 Views Asked by At

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!

2

There are 2 best solutions below

0
Andre du Toit On BEST ANSWER

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

1
Kylian19 On

I see if you try to create dropdown filters triggered by a button, like you saw in Azure DevOps, then hide the input field. I have encountered similar needs in my industry, and I think I can help you.

First, since you want the dropdown to be opened with a button and not a normal input field, you will need to use an Angular ViewChild to programmatically control the opening of the mat-select and, hide the input field but retain functionality the , you will need to use some CSS tricks can . Here’s a way to work around it:

Update HTML: Modify your existing HTML to include a button that will be responsible for rotating the underlying content. Make sure your mat-select has a reference to the template variable, which we will use in the TypeScript file.

<button mat-button(click)="toggleCheckBox()">{{box.title}}</button>
<mat-form-field style="display:none;">
    <mat - select # checkBoxFilter >
        <!-- Options go here -->
    </mat-select> do
</mat-context>

Component logic: Use ViewChild to get a reference to MatSelect in your component. You can then use this context to open and close the dropdown programmatically.

@ViewChild('checkBoxFilter') checkbox: MatSelect;

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

CSS Styling: To ensure that only the button is displayed and the mat-selection remains hidden, use a simple CSS style such as display: none; It's going to be the field that I cry for.

Position of the dropdown: Since you mentioned that you don’t want the content of your screen to change when the filters are open, you might consider using the Overlay panel of the Angular CDK to position your dropdown to your liking this enables determine where the dropdown appears relative to the button.

Clicking the button with this step will make your dropdown filter visible. This option ensures that the actual mat-select remains saved and does not affect your settings, while still being active when the button is clicked.

I hope this helps!