Filterable Select Dropdown in Angular Material

Currently, I am attempting to build a basic select list using the Angular Material List Module.

However, I am encountering an issue when trying to integrate this with a filter. While I have managed to implement this functionality, I face difficulty in filtering and un-filtering the list without losing track of the selected items within it. This is because during the filtration process, previously selected items are removed when they are "filtered out".

An alternative approach was trying a simple @for loop of mat-checkboxes. Yet, this method requires a costly function to determine the selected status of each item. Something like:

HTML

<mat-form-field class="w-full">
   <mat-label>Filter</mat-label>
   <input matInput formControlName="itemFilter" (ngModelChange)="filterItem($event)" />
</mat-form-field>

<div class="w-full flex-column my-list d-flex align-items-center justify-content-start">
@for(s of filteredOptions; track s){
    <mat-checkbox class="w-full" [checked]="isSelected(s)" (click)="toggleSelected(s)">{{s.name}} ({{s.code}})</mat-checkbox>
}
</div>

Component

filterItem($event: any): void {
    if (!$event) {
      this.filteredOptions = this.origList;
    }
    if (typeof $event === 'string') {
      this.filteredOptions = this.origList.filter((a) =>
        a.name.toLowerCase().startsWith($event.toLowerCase())
      );
    }
  }


isSelected(item: MyItem): boolean {
    return (this.searchForm.get('selectedItems')?.value as MyItem[]).indexOf(item) > -1;
  }

toggleSelectedShed(item: MyItem) {
    const current = this.searchForm.get('selectedItems')
      ?.value as MyItem[];
    if (current.indexOf(item) > -1) {
      // Remove it
      current.splice(current.indexOf(item), 1);
    } else {
      // Add it
      current.push(item);
    }

    this.searchForm.get('selectedItems')?.patchValue(current);
  }

This particular function runs for every individual item during any change.

Is there an alternative solution that allows for filtering without these limitations?

I have abandoned the idea of filtering to focus on a straightforward checkbox array. However, this too poses challenges. Here is a stackblitz link to illustrate my work: Stackblitz to my work

Answer №1

If you're looking for a solution that involves using a FormArray and a custom pipe for filtering, here's a great approach. By storing the selected state within each form array item, you can streamline the process without having to individually check each item's selection status.

Enhance Your Component

const items: Item[] = [
  { id: 1, name: 'Item 1', selected: false },
  { id: 2, name: 'Item 2', selected: false },
  { id: 3, name: 'Item 3', selected: false },
  { id: 4, name: 'Item 4', selected: false },
  { id: 5, name: 'Item 5', selected: false },
];

@Component({
  selector: 'app-select-list',
  standalone: true,
  imports: [FormsModule, ReactiveFormsModule, FilterItemsPipe],
  template: `
    <input type="text" [(ngModel)]="filterText">
    <form [formGroup]="form">
      <ul formArrayName="items">
        @for (item of itemsFormArray.controls | filterItems : filterText; track $index) {
          <li [formGroup]="item">
            <label>
              <input type="checkbox" formControlName="selected">
              {{ item.value.name }}
            </label>
          </li>
        }
      </ul>
    </form>
  `,
})
export class SelectListComponent {
  filterText = '';
  itemsFormArray = new FormArray(items.map((item) => new FormGroup({
    selected: new FormControl(item.selected),
    id: new FormControl(item.id),
    name: new FormControl(item.name),
  })));
  form = new FormGroup({
    items: this.itemsFormArray,
  });
}

Create Your Custom Pipe

@Pipe({
  name: 'filterItems',
  standalone: true,
})
export class FilterItemsPipe implements PipeTransform {

  transform(items: FormGroup[], filterText: string | undefined | null): FormGroup[] {
    if (!filterText) {
      return items;
    }
    return items.filter((item) => (item.value.name ?? '').toLowerCase().includes(filterText.toLowerCase()));
  }

}

Answer №2

My strategy involved creating two separate lists and adjusting the display accordingly:

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'app-select-list-example',
  templateUrl: './select-list-example.component.html',
  styleUrls: ['./select-list-example.component.css']
})
export class SelectListExampleComponent implements OnInit {
  items = [{ name: 'Item 1', code: '001' }, { name: 'Item 2', code: '002' }]; // Sample items array
  filteredItems = [...this.items]; // Initializing filteredItems with all items
  selectForm: FormGroup;

  constructor() {
    // Form initialization with an empty selected items array
    this.selectForm = new FormGroup({
      selectedItems: new FormControl([]),
    });
  }

  ngOnInit() {
    this.applyFilter(''); // Initial application of no filter
  }

  applyFilter(filterValue: string) {
    // Filtering based on the filterValue (name in this case)
    this.filteredItems = this.items.filter(item =>
      item.name.toLowerCase().includes(filterValue.toLowerCase())
    );
  }

  toggleSelected(item: any) {
    const currentSelectedItems = this.selectForm.get('selectedItems')?.value;
    const index = currentSelectedItems.findIndex(selectedItem => selectedItem.code === item.code);
    if (index > -1) {
      // Removing item if already selected
      currentSelectedItems.splice(index, 1);
    } else {
      // Adding item if not selected
      currentSelectedItems.push(item);
    }
    this.selectForm.get('selectedItems')?.setValue([...currentSelectedItems]);
  }

  isSelected(item: any): boolean {
    const currentSelectedItems = this.selectForm.get('selectedItems')?.value;
    return currentSelectedItems.some(selectedItem => selectedItem.code === item.code);
  }
}

Then, here is the corresponding HTML structure:

