Holiday Notice: Support will be provided on a limited scale from December 24th, 2024, to January 2nd, 2025. Happy holidays and a wonderful New Year!


Topic: Angular DataTable Pagination issue

JamesR free asked 5 years ago


Expected behavior When selecting records with a mdb-checkbox, the checked value should persist between pagination

Actual behavior when you select a checkbox and navigate away to the the next page, then back again, the check box looses it state.

Resources (screenshots, code snippets etc.)

Grid.Component.ts

import { Component, OnInit, HostListener, AfterViewInit, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
import { BillingService } from '../Services/billing.service';
import { FormControl, FormGroup } from '@angular/forms';
import { MdbTableService, WavesModule, TableModule, InputsModule, MdbTableDirective, MdbTablePaginationComponent  } from 'ng-uikit-pro-standard';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { BillingRecord } from '../Models/billingRecord';
import { forEach } from '@angular/router/src/utils/collection';

@Component({
  selector: 'app-billing-grid',
  templateUrl: './billing-grid.component.html',
  styleUrls: ['./billing-grid.component.scss']
})
export class BillingGridComponent implements OnInit, AfterViewInit {
  @ViewChild(MdbTableDirective) mdbTable: MdbTableDirective;
  @ViewChild(MdbTablePaginationComponent) mdbTablePagination: MdbTablePaginationComponent;
  @ViewChild('row') row: ElementRef;





  constructor(private billingService: BillingService,
    private tableService: MdbTableService,
    private cdRef: ChangeDetectorRef,
    private http: HttpClient) { }



  // Search Function variables
  searchText: string = '';
  previous: string;
  maxVisibleItems: number = 10;
  firstItemIndex: any;
  lastItemIndex: any;


  // Dataset from API 
  private billingRecords: any[];

  // Dataset going to API
  private doNotBillRecords: any[] = [];

  // Table Header data 
  private headElements: string[] = [
    'Project Id',
    'Project Name',
    'Region',
    'Client Matter Number',
    'Project Owner',
    'TimeKeeper Id',
    'Days Idle',
    'Document Count',
    'Per Document Rate',
    'Total Cost',
    'Bill Status',
    'DO NOT BILL'
  ]

  ngOnInit() {
    this.getAllBillingRecords();
  }

  ngAfterViewInit() {
    this.mdbTablePagination.setMaxVisibleItemsNumberTo(this.maxVisibleItems);
    this.firstItemIndex = this.mdbTablePagination.firstItemIndex;
    this.lastItemIndex = this.mdbTablePagination.lastItemIndex;

    this.mdbTablePagination.calculateFirstItemIndex();
    this.mdbTablePagination.calculateLastItemIndex();

    this.cdRef.detectChanges();
  }

  onNextPageClick(data: any) {
    this.firstItemIndex = data.first;
    this.lastItemIndex = data.last;
  }

  onPreviousPageClick(data: any) {
    this.firstItemIndex = data.first;
    this.lastItemIndex = data.last;
  }


  // Fetches billing records from api and stashes them into array for in memory
  getAllBillingRecords() {
    this.billingService.getAllBillingRecords()
      .subscribe((data: any) => {
        data.forEach((record: any) => {
          this.billingRecords.push({
            projectId: record.projectId.toString(),
            projectName: record.projectName,
            region: record.region,
            clientMatterNumber: record.clientMatterNumber,
            projectOwner: record.projectOwner,
            tkid: record.tkid,
            numberOfDaysIdle: record.numberOfDaysIdle,
            documentCount: record.documentCount,
            perDocumentRate: record.perDocumentRate,
            totalCost: record.totalCost,
            billStatus: record.billStatus
          });
        });
        this.tableService.setDataSource(this.billingRecords);
      });
    this.billingRecords = this.tableService.getDataSource();
    this.previous = this.tableService.getDataSource();

  }

  searchRecords() {
    const prev = this.tableService.getDataSource();

    if (!this.searchText) {
      this.tableService.setDataSource(this.previous);
      this.billingRecords = this.tableService.getDataSource();
    }

    if (this.searchText) {
      this.billingRecords = this.tableService.searchLocalDataBy(this.searchText);
      this.tableService.setDataSource(prev);
    }

    this.mdbTablePagination.calculateFirstItemIndex();
    this.mdbTablePagination.calculateLastItemIndex();

    this.tableService.searchDataObservable(this.searchText).subscribe((data: any) => {

      if (data.length === 0) {
        this.firstItemIndex = 0;
      }

      if (this.tableService.getDataSource().length !== data.length) {
        this.firstItemIndex = 1;
        this.lastItemIndex = this.maxVisibleItems;
      }

      this.mdbTablePagination.calculateFirstItemIndex();
      this.mdbTablePagination.calculateLastItemIndex();


    });
  }


    doNotBill(i) {
      var index = this.doNotBillRecords.indexOf(i);

      if (index === -1) {

        this.doNotBillRecords.push(i);
      } else {

        this.doNotBillRecords.splice(index, 1);
      }

    }


  findAndUpdate() {


    for (var i = 0; i < this.doNotBillRecords.length; i++) {
      if (this.doNotBillRecords[i].billStatus === 'BILL') {
        this.doNotBillRecords[i].billStatus = 'DO NOT BILL'
      }
    }

  }


  updateBilling() {

    this.findAndUpdate();
    this.billingService.updateBillingRecords(this.doNotBillRecords).subscribe();
  }

}

Grid.HTML

<div class="container">
  <table mdbTable striped="true" small="true">
    <thead class="sticky-top klgDarkBlue white-text">
      <tr>
        <th *ngFor="let head of headElements; let i = index" [mdbTableSort]="elements" [sortBy]="headElements[i]" scope="col">{{ head }} </th>
      </tr>
    </thead>

    <tbody #row>
      <tr mdTablecol (rowCreated)="onRowCreate($event)" (rowRemoved)="onRowRemove($event)" *ngFor="let record of billingRecords; let i = index">
        <th *ngIf="i+1 >= firstItemIndex && i < lastItemIndex" scope="row">{{ record.projectId}}</th>
        <td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{ record.projectName }}</td>
        <td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{ record.region }}</td>
        <td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{ record.clientMatterNumber }}</td>
        <td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{ record.projectOwner }}</td>
        <td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{ record.tkid }}</td>
        <td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{ record.numberOfDaysIdle }}</td>
        <td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{ record.documentCount }}</td>
        <td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{ record.perDocumentRate | currency }}</td>
        <td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{ record.totalCost | currency }}</td>
        <td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">{{ record.billStatus }}</td>
        <td *ngIf="i+1 >= firstItemIndex && i < lastItemIndex">
            <mdb-checkbox (change)="doNotBill(record)"></mdb-checkbox>
        </td>
      </tr>
    </tbody>


    <tfoot class="grey lighten-5 w-100">
      <tr>
        <td colspan="8">
          <mdb-table-pagination paginationAlign="" [searchDataSource]="billingRecords" (nextPageClick)="onNextPageClick($event)"
                                (previousPageClick)="onPreviousPageClick($event)">
          </mdb-table-pagination>
        </td>

         <td colspan="4">
            <button mdbBtn type="button" (click)="updateBilling()" color="primary" outline="true" mdbWavesEffect>Update Billing</button>
        </td>
      </tr>

    </tfoot>

  </table>

