The filtering feature in the Row Group table of PrimeNG controls is malfunctioning and causing issues with the Dropdown

Within my Angular project, I have integrated PrimeNG controls version 11.4.4. Utilizing the Table control, I've structured tabular data to display rows in a grouped fashion with collapsible functionality.

I've recently introduced a textbox and dropdown just before the header row for filtering table data. However, I've encountered an issue specifically with the Row Group feature - the filtering using the dropdown doesn't consistently work. Only the dropdown item "Accessories" seems to function correctly, while other items fail to filter as expected.

You can find my GitHub Repo here.

If anyone could take a look at the code to identify the problem and provide suggestions on how to address this issue, it would be greatly appreciated.

<h2>Table with Row 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>

TypeScript 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 };
        }
      }
    }
  }
}

Answer №1

Challenge

Identified an issue where the rowGroupMetadata function crashes 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>

The key point is ensuring that the rowGroupMetadata function is updated properly even after the table has been filtered..


Resolution

In the HTML section, it is recommended to not directly call the filter callback, but rather use a custom function called dropdownFilter by passing the 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]="ture">
      <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. The dropDownFilter method now takes in the filter callback and value parameters. After filtering, it passes the filteredValue to the updateRowGroupMetaData method.
  2. The updateRowGroupMetaData method receives rows as an optional parameter. If provided, it updates the rowGroupMetadata based on it; otherwise, it utilizes this.products for 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);
}

View Example Solution on StackBlitz

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

Challenges with handling click events in Angular Material's mat-sidenav-content

When I add a click event to the mat-sidenav-content element like this: <mat-sidenav-content (click)="isNavBarOpened=false">, the mat-slide-toggle inside it stops functioning. Check out the code example here ...

Disregard the JSON formatting and extract solely the values

After extracting data from an API, the format of the returned information looks like this: [{"id":21},{"id":22},{"id":24}] Next, I need to send this data to a database using a different API. However, the format for sending should be like this: [21,22,24] ...

In the search for "action.payload" using Redux Toolkit

const userSlice = createSlice({ name: 'user', initialState, reducers: { // Setting the user setUser: (state, action: PayloadAction<string | null>) => { state.user.email = action.payload; }, setLoading: (state, ...

Angular2 import functions properly on the Windows operating system, however, it encounters issues on the Linux

import { Injectable } from '@angular/core'; import { Http, Response, Headers } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import { User } from './../../class/User'; I am encountering the fol ...

Issue with Angular 5 HttpClient - PUT request not working in Firefox, however functions correctly in Chrome

Currently in the process of developing a website with Angular 5 and CouchDB. One of my methods in database.service.ts looks like this: import {HttpClient} from '@angular/common/http'; const auth = my database adress; constructor(private http: Ht ...

Ionic 5 Error: Unable to access 'subscribe' property of undefined object

Working with IONIC 5 service: import { HttpClient } from '@angular/common/http'; . . . getPlaces(placeId: string) { return this.httpClient.get( `https://test.com/offered-place/${placeId}.json`) } . . . Encountering an issue when tryin ...

Utilizing TypeScript code to access updatedAt timestamps in Mongoose

When querying the database, I receive the document type as a return. const table: TableDocument = await this.tableSchema.create({ ...createTableDto }) console.log(table) The structure of the table object is as follows: { createdBy: '12', cap ...

Creating a JSON schema for MongoDB using a TypeScript interface: a step-by-step guide

In order to enhance the quality of our data stored in MongoDB database, we have decided to implement JSON Schema validation. Since we are using typescript in our project and have interfaces for all our collections, I am seeking an efficient method to achie ...

What is the best way to configure Angular viewProviders when using an NgForm in a unit test?

One of the challenges I faced was with an Angular 16 component that required the form it will be placed inside as input. The solution came from using the viewProviders property, which I discovered through a helpful response to a question I had asked previo ...

Angular - Showcasing Nested Objects in JSON

I am experimenting with using angular ngFor to iterate through this data: Link: Although I can successfully retrieve the data by subscribing to it, I encounter an issue when trying to display attributes that contain objects. The output shows as [object O ...

Display content exclusively in PDF format

On my HTML page, I have two sections - one for inputting values and the other for viewing a PDF. To ensure that the PDF view is hidden until explicitly requested, I need it to remain invisible by default. It should only appear as a PDF when someone clicks ...

Error: Serialization of circular structure to JSON not possible in Next.js

I am currently working on creating an API in Next.js to add data into a MySQL database. The issue I am facing is related to a circular reference, but pinpointing it has proven to be challenging. It's worth mentioning that Axios is also being utilized ...

Tips for ensuring all files are recognized as modules during the transition of an established NodeJS project to TypeScript

I'm diving into TypeScript as a newcomer and I am exploring the process of transitioning a large, existing NodeJS codebase that is compliant with ES2017 to TypeScript. Here's a glimpse at my tsconfig.json: { "compilerOptions": { "mo ...

What is the best way to retrieve the ID of an element using ViewChild in Angular 2 and

How can I establish a relationship between jQuery and ViewChild in Angular 2? I attempted the following code, but it returned an undefined value: @ViewChild('item', {read: ViewContainerRef}) Item; console.log( $(this.Item.nativeElement).attr(& ...

Looking to showcase a nested JSON object within a Material Data Table

My goal is to present a nested JSON Object received from the backend as the column fields in my MatTableDataSource. This is the structure of my JSON Object: [{ "workstationId": 100, "assemblylineId": 100, "workstationDescription": "Testing1", ...

Tips for integrating external libraries (npm packages) in Ionic 4 applications

With the changes in Ionic 4, I am seeking a definitive guide on implementing third party libraries, such as "rss-parser". I have reviewed this article which seems to be the latest resource on the topic: https://ionicframework.com/docs/v3/developer-resour ...

Enhance Your Search Functionality with an Angular Pipe

I created a custom pipe that filters the search field and displays a list of items based on the search text. Currently, it only filters by companyDisplay, but I want to also filter by companyCode and companyName. JSON [{ companyDisplay: "ABC", co ...

Leverage the keyof operator for automatic determination of return type

My interface looks like this: interface ResourceState<ItemType> { rows: ItemType[], store: Record<String, ItemType> } To create a root state, I use the following structure: RootState{ car: ResourceState<Car> user: ResourceState<User&g ...

Retrieve the array from within the string

Any suggestions on how I can extract the array from this string? The current string is: "['Biller.Customer.Data@Taxonomy', 'Product.Platform and Enterprise Services Data.Data@Taxonomy']" I need to extract it to look like thi ...

unable to verify identity through a web browser

When I try to launch my Android application built with Ionic 2 on my smartphone, I encounter this error: If you need a tutorial for the application, check out: https://medium.com/appseed-io/third-party-authentication-for-your-ionic-2-mobile-app-9fdd43169d ...