[FIXED] Dropdown filter is not working in with Row Group table in PrimeNG controls

Issue

In my angular project, I have installed PrimeNG controls version 11.4.4. I have used Table control to create tabular data to show the rows in the group with collapsible style.

Now I have added a textbox and dropdown control right before the header row to filter the table data. But the problem is with Row Group table data, filtering with dropdown is not working always. Only the dropdown item Accessories is working. But for other dropdown item filtering is not working.

Here is my GitHub Repo.

Can anyone please run the code to see the problem and suggest to me how to solve this?

<h2>Table with Rog Group</h2>

<p-table #dt2 [columns]="selectedColumns" [value]="products" sortField="category" sortMode="single" (onSort)="onSort()"
  dataKey="category" styleClass="p-datatable-gridlines p-datatable-striped">
  <ng-template pTemplate="header" let-columns>
    <tr>
      <th *ngFor="let col of columns">
        {{col.header}}
      </th>
    </tr>
    <tr>
      <th *ngFor="let col of columns" [ngSwitch]="col.field">
        <p-columnFilter *ngSwitchCase="'code'" type="text" field="code" matchMode="contains"
          (input)="applyFilter1($event, 'code', 'contains')">
        </p-columnFilter>

        <p-columnFilter *ngSwitchCase="'name'" type="text" field="name" matchMode="contains"
          (input)="applyFilter1($event, 'name', 'contains')">
        </p-columnFilter>

        <p-columnFilter *ngSwitchCase="'category'" field="category" matchMode="equals" [showMenu]="false">
          <ng-template pTemplate="filter" let-value let-filter="filterCallback">
            <p-dropdown [ngModel]="value" [options]="categories" (onChange)="filter($event.value)" placeholder="Any"
              [showClear]="true">
              <ng-template let-option pTemplate="item">
                <div class="p-multiselect-representative-option">
                  <span class="p-ml-1">{{option.label}}</span>
                </div>
              </ng-template>
            </p-dropdown>
          </ng-template>
        </p-columnFilter>

        <p-columnFilter *ngSwitchCase="'quantity'" type="text" field="quantity" matchMode="equals"
          (input)="dt2.filter($event.target.value1)">
        </p-columnFilter>
      </th>
    </tr>
  </ng-template>
  <ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex" let-expanded="expanded">
    <tr *ngIf="rowGroupMetadata[rowData.category].index === rowIndex">
      <td colspan="4">
        <button type="button" pButton pRipple [pRowToggler]="rowData"
          class="p-button-text p-button-rounded p-button-plain p-mr-2"
          [icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></button>
        <span class="p-text-bold p-ml-2">{{rowData.category}}</span>
      </td>
    </tr>
  </ng-template>

  <ng-template pTemplate="rowexpansion" let-rowData let-columns="columns">
    <tr>
      <td *ngFor="let col of columns">
        <span>{{rowData[col.field]}}</span>
      </td>
    </tr>
  </ng-template>
</p-table>

TS file:

import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { Table } from 'primeng/table';
import { Product } from '../_models/product.model';
import { ProductService } from '../_services/product.service';

@Component({
  selector: 'app-row-group-grid',
  templateUrl: './row-group-grid.component.html',
  styleUrls: ['./row-group-grid.component.css']
})
export class RowGroupGridComponent implements OnInit {
  products: Product[] = [];

  cols: any[] = [];
  _selectedColumns: any[] = [];
  categories: any[] = [];
  rowGroupMetadata: any;

  @ViewChild('dt2') dt2!: Table;

  constructor(private productService: ProductService) { }

  ngOnInit() {
    this.productService.getProductsSmall().then(data => {
      this.products = data;
      this.updateRowGroupMetaData();
    });

    this.cols = [
      { field: 'code', header: 'Code' },
      { field: 'name', header: 'Name' },
      { field: 'category', header: 'Category' },
      { field: 'quantity', header: 'Quantity' }
    ];

    this._selectedColumns = this.cols;

    this.categories = [      
      { label: "Clothing", value: "Clothing" },
      { label: "Electronics", value: "Electronics" },
      { label: "Fitness", value: "Fitness" },
      { label: "Accessories", value: "Accessories" },
    ];
  }

  @Input() get selectedColumns(): any[] {
    return this._selectedColumns;
  }

  set selectedColumns(val: any[]) {
    //restore original order
    this._selectedColumns = this.cols.filter(col => val.includes(col));
  }

  applyFilter1($event: any, field: string, matchMode: string) {
    let value = ($event.target as HTMLInputElement)?.value;
    this.dt2.filter(value, field, matchMode);
  }

  onSort() {
    this.updateRowGroupMetaData();
  }

  updateRowGroupMetaData() {
    this.rowGroupMetadata = {};

    if (this.products) {
      for (let i = 0; i < this.products.length; i++) {
        let rowData = this.products[i];
        let category1 = rowData.category;

        if (i == 0) {
          this.rowGroupMetadata[category1] = { index: 0, size: 1 };
        }
        else {
          let previousRowData = this.products[i - 1];
          let previousRowGroup = previousRowData.category;

          if (category1 === previousRowGroup)
            this.rowGroupMetadata[category1].size++;
          else
            this.rowGroupMetadata[category1] = { index: i, size: 1 };
        }
      }
    }
  }
}

Solution

Issue

Checked that this logic in rowGroupMetadata crash when the table is filtered.

<tr *ngIf="rowGroupMetadata[rowData.category].index === rowIndex">
  <td colspan="4">
    <button type="button" pButton pRipple [pRowToggler]="rowData"
      class="p-button-text p-button-rounded p-button-plain p-mr-2"
      [icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></button>
    <span class="p-text-bold p-ml-2">{{rowData.category}}</span>
  </td>
</tr>

You need to make sure that rowGroupMetadata is also updated when the table is filtered.


Solution

For HTML part, rather than directly call filter callback, you should call dropdownFilter custom function by parsing filter callback and $event.value.

.component.html

<p-columnFilter *ngSwitchCase="'category'" field="category" matchMode="equals" [showMenu]="false">
  <ng-template pTemplate="filter" let-value let-filter="filterCallback">
    <p-dropdown [options]="categories" (onChange)="dropdownFilter(filter, $event.value)" placeholder="Any" [showClear]="true">
      <ng-template let-option pTemplate="item">
        <div class="p-multiselect-representative-option">
          <span class="p-ml-1">{{option.label}}</span>
        </div>
      </ng-template>
    </p-dropdown>
  </ng-template>
</p-columnFilter>
  1. dropDownFilter method accepts filter callback and value
    parameters. In this method, after the table is filtered, would pass
    the filteredValue (filtered result) to updateRowGroupMetaData
    method.
  2. updateRowGroupMetaData method is modified that receives rows as optional parameter. When received rows, will update rowGroupMetadata based on it. Otherwise, this.products is used to remain the existing logic.

.component.ts

updateRowGroupMetaData(rows?: Product[]) {
  let products = rows ?? this.products;
  this.rowGroupMetadata = {};

  if (products) {
    for (let i = 0; i < products.length; i++) {
      let rowData = products[i];
      let category1 = rowData.category;
      if (i == 0) {
        this.rowGroupMetadata[category1] = { index: 0, size: 1 };
      }
      else {
        let previousRowData = products[i - 1];
        let previousRowGroup = previousRowData.category;

        if (category1 === previousRowGroup)
          this.rowGroupMetadata[category1].size++;
        else
          this.rowGroupMetadata[category1] = { index: i, size: 1 };
      }
    }
  }
}

dropdownFilter(filter: (a: string) => void, value: string) {
  filter(value);
  this.updateRowGroupMetaData(this.dt2.filteredValue);
}

Sample Solution on StackBlitz

Answered By – Yong Shun

Answer Checked By – Cary Denson (Easybugfix Admin)

Leave a Reply

(*) Required, Your email will not be published