</div>

Damian Gemza staff answered 5 years ago


Dear James,

Please take a look at the below code (i have modified only the *ngFor on tr elements (removed *ngIf and replaced it with [ngClass]). Now it works fine.

.html:

<div class="container">
  <div class="row">
    <div class="col-md-6 mx-auto">
      <div class="md-form">
        <input type="text" class="form-control" [(ngModel)]="searchText" (keyup)="searchItems()" id="search-input"
               mdbInput>
        <label for="search-input">Search</label>
      </div>
    </div>
    <table mdbTable stickyHeader="true" hover="true" striped="true" class="z-depth-1">
      <thead class="sticky-top">
      <tr>
        <th *ngFor="let head of headElements; let i = index" [mdbTableSort]="elements" [sortBy]="headElements[i]"
            scope="col">{{head}} <mdb-icon fas icon="sort"></mdb-icon>
        </th>
      </tr>
      </thead>
      <tbody #row>
      <tr mdbTableCol (rowCreated)="onRowCreate($event)" (rowRemoved)="onRowRemove($event)" *ngFor="let el of elements; let i = index">
        <th [ngClass]="{'d-none': !(i+1 >= firstItemIndex && i < lastItemIndex)}" scope="row">
          <mdb-checkbox [default]="true"></mdb-checkbox>
          {{el.id}}
        </th>
        <td [ngClass]="{'d-none': !(i+1 >= firstItemIndex && i < lastItemIndex)}" class="red-text">{{el.first}}</td>
        <td [ngClass]="{'d-none': !(i+1 >= firstItemIndex && i < lastItemIndex)}">{{el.last}}</td>
        <td [ngClass]="{'d-none': !(i+1 >= firstItemIndex && i < lastItemIndex)}">{{el.handle}}</td>
      </tr>
      </tbody>
      <tfoot class="grey lighten-5 w-100">

      <tr>
        <td colspan="4">
          <mdb-table-pagination paginationAlign="" [searchDataSource]="elements" (nextPageClick)="onNextPageClick($event)"
                                (previousPageClick)="onPreviousPageClick($event)"></mdb-table-pagination>
        </td>
      </tr>

      </tfoot>
    </table>

  </div>

</div>

Best Regards,

Damian


JamesR free commented 5 years ago

this is working! thank you !


Damian Gemza staff answered 5 years ago


Dear James,

That's the correct behavior because we're rendering the table row's with a mix of two directives: *ngFor and *ngIf. ngFor is iterating through your data array to render items, and ngIf is checking if element should be visible in DOM or not.

A possible workaround for this situation is not to use *ngIf to dynamically render or not items, but use the [ngClass] to add a .d-block class, whenever the table row is not needed to be visible.

Best Regards,

Damian


JamesR free commented 5 years ago

Damian,

can you please provide a code snip on what to change based on your above reccomendation?

thanks



Please insert min. 20 characters.

FREE CONSULTATION

Hire our experts to build a dedicated project. We'll analyze your business requirements, for free.

Status

Resolved

Specification of the issue

  • ForumUser: Free
  • Premium support: No
  • Technology: MDB Angular
  • MDB Version: 7.4.3
  • Device: Windows
  • Browser: chrome
  • OS: Windows 10
  • Provided sample code: No
  • Provided link: No