<div class="w-full flex-column my-list d-flex align-items-center justify-content-start">
      <form [formGroup]="selectForm">
        <div *ngFor="let item of filteredItems">
          <mat-checkbox class="w-full" [checked]="isSelected(item)" (click)="toggleSelected(item)">
            {{item.name}} ({{item.code}})
          </mat-checkbox>
        </div>
      </form>
    </div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

.observe({ action: (response) => { this.updateData = response.Items; }. what comes after this

I need some guidance on what comes next within the callback function .subscribe({ next: (data) => { this.newData = data.Items; } ...

Angular 2: A Beginner's Guide to Creating Objects and Transforming Code from Angular to Angular 2

Currently, I am learning Angular 2 and facing an issue. I am unsure about how to create an object in my login function (Angular1). public logIn() { let phone = this.user.number.replace(/\s+/g, ''); let email = 'u&a ...

Which tool is best suited for generating a diagram within an Angular application?

I am looking to create a project diagram with drag and drop functionality, allowing users to add nodes. I am interested in incorporating a feature similar to the image provided below using Angular9. Can anyone recommend an npm package that is compatible wi ...

What is the best way to position an image in the center of an Ionic 2 application?

Hey there, I've got a few pages in my Ionic 2 app that contain an <ion-img> inside an <ion-content padding> like this: <ion-content padding> <p>Some text here....</p> <p>Some other text here...</p> < ...

Angular2/4 preflight request encountered an invalid response which resulted in a redirect

In my project, I am utilizing angular4 for the frontend and a spring boot application for the backend. The communication between the frontend and BFF (backend for frontend) is done via RESTful requests. When the frontend sends a POST request to the backen ...

Issues with maintaining the checked state of radio buttons in an Angular 4 application with Bootstrap 4

In my Angular 4 reactive form, I am struggling with the following code: <div class="btn-group" data-toggle="buttons"> <label class="btn btn-primary" *ngFor="let item of list;let i=index" > <input type="radio" name="som ...

How can we use Angular Table to automatically shift focus to the next row after we input a value in the last cell of the current row and press the Enter key

When the last cell of the first row is completed, the focus should move to the next row if there are no more cells in the current row. <!-- HTML file--> <tbody> <tr *ngFor="let row of rows;let i=index;" [c ...

Managing multiple child components in a parent component based on certain conditions in Angular

One issue that I am facing is related to loading different child views based on a condition. Everything seems to be working fine, however, I keep getting an error message stating ViewDestroyedError: Attempt to use a destroyed view: detectChanges. I suspect ...

Tips for troubleshooting the error "Cannot locate module mp3 file or its associated type declarations"

https://i.sstatic.net/q4x3m.png Seeking guidance on resolving the issue with finding module './audio/audio1.mp3' or its type declarations. I have already attempted using require('./audio/audio1.mp3'), but continue to encounter an error ...

Server requests are being redirected to /index.html in order to load the React/Angular SPA

Imagine having a React or Angular application hosted at www.mywebsite.com/index.html with Apache as the server. The app includes various routes like /aboutus or /faq, even though there are no individual files for each route (/aboutus.html or /faq.html). Th ...

The expanded interfaces of Typescript's indexable types (TS2322)

Currently, I am in the process of learning typescript by reimagining a flowtype prototype that I previously worked on. However, I have hit a roadblock with a particular issue. error TS2322: Type '(state: State, action: NumberAppendAction) => State ...

Tips for creating a Next.js "Link" component with an optional "href" property

I've created a custom React function using typescript for the Next.js Link component. The "href" property is necessary for the "Link" to be used anywhere, so it couldn't be utilized as a button that functions as a submit button in forms. import N ...

Encountering an issue with importing an enum: An error is triggered stating 'Variable implicitly has type 'any' in certain areas where its type remains undetermined.'

When I define simple enums in the same file, everything works fine. However, exporting/importing them causes numerous compilation errors related to types. It seems like the issue only arises when defining enums in a separate file, pointing towards a proble ...

What is the process of creating a new array by grouping data from an existing array based on their respective IDs?

Here is the initial array object that I have: const data = [ { "order_id":"ORDCUTHIUJ", "branch_code":"MVPA", "total_amt":199500, "product_details":[ { ...

Add aesthetic flair to scrollable containers

I am currently working on displaying data in columns using ngFor. However, I've run into an issue where the styles I apply to the column div are only visible on the top part of the column. As I scroll down to the bottom, the styles disappear. I believ ...

Click the link to copy it and then paste the hyperlink

I am facing an issue with copying and pasting file names as hyperlinks. I have checkboxes next to multiple files and a share button. When I check the boxes and click on the share button, I want to copy the download URLs for those files. Although I can succ ...

What is preventing me from downgrading Rxjs?

Looking for a solution to downgrade my rxjs package from version 6.1.0 to 5.5.4. Here's what I've tried so far: npm -v rxjs 6.1.0 npm install <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="73010b190033465d465d47"> ...

Issue with getStaticProps in Next.js component not functioning as expected

I have a component that I imported and used on a page, but I'm encountering the error - TypeError: Cannot read property 'labels' of undefined. The issue seems to be with how I pass the data and options to ChartCard because they are underline ...

What is the best way to seamlessly update a Redux state array in an immutable manner using Typescript?

I'm embarking on a journey to grasp Typescript through the creation of a simple Todo-List application. However, I've hit a roadblock in updating the Redux state array within a slice that I've established (or rather, I'm unsure how to go ...

Expanding Generic Interfaces in Typescript

I am working on a project where I need to enhance a typescript type by adding a new property in order to extend a generic type. To achieve this, I plan to define a Confidence<any> type that has the following structure: export interface Confidence< ...