How to create a custom range calendar filter for Angular PrimeNg data turbo table

Let’s look at how to implement the Angular selection range for the newest Angular PrimeNg version. This tutorial was developed on v10.0.0-rc-1 version.

I had difficulty finding a proper solution for range selection in the PrimeNg turbo data table for the newest version. Therefore I have decided to create a workaround. For the time selection, each click event on the calendar panel fire only one piece of data – the currently selected date. So the calendar range is stateless. You need to remember somehow to keep in memory what date was fired before and implement this logic in a custom filter.

I observed that there are several rules according to which p-calendar component behave when the selection mode "range" is selected. Here are my findings according to which I programmed the logic:

  • At first, the calendar component has an empty, undefined selection.
  • If one date is selected, only one date is fired on click event.
  • If the same date as date previously selected is selected, new date (which is the same as previously selected date) is fired on click event.
  • If a date before the already selected date is selected, the selection is cancelled and earlier date is fired on click event.
  • If a date after the already selected date is selected, the selection range is visible.
  • If any new date is selected after the selection range is visible, a new date is selected, fired upon click event and selection range is cancelled.

When all this wrote down, here is the implementation code for component and custom filter:

record-list.component.html

<p-table #dt [value]="records" sortMode="multiple" [paginator]="true" [rowsPerPageOptions]="rowsPerPageTable" [rows]="rowsPerTable" [showCurrentPageReport]="true" [(first)]="page" styleClass="p-datatable-striped" currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries">
    <ng-template pTemplate="header">
        <tr class="ui-table-thead">
            <th [ngStyle]="{'width':'50%'}" pSortableColumn="createdDate">Created <p-sortIcon field="createdDate"></p-sortIcon></th>
            <th [ngStyle]="{'width':'50%'}" pSortableColumn="info">Info <p-sortIcon field="info"></p-sortIcon></th>
        </tr>
        <tr class="ui-table-thead">
            <th scope="col">
                <p-calendar (onSelect)="onDateSelect($event)" [style]="{'width':'100%'}" selectionMode="range" [readonlyInput]="true" (onClearClick)="onDateClear($event)" [showButtonBar]="true" placeholder="Created Date" class="p-column-filter" dateFormat="yy-mm-dd"></p-calendar>
            </th>
            <th scope="col">
                <input pInputText type="text" [style]="{'width':'100%'}" (input)="dt.filter($event.target.value, 'info', 'contains')" placeholder="Info">
            </th>
        </tr>
    </ng-template>

    <ng-template pTemplate="body" let-rowData>
        <tr ng-repeat="rowData in let-rowData" ng-click="showClient(rowData)">
            <td>{{ rowData.createdDate }}</td>
            <td>{{ rowData.info }}</td>
        </tr>
    </ng-template>

</p-table>

record-list.component.ts

export class RecordListComponent implements OnInit {

    public records: IssueShortInfo[];

    @ViewChild('dt') table: Table;

    cols: any[];
    dateRangeStart: string;
    dateRangeEnd: string;

    rowsPerPageTable: number[] = [25, 50, 100, 200];

    page: number = 0;
    rowsPerTable: number = 25;

    constructor(private recordService: RecordService) {}

    ngOnInit() {

        this.recordService.getAllRecords().subscribe(page => {
            this.getRecords(page);
        }, (error => {
            console.log(error)
        }));

        this.cols = [
            {field: 'id', header: 'Id'},
            {field: 'createdDate', header: 'Created Date'},
            {field: 'info', header: 'Info'}
        ];

        FilterUtils['customCreatedDateFilter'] = (value: string, filter) => {

            if (this.dateRangeStart === value && this.dateRangeEnd === undefined) {
                return true;
            }

            if (this.dateRangeStart === value || this.dateRangeEnd === value) {
                return true;
            }

            if (this.dateRangeStart !== undefined && this.dateRangeEnd !== undefined &&
                moment(this.dateRangeStart).isBefore(value) && moment(this.dateRangeEnd).isAfter(value)) {
                return true;
            }

            return false;
        };
    }

    getRecords(page: Page) {
        this.records = page.content.map(incomingRecordDTO => new Record(incomingRecordDTO));
    }

    onDateSelect($event) {

        const eventDate = this.formatDate($event);

        if (this.dateRangeStart === undefined) {
            this.dateRangeStart = eventDate;
        } else if (moment($event).isBefore(this.dateRangeStart)) {
            this.dateRangeStart = eventDate;
            this.dateRangeEnd = undefined;
        } else if (moment($event).isSame(this.dateRangeStart) && this.dateRangeStart !== undefined && this.dateRangeEnd === undefined) {
            this.dateRangeEnd = eventDate;
        } else if (moment($event).isSame(this.dateRangeStart) && this.dateRangeStart !== undefined && this.dateRangeEnd !== undefined) {
            this.dateRangeStart = eventDate;
            this.dateRangeEnd = undefined;
        } else if (moment($event).isAfter(this.dateRangeStart) && this.dateRangeStart !== undefined && this.dateRangeEnd !== undefined) {
            this.dateRangeStart = eventDate;
            this.dateRangeEnd = undefined;
        } else {
            this.dateRangeEnd = eventDate;
        }

        this.table.filter(eventDate, 'createdDate', 'customCreatedDateFilter');
    }

    onDateClear($event) {
        this.dateRangeStart = undefined;
        this.dateRangeEnd = undefined;
        this.table.filter('', 'createdDate', 'equals');
    }

    formatDate(date) {
        let month = date.getMonth() + 1;
        let day = date.getDate();

        if (month < 10) {
            month = '0' + month;
        }

        if (day < 10) {
            day = '0' + day;
        }
        return date.getFullYear() + '-' + month + '-' + day;
    }

    customCreatedDateArrayFilter(event) {
        this.table.filter(event, 'createdDate', 'customCreatedDateFilter');
    }

}
This entry was posted in Tutorials and tagged , , , , , , . Bookmark the permalink.

One Response to How to create a custom range calendar filter for Angular PrimeNg data turbo table

  1. Javier Cuevas says:

    Thanks for sharing, I was looking for a solution like this

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.