Angular Table

Angular Table

·

33 min read

Angular Table displays data in tabular format.

Setup

Refer to PrimeNG setup documentation for download and installation steps for your environment.

CDK

VirtualScrolling depends on @angular/cdk's ScrollingModule so begin with installing CDK if not already installed.

npm install @angular/cdk --save

Import

import {TableModule} from 'primeng/table';

Getting Started

Table requires a value as an array of objects and templates for the presentation. Throughout the samples, a car interface having vin, brand, year and color properties is used to define an object to be displayed by the table. Cars are loaded by a CarService that connects to a server to fetch the data.

export interface Car {
    vin;
    year;
    brand;
    color;
}
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

import { Car } from '../domain/car';

@Injectable()
export class CarService {

    constructor(private http: HttpClient) {}

    getCarsSmall() {
        return this.http.get('/showcase/resources/data/cars-small.json')
                    .toPromise()
                    .then(res => <Car[]> res.data)
                    .then(data => { return data; });
    }
}

Following sample has a table of 4 columns and retrieves the data from a service on ngOnInit.

export class TableDemo implements OnInit {

    cars: Car[];

    constructor(private carService: CarService) { }

    ngOnInit() {
        this.carService.getCarsSmall().then(cars => this.cars = cars);
    }
}

List of cars are bound to the value property whereas header and body templates are used to define the content of these sections.

<p-table [value]="cars">
    <ng-template pTemplate="header">
        <tr>
            <th>Vin</th>
            <th>Year</th>
            <th>Brand</th>
            <th>Color</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-car>
        <tr>
            <td>{{car.vin}}</td>
            <td>{{car.year}}</td>
            <td>{{car.brand}}</td>
            <td>{{car.color}}</td>
        </tr>
    </ng-template>
</p-table>

Dynamic Columns

Instead of configuring columns one by one, a simple ngFor can be used to implement dynamic columns. cols property below is an array of objects that represent a column, only property that table component uses is field, rest of the properties like header depend on your choice.

export class DynamicColumnsDemo implements OnInit {

    cars: Car[];

    cols: any[];

    constructor(private carService: CarService) { }

    ngOnInit() {
        this.carService.getCarsSmall().then(cars => this.cars = cars);

        this.cols = [
            { field: 'vin', header: 'Vin' },
            { field: 'year', header: 'Year' },
            { field: 'brand', header: 'Brand' },
            { field: 'color', header: 'Color' }
        ];
    }
}

There are two ways to render dynamic columns, since cols property is in the scope of component you can just simply bind it to ngFor directive to generate the structure.

<p-table [value]="cars">
    <ng-template pTemplate="header">
        <tr>
            <th *ngFor="let col of cols">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-car>
        <tr>
            <td *ngFor="let col of cols">
                    {{car[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

Other alternative is binding the cols array to the columns property and then defining a template variable to access it within your templates. There are 3 cases where this is required which are csv export, reorderable columns and global filtering without the globalFilterFields property.

<p-table [columns]="cols" [value]="cars">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-car let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                    {{car[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

Tip: Use ngSwitch to customize the column content per dynamic column.

Table Layout

For performance reasons, default table-layout is fixed meaning the cell widths do not depend on their content. If you require cells to scale based on their contents set autoLayout property to true. Note that Scrollable and/or Resizable tables do not support auto layout due to technical reasons.

Change Detection

Table may need to be aware of changes in its value in some cases such as reapplying sort. For the sake of performance, this is only done when the reference of the value changes meaning a setter is used instead of ngDoCheck/IterableDiffers which can reduce performance. So when you manipulate the value such as removing or adding an item, instead of using array methods such as push, splice create a new array reference using a spread operator or similar.

Sections

Table offers various templates to display additional information about the data such as a caption or summary.

<p-table [columns]="cols" [value]="cars">
    <ng-template pTemplate="caption">
        List of Cars
    </ng-template>
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
            {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
    <ng-template pTemplate="footer" let-columns>
        <tr>
            <td *ngFor="let col of columns">
            {{col.header}}
            </td>
        </tr>
    </ng-template>
    <ng-template pTemplate="summary">
        There are {{cars?.length}} cars
    </ng-template>
</p-table>

See the live example.

Column Grouping

Columns can easily be grouped using templating. Let's start with sample data of sales of brands per year.

export class TableColGroupDemo implements OnInit {

    sales: any[];

    ngOnInit() {
        this.sales = [
            { brand: 'Apple', lastYearSale: '51%', thisYearSale: '40%', lastYearProfit: '$54,406.00', thisYearProfit: '$43,342' },
            { brand: 'Samsung', lastYearSale: '83%', thisYearSale: '96%', lastYearProfit: '$423,132', thisYearProfit: '$312,122' },
            { brand: 'Microsoft', lastYearSale: '38%', thisYearSale: '5%', lastYearProfit: '$12,321', thisYearProfit: '$8,500' },
            { brand: 'Philips', lastYearSale: '49%', thisYearSale: '22%', lastYearProfit: '$745,232', thisYearProfit: '$650,323,' },
            { brand: 'Song', lastYearSale: '17%', thisYearSale: '79%', lastYearProfit: '$643,242', thisYearProfit: '500,332' },
            { brand: 'LG', lastYearSale: '52%', thisYearSale: ' 65%', lastYearProfit: '$421,132', thisYearProfit: '$150,005' },
            { brand: 'Sharp', lastYearSale: '82%', thisYearSale: '12%', lastYearProfit: '$131,211', thisYearProfit: '$100,214' },
            { brand: 'Panasonic', lastYearSale: '44%', thisYearSale: '45%', lastYearProfit: '$66,442', thisYearProfit: '$53,322' },
            { brand: 'HTC', lastYearSale: '90%', thisYearSale: '56%', lastYearProfit: '$765,442', thisYearProfit: '$296,232' },
            { brand: 'Toshiba', lastYearSale: '75%', thisYearSale: '54%', lastYearProfit: '$21,212', thisYearProfit: '$12,533' }
        ];
    }
}
<p-table [value]="sales">
    <ng-template pTemplate="header">
        <tr>
            <th rowspan="3">Brand</th>
            <th colspan="4">Sale Rate</th>
        </tr>
        <tr>
            <th colspan="2">Sales</th>
            <th colspan="2">Profits</th>
        </tr>
        <tr>
            <th>Last Year</th>
            <th>This Year</th>
            <th>Last Year</th>
            <th>This Year</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-sale>
        <tr>
            <td>{{sale.brand}}</td>
            <td>{{sale.lastYearSale}}</td>
            <td>{{sale.thisYearSale}}</td>
            <td>{{sale.lastYearProfit}}</td>
            <td>{{sale.thisYearProfit}}</td>
        </tr>
    </ng-template>
    <ng-template pTemplate="footer">
        <tr>
            <td colspan="3">Totals</td>
            <td>$506,202</td>
            <td>$531,020</td>
        </tr>
    </ng-template>
</p-table>

See the live example.

Row Grouping

Templating features can also be used to implement row grouping functionality, here is an example implementation that uses a metadata object to keep at what index a group starts and how many items it has.

export class TableRowGroupDemo implements OnInit {

    cars: Car[];

    rowGroupMetadata: any;

    constructor(private carService: CarService) { }

    ngOnInit() {
        this.carService.getCarsMedium().then(cars => {
            this.cars = cars;
            this.updateRowGroupMetaData();
        });
    }

    onSort() {
        this.updateRowGroupMetaData();
    }

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

}

Using this metadata rows can be grouped using a subheader that displays the group. Note that grouped data should be sorted so enable sortField so that table applies sorting before grouping if your data is not sorted.

<p-table [value]="cars" sortField="brand" sortMode="single" (onSort)="onSort()">
    <ng-template pTemplate="header">
        <tr>
            <th>Vin</th>
            <th>Year</th>
            <th>Color</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex">
        <tr class="ui-widget-header" *ngIf="rowGroupMetadata[rowData.brand].index === rowIndex">
            <td colspan="3">
                <span style="font-weight:bold">{{rowData.brand}}</span>
            </td>
        </tr>
        <tr>
            <td>{{rowData.vin}}</td>
            <td>{{rowData.year}}</td>
            <td>{{rowData.color}}</td>
        </tr>
    </ng-template>
</p-table>

An alternative grouping could be using rowspans for the group field.

<p-table [value]="cars" sortField="brand" sortMode="single" (onSort)="onSort()">
    <ng-template pTemplate="header">
        <tr>
            <th>Brand</th>
            <th>Vin</th>
            <th>Year</th>
            <th>Color</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex">
        <tr>
            <td *ngIf="rowGroupMetadata[rowData.brand].index === rowIndex" [attr.rowspan]="rowGroupMetadata[rowData.brand].size">
                {{rowData.brand}}
            </td>
            <td>{{rowData.vin}}</td>
            <td>{{rowData.year}}</td>
            <td>{{rowData.color}}</td>
        </tr>
    </ng-template>
</p-table>

See the live example.

Multi Field grouping

Previous example uses a single field to group the rows however nothing limits you to implement multiple field grouping as well. Similarly to single grouping, your data should be sorted first, you may use the built-in multiSorting or provide it sorted to the table and create a rowGroupMetadata for multiple fields.

Paginator

Pagination is enabled by setting paginator property to true, rows property defines the number of rows per page and pageLinks specify the the number of page links to display. See paginator component for more information.

<p-table [columns]="cols" [value]="cars" [paginator]="true" [rows]="10">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

Paginator can also be controlled via model using a binding to the first property where changes trigger a pagination.

<p-table [columns]="cols" [value]="cars" [paginator]="true" [rows]="10" [(first)]="first">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>
export class TablePageDemo implements OnInit {

    cars: Car[];

    first: number = 0;

    constructor(private carService: CarService) { }

    ngOnInit() {
        this.carService.getCarsSmall().then(cars => this.cars = cars);
    }

    reset() {
        this.first = 0;
    }
}

To customize the paginator, use paginatorLeftTemplate, paginatorRightTemplate and paginatorDropdownItemTemplate templates.

<p-table [columns]="cols" [value]="cars" [paginator]="true" [rows]="10" [first]="first">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
    <ng-template pTemplate="paginatorleft" let-state>
        {{state.first}}
        <button type="button" pButton icon="pi-refresh"></button>
    </ng-template>
    <ng-template pTemplate="paginatorright">
        <button type="button" pButton icon="pi-cloud-upload"></button>
    </ng-template>
    <ng-template let-item pTemplate="paginatordropdownitem">
        {{item.value}} - per page
    </ng-template>
</p-table>

Paginator templates gets the paginator state as an implicit variable that provides the following properties

  • first
  • rows
  • page
  • totalRecords

See the live example.

Sorting

A column can be made sortable by adding the pSortableColumn directive whose value is the field to sort against and a sort indicator via p-sortIcon component. For dynamic columns, setting pSortableColumnDisabled property as true disables sorting for that particular column.

<p-table [columns]="cols" [value]="cars1">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns" [pSortableColumn]="col.field">
                {{col.header}}
                <p-sortIcon [field]="col.field"></p-sortIcon>
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

Default sorting is executed on a single column, in order to enable multiple field sorting, set sortMode property to "multiple" and use metakey when clicking on another column.

<p-table [value]="cars" sortMode="multiple">

In case you'd like to display the table as sorted by default initially on load, use the sortField - sortOrder properties in single mode.

<p-table [columns]="cols" [value]="cars1" sortField="year">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns" [pSortableColumn]="col.field">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

In multiple mode, use the multiSortMeta property and bind an array of SortMeta objects.

<p-table [columns]="cols" [value]="cars1" sortMode="multiple" [multiSortMeta]="multiSortMeta">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns" [pSortableColumn]="col.field">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>
this.multiSortMeta = [];
this.multiSortMeta.push({field: 'year', order: 1});
this.multiSortMeta.push({field: 'brand', order: -1});

Instead of using the built-in sorting algorithm a custom sort can be attached by enabling customSort property and defining a sortFunction implementation. This function gets a SortEvent instance that provides the data to sort, sortField, sortOrder and multiSortMeta.

export class CustomTableSortDemo implements OnInit {

    cars: Car[];

    constructor(private carService: CarService) { }

    ngOnInit() {
        this.carService.getCarsSmall().then(cars => this.cars = cars);

        this.cols = [
            { field: 'vin', header: 'Vin' },
            { field: 'year', header: 'Year' },
            { field: 'brand', header: 'Brand' },
            { field: 'color', header: 'Color' }
        ];
    }

    customSort(event: SortEvent) {
        //event.data = Data to sort
        //event.mode = 'single' or 'multiple' sort mode
        //event.field = Sort field in single sort
        //event.order = Sort order in single sort
        //event.multiSortMeta = SortMeta array in multiple sort

        event.data.sort((data1, data2) => {
            let value1 = data1[event.field];
            let value2 = data2[event.field];
            let result = null;

            if (value1 == null && value2 != null)
                result = -1;
            else if (value1 != null && value2 == null)
                result = 1;
            else if (value1 == null && value2 == null)
                result = 0;
            else if (typeof value1 === 'string' && typeof value2 === 'string')
                result = value1.localeCompare(value2);
            else
                result = (value1 < value2) ? -1 : (value1 > value2) ? 1 : 0;

            return (event.order * result);
        });
    }
}
<p-table [columns]="cols" [value]="cars" (sortFunction)="customSort($event)" [customSort]="true">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns" [pSortableColumn]="col.field">
                {{col.header}}
                <p-sortIcon [field]="col.field"></p-sortIcon>
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

See the live example.

Filtering

Filtering feature provides advanced and flexible options to query the data. Core of the filtering functionality is the p-columnFilter component.

Data Types

ColumnFilter requires a field to reference a data property along with a type to define the built-in form element to use in filtering. Valid values for the type are the listed below. Each type displays a suitable form element such as calendar for dates. Custom filter elements are also supported with templating.

  • text (default)
  • numeric
  • boolean
  • date

Separate Row and Filter Menu

There are two possible display alternatives for the ColumnFilter component, default is creating a separate mode whereas alternative is the advanced overlay mode enabled with setting display as "menu".

Separate Row Filters are displayed within a new row in this setting and appropriate filter constraints can be selected with a dropdown.

<ng-template pTemplate="header">
    <tr>
        <th>Name</th>
    </tr>
    <tr>
        <th>
            <p-columnFilter type="text" field="name"></p-columnFilter>
        </th>
    </tr>
</ng-template>

Filter Menu Filter Menu is more advanced compared to the separate row as it provides multiple rules with operator support.

<ng-template pTemplate="header">
    <tr>
        <th>Name</th>
        <p-columnFilter type="text" field="name" display="menu"></p-columnFilter>
    </tr>
</ng-template>

Match modes

Each filter data type has its own match modes, for example "text" type displays string matchers e.g. startsWith and "date" type has before/after matchers. Defaults are defined at the PrimeNGConfig instance to apply matchers for a certain type in the entire application with ability to override the global configuration using the matchModeOptions property that accepts a SelectItem array. Global configuration can be changed during initialization of the root component. Here is an example that appclies options for data types, note that values used in this sample are the default values and this configuration is only required if you'd like to customize defaults globally.

import { Component, OnInit } from '@angular/core';
import { PrimeNGConfig } from 'primeng/api';
import { FilterMatchMode } from './filtermatchmode';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {

    constructor(private config: PrimeNGConfig) {}

    ngOnInit() {
        this.config.filterMatchModeOptions = {
            text: [
                FilterMatchMode.STARTS_WITH,
                FilterMatchMode.CONTAINS,
                FilterMatchMode.NOT_CONTAINS,
                FilterMatchMode.ENDS_WITH,
                FilterMatchMode.EQUALS,
                FilterMatchMode.NOT_EQUALS
            ],
            numeric: [
                FilterMatchMode.EQUALS,
                FilterMatchMode.NOT_EQUALS,
                FilterMatchMode.LESS_THAN,
                FilterMatchMode.LESS_THAN_OR_EQUAL_TO,
                FilterMatchMode.GREATER_THAN,
                FilterMatchMode.GREATER_THAN_OR_EQUAL_TO
            ],
            date: [
                FilterMatchMode.DATE_IS,
                FilterMatchMode.DATE_IS_NOT,
                FilterMatchMode.DATE_BEFORE,
                FilterMatchMode.DATE_AFTER
            ]
        }
    }
}

Custom Form Elements

Using templating, a custom element can be defined as the filter. The template gets the filter value and a filterCallback as parameters so that current filter value can be displayed on the element with support for an event of your choice to trigger the filter. In this example, a dropdown is used to select a value from a list.

<th>
    <p-columnFilter field="status" matchMode="equals">
        <ng-template pTemplate="filter" let-value let-filter="filterCallback">
            <p-dropdown [ngModel]="value" [options]="statuses" optionLabel="name" optionValue="value"
                (onChange)="filter($event.value)" placeholder="Select a Status" [showClear]="true">
            </p-dropdown>
        </ng-template>
    </p-columnFilter>
</th>
export class TableFilterDemo implements OnInit {

    statuses: any[];

    ngOnInit() {
        this.statuses = [
            {name: 'Unqualified', value: 'unqualified'},
            {name: 'Qualified', value: 'qualified'},
            {name: 'New', value: 'new'},
            {name: 'Negotiation', value: 'negotiation'},
            {name: 'Renewal', value: 'renewal'},
            {name: 'Proposal', value: 'proposal'}
        ]
    }

}

Global Filtering

Global filtering searches all the fields from a single form element, in order to implement global filtering call the filterGlobal of the table with a value and match mode. If you have static columns and need to use global filtering, globalFilterFields property must be defined to configure which fields should be used in global filtering. Another use case of this property is to change the fields to utilize in global filtering with dynamic columns.

<p-table #dt [value]="cars" [globalFilterFields]="['vin','year']">
    <ng-template pTemplate="caption">
    <div class="p-d-flex">
        <span class="p-input-icon-left p-ml-auto">
            <i class="pi pi-search"></i>
            <input pInputText type="text" (input)="dt.filterGlobal($event.target.value, 'contains')" placeholder="Search keyword" />
        </span>
    </div>
    </ng-template>
</p-table>

Custom Filters

FilterService provides an API to register custom filters, Visit the FilterService documentation for more information.

See the live example.

Selection

Table provides built-in single and multiple selection features where selected rows are bound to the selection property and onRowSelect-onRowUnselect events are provided as optional callbacks. In order to enable this feature, define a selectionMode, bind a selection reference and add pSelectableRow directive whose value is the rowData to the rows that can be selected. Additionally if you prefer double click use pSelectableRowDblClick directive instead and to disable selection events on a particular row use pSelectableRowDisabled. In both cases optional pSelectableRowIndex property is avaiable to access the row index. By default each row click adds or removes the row from the selection, if you prefer a classic metaKey based selection approach enable metaKeySelection true so that multiple selection or unselection of a row requires metaKey to be pressed. Note that, on touch enabled devices, metaKey based selection is turned off automatically as there is no metaKey in devices such as mobile phones.

Alternative to the row click, radiobutton or checkbox elements can be used to implement row selection.

When resolving if a row is selected, by default Table compares selection array with the datasource which may cause a performance issue with huge datasets that do not use pagination. If available the fastest way is to use dataKey property that identifies a unique row so that Table can avoid comparing arrays as internally a map instance is used instead of looping arrays, on the other hand if dataKey cannot be provided consider using compareSelectionBy property as "equals" which uses reference comparison instead of the default "deepEquals" comparison. Latter is slower since it checks all properties.

In single mode, selection binding is an object reference.

export class TableDemo implements OnInit {

    cars: Car[];

    selectedCar: Car;

    constructor(private carService: CarService) { }

    ngOnInit() {
        this.carService.getCarsSmall().then(cars => this.cars = cars);
    }
}
<p-table [columns]="cols" [value]="cars" selectionMode="single" [(selection)]="selectedCar">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr [pSelectableRow]="rowData">
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

In multiple mode, selection binding should be an array. Note that if you require shiftKey based range selection, pass the rowIndex to the SelectableRow directive.

export class TableDemo implements OnInit {

    cars: Car[];

    selectedCars: Car[];

    constructor(private carService: CarService) { }

    ngOnInit() {
        this.carService.getCarsSmall().then(cars => this.cars = cars);
    }
}
<p-table [columns]="cols" [value]="cars" selectionMode="multiple" [(selection)]="selectedCars">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns" let-rowIndex="rowIndex">
        <tr [pSelectableRow]="rowData" [pSelectableRowIndex]="rowIndex">
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

Single selection using a radiobutton can be done by using p-tableRadioButton component.

<p-table [columns]="cols" [value]="cars" [(selection)]="selectedCar" dataKey="vin">
<ng-template pTemplate="header" let-columns>
    <tr>
        <th style="width: 2.25em"></th>
        <th *ngFor="let col of columns">
            {{col.header}}
        </th>
    </tr>
</ng-template>
<ng-template pTemplate="body" let-rowData let-columns="columns">
    <tr>
        <td>
            <p-tableRadioButton [value]="rowData"></p-tableRadioButton>
        </td>
        <td *ngFor="let col of columns">
            {{rowData[col.field]}}
        </td>
    </tr>
</ng-template>
<ng-template pTemplate="summary">
        <div style="text-align: left">
            Selected Car: {{selectedCar4 ? selectedCar4.vin + ' - ' + selectedCar4.brand + ' - ' + selectedCar4.year + ' - ' + selectedCar4.color: 'none'}}
        </div>
    </ng-template>
</p-table>

Similarly p-tableCheckbox and p-tableHeaderCheckbox elements are provided to implement checkbox based multiple selection.

<p-table [columns]="cols" [value]="cars" [(selection)]="selectedCars" dataKey="vin">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th style="width: 2.25em">
                <p-tableHeaderCheckbox></p-tableHeaderCheckbox>
            </th>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td>
                <p-tableCheckbox [value]="rowData"></p-tableCheckbox>
            </td>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

Both p-tableCheckbox and p-tableRadioButton components can be disabled using their property with the same name to prevent selection of a particular row. In addition, index of the row needs to be provided to the checkbox/radiobutton components so that they can be available at the onRowSelect or onRowUnselect events of the Table.

<ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex" let-columns="columns">
    <tr [pSelectableRow]="rowData">
        <td>
            <p-tableCheckbox [value]="rowData" [disabled]="rowData.year > 2010" [index]="rowIndex"></p-tableCheckbox>
        </td>
        <td *ngFor="let col of columns">
            {{rowData[col.field]}}
        </td>
    </tr>
</ng-template>

See the live example.

ContextMenu

Table has exclusive integration with contextmenu component. In order to attach a menu to a table, add pContextMenuRow directive to the rows that can be selected with context menu, define a local template variable for the menu and bind it to the contextMenu property of the table. This enables displaying the menu whenever a row is right clicked. Optional pContextMenuRowIndex property is available to access the row index. A separate contextMenuSelection property is used to get a hold of the right clicked row. For dynamic columns, setting pContextMenuRowDisabled property as true disables context menu for that particular row.

<p-table [columns]="cols" [value]="cars" [(contextMenuSelection)]="selectedCar" [contextMenu]="cm">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns" let-rowIndex="rowIndex">
        <tr [pContextMenuRow]="rowData" [pContextMenuRowIndex]="rowIndex">
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

<p-contextMenu #cm [model]="items"></p-contextMenu>

By default context menu uses a different property called contextMenuSelection as above, this means when row selection mode is also enabled, the two properties, both selection and contextMenuSelection need to be maintained. In case you prefer to configure Table to manage the same selection property both on row click and context menu, set contextMenuSelectionMode as "joint". Table below has both selectionMode and contextMenu enabled where both of these features update the same selection object.

<p-table [columns]="cols" [value]="cars" selectionMode="single" [(selection)]="selectedCar" [contextMenu]="cm" contextMenuSelectionMode="joint">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr [pSelectableRow]="rowData" [pContextMenuRow]="rowData">
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

<p-contextMenu #cm [model]="items"></p-contextMenu>

See the live example.

Cell Editing

Incell editing is enabled by adding pEditableColumn directive to an editable cell that has a p-cellEditor helper component to define the input-output templates for the edit and view modes respectively.

<p-table [columns]="cols" [value]="cars">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th>Vin</th>
            <th>Year</th>
            <th>Brand</th>
            <th>Color</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td pEditableColumn>
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input type="text" [(ngModel)]="rowData.vin">
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{rowData.vin}}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td pEditableColumn>
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input type="text" [(ngModel)]="rowData.year" required>
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{rowData.year}}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td pEditableColumn>
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input type="text" [(ngModel)]="rowData.brand">
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{rowData.brand}}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td pEditableColumn>
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input type="text" [(ngModel)]="rowData.color">
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{rowData.color}}
                    </ng-template>
                </p-cellEditor>
            </td>
        </tr>
    </ng-template>
</p-table>

If you require the edited row data, rowIndex and the selected field in the onEditInit, onEditComplete, and onEditCancel events, bind the row data to the pEditableColumn directive, the rowIndex pEditableColumnRowIndex property and field to the the field to the pEditableColumnField directive.

<td [pEditableColumn]="rowData" [pEditableColumnField]="'year'" [pEditableColumnRowIndex]="index">

When opening a cell for editing, the table will automatically focus the first input, textarea, or select element inside the output template. If you want to override this default behavior, you can pass a custom selector for the elements to focus into the pFocusCellSelector directive. This is useful when you would like the Tab and Shift+Tab keyboard navigation to focus on buttons or custom edit controls.

<td [pFocusCellSelector]="'input, .custom-edit-control'">

Row Editing

Row editing toggles the visibility of the all editors in the row at once and provides additional options to save and cancel editing. Row editing functionality is enabled by setting the editMode to "row" on table, defining a dataKey to uniquely identify a row, adding pEditableRow directive to the editable rows and defining the UI Controls with pInitEditableRow, pSaveEditableRow and pCancelEditableRow directives respectively.

Save and Cancel functionality implementation is left to the page author to provide more control over the editing business logic, example below utilizes a simple implementation where a row is cloned when editing is initialized and is saved or restored depending on the result of the editing. An implicit variable called "editing" is passed to the body template so you may come up with your own UI controls that implement editing based on your own requirements such as adding validations and styling. Note that pSaveEditableRow only switches the row to back view mode when there are no validation errors.

Moreover you may use setting pEditableRowDisabled property as true to disable editing for that particular row and in case you need to display rows in edit mode by default use editingRowKeys property which is a map whose key is the dataKey of the record where value is any arbitrary number greater than zero.

<p-table [value]="cars" dataKey="vin" editMode="row">
    <ng-template pTemplate="header">
        <tr>
            <th>Vin</th>
            <th>Year</th>
            <th>Brand</th>
            <th>Color</th>
            <th style="width:8em"></th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-editing="editing" let-ri="rowIndex">
        <tr [pEditableRow]="rowData">
            <td>
                {{rowData.vin}}
            </td>
            <td>
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input pInputText type="text" [(ngModel)]="rowData.year" year>
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{rowData.year}}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td>
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <p-dropdown [options]="brands" [(ngModel)]="rowData.brand" [style]="{'width':'100%'}"></p-dropdown>
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{rowData.brand}}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td>
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input pInputText type="text" [(ngModel)]="rowData.color">
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{rowData.color}}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td style="text-align:center">
                <button *ngIf="!editing" pButton type="button" pInitEditableRow icon="pi pi-pencil" class="ui-button-info" (click)="onRowEditInit(rowData)"></button>
                <button *ngIf="editing" pButton type="button" pSaveEditableRow icon="pi pi-check" class="ui-button-success" style="margin-right: .5em" (click)="onRowEditSave(rowData)"></button>
                <button *ngIf="editing" pButton type="button" pCancelEditableRow icon="pi pi-times" class="ui-button-danger" (click)="onRowEditCancel(rowData, ri)"></button>
            </td>
        </tr>
    </ng-template>
</p-table>
export class TableEditDemo implements OnInit {

    cars: Car[];

    brands: SelectItem[];

    clonedCars: { [s: string]: Car; } = {};

    constructor(private carService: CarService) { }

    ngOnInit() {
        this.carService.getCarsSmall().then(cars => this.cars = cars);

        this.brands = [
            {label: 'Audi', value: 'Audi'},
            {label: 'BMW', value: 'BMW'},
            {label: 'Fiat', value: 'Fiat'},
            {label: 'Ford', value: 'Ford'},
            {label: 'Honda', value: 'Honda'},
            {label: 'Jaguar', value: 'Jaguar'},
            {label: 'Mercedes', value: 'Mercedes'},
            {label: 'Renault', value: 'Renault'},
            {label: 'VW', value: 'VW'},
            {label: 'Volvo', value: 'Volvo'}
        ];
    }

    onRowEditInit(car: Car) {
        this.clonedCars[car.vin] = {...car};
    }

    onRowEditSave(car: Car) {
        if (car.year > 0)
            delete this.clonedCars[car.vin];
        else
            this.messageService.add({severity:'error', summary: 'Error', detail:'Year is required'});
    }

    onRowEditCancel(car: Car, index: number) {
        this.cars[index] = this.clonedCars[car.vin];
        delete this.clonedCars[car.vin];
    }

}

See the live example.

Expandable Rows

Row expansion allows displaying detailed content for a particular row. To use this feature, add a template named rowexpansion and use the pRowToggler directive whose value is the row data instance on an element of your choice whose click event toggles the expansion. This enables providing your custom UI such as buttons, links and so on. Example below uses an anchor with an icon as a toggler. Setting pRowTogglerDisabled as true disables the toggle event for the element.

<p-table [columns]="cols" [value]="cars" dataKey="vin">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th style="width: 2.25em"></th>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-expanded="expanded" let-columns="columns">
        <tr>
            <td>
                <a href="#" [pRowToggler]="rowData">
                    <i [ngClass]="expanded ? 'pi pi-fw pi-chevron-circle-down' : 'pi pi-fw pi-chevron-circle-right'"></i>
                </a>
            </td>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
    <ng-template pTemplate="rowexpansion" let-rowData let-columns="columns">
        <tr>
            <td [attr.colspan]="columns.length + 1">
                <div class="p-grid -fluid" style="font-size:16px;padding:20px">
                    <div class="p-col-12 p-md-3" style="text-align:center">
                        <img [attr.alt]="rowData.brand" src="assets/showcase/images/demo/car/{{rowData.brand}}.png">
                    </div>
                    <div class="p-col-12 p-md-9">
                        <div class="p-grid">
                            <div class="p-col-12">
                                <b>Vin:</b> {{rowData.vin}}
                            </div>
                            <div class="p-col-12">
                                <b>Year:</b> {{rowData.year}}
                            </div>
                            <div class="p-col-12">
                                <b>Brand:</b> {{rowData.brand}}
                            </div>
                            <div class="p-col-12">
                                <b>Color:</b> {{rowData.color}}
                            </div>
                        </div>
                    </div>
                </div>
            </td>
        </tr>
    </ng-template>
</p-table>

Multiple rows can be expanded at the same time, if you prefer a single row expansion at any time set rowExpandMode property to "single". All rows are collapsed initially and providing expandedRowKeys property whose value is the dataKeys of the rows to be expanded enables rendering these rows as expanded. A dataKey must be defined for this feature.

<p-table [columns]="cols" [value]="cars" dataKey="vin" [expandedRowKeys]="expandedRows">
   ...
</p-table>

See the live example.

Column Resize

Columns can be resized using drag drop by setting the resizableColumns to true. There are two resize modes; "fit" and "expand". Fit is the default one and the overall table width does not change when a column is resized. In "expand" mode, table width also changes along with the column width. onColumnResize is a callback that passes the resized column header as a parameter. For dynamic columns, setting pResizableColumnDisabled property as true disables resizing for that particular column.

<p-table [columns]="cols" [value]="cars" [resizableColumns]="true">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns" pResizableColumn>
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns" class="ui-resizable-column">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

It is important to note that when you need to change column widths, since table width is 100%, giving fixed pixel widths does not work well as browsers scale them, instead give percentage widths.

<p-table [value]="cars" [resizableColumns]="true">
    <ng-template pTemplate="header">
        <tr>
            <th style="width:20%">Vin</th>
            <th style="width:30%">Year</th>
            <th style="width:15%">Brand</th>
            <th style="width:35%">Color</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-car>
        <tr>
            <td>{{car.vin}}</td>
            <td>{{car.year}}</td>
            <td>{{car.brand}}</td>
            <td>{{car.color}}</td>
        </tr>
    </ng-template>
</p-table>

Note: Scrollable tables require a column group to support resizing.

<p-table [columns]="cols" [value]="cars" [scrollable]="true" scrollHeight="200px" [resizableColumns]="true">
    <ng-template pTemplate="colgroup" let-columns>
        <colgroup>
            <col *ngFor="let col of columns">
        </colgroup>
    </ng-template>
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns" pResizableColumn>
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns" class="ui-resizable-column">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

See the live example.

Column Reordering

Columns can be reordered using drag drop by setting the reorderableColumns to true and adding pReorderableColumn directive to the columns that can be dragged. Note that columns should be dynamic for reordering to work. For dynamic columns, setting pReorderableColumnDisabled property as true disables reordering for that particular column.

<p-table [columns]="cols" [value]="cars" [reorderableColumns]="true">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns" pReorderableColumn>
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

See the live example.

Rows Reordering

Row reordering is enabled by adding pReorderableRow directive with a row index binding to the rows that can be reordered with drag and drop. The optional pReorderableRowDisabled property is available to disable dragging for a particular row. In addition, drag handle should get pReorderableRowHandle directive to specify which element is used to initiate the dragging.

<p-table [columns]="cols" [value]="cars">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th style="width:2em"></th>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns" let-index="rowIndex">
        <tr [pReorderableRow]="index">
            <td>
                <i class="pi pi-bars" pReorderableRowHandle></i>
            </td>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

See the live example.

Data Export

Table can export its data in CSV format using the built-in exportCSV() function. By default whole data is exported, if you'd like to export only the selection then pass a config object with selectionOnly property as true. Note that columns should be dynamic for export functionality to work and column objects must define field/header properties.

PDF and EXCEL export are also available using 3rd party libraries such as jspdf. Example below demonstrates how to implement all three export options.

<p-table #dt [columns]="cols" [value]="cars" selectionMode="multiple" [(selection)]="selectedCars">
    <ng-template pTemplate="caption">
        <div class="ui-helper-clearfix" style="text-align: left">
            <button type="button" pButton icon="pi pi-file-o" iconPos="left" label="CSV" (click)="dt.exportCSV()" style="margin-right: 0.5em;"></button>
            <button type="button" pButton icon="pi pi-file-excel" iconPos="left" label="EXCEL" (click)="exportExcel()" style="margin-right: 0.5em;" class="ui-button-success"></button>
            <button type="button" pButton icon="pi pi-file-pdf" iconPos="left" label="PDF" (click)="exportPdf()" class="ui-button-warning"></button>
            <button type="button" pButton icon="pi pi-file" iconPos="left" label="CSV - Selection Only" (click)="dt.exportCSV({selectionOnly:true})" style="float:right"></button>
        </div>
    </ng-template>
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr [pSelectableRow]="rowData">
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>
export class TableExportDemo implements OnInit {

    cars: Car[];

    selectedCars: Car[];

    cols: any[];

    columns: any[];

    constructor(private carService: CarService) { }

    ngOnInit() {
        this.carService.getCarsSmall().then(cars => this.cars = cars);

        this.cols = [
            { field: 'vin', header: 'Vin' },
            { field: 'year', header: 'Year' },
            { field: 'brand', header: 'Brand' },
            { field: 'color', header: 'Color' }
        ];

        this.exportColumns = this.cols.map(col => ({title: col.header, dataKey: col.field}));
    }

    exportPdf() {
        import("jspdf").then(jsPDF => {
            import("jspdf-autotable").then(x => {
                const doc = new jsPDF.default(0,0);
                doc.autoTable(this.columns, this.cars);
                doc.save('primengTable.pdf');
            })
        })
    }

    exportExcel() {
        import("xlsx").then(xlsx => {
            const worksheet = xlsx.utils.json_to_sheet(this.getCars());
            const workbook = { Sheets: { 'data': worksheet }, SheetNames: ['data'] };
            const excelBuffer: any = xlsx.write(workbook, { bookType: 'xlsx', type: 'array' });
            this.saveAsExcelFile(excelBuffer, "primengTable");
        });
    }

    saveAsExcelFile(buffer: any, fileName: string): void {
        import("file-saver").then(FileSaver => {
            let EXCEL_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8';
            let EXCEL_EXTENSION = '.xlsx';
            const data: Blob = new Blob([buffer], {
                type: EXCEL_TYPE
            });
            FileSaver.saveAs(data, fileName + '_export_' + new Date().getTime() + EXCEL_EXTENSION);
        });
    }

    getCars() {
        let cars = [];
        for(let car of this.cars) {
            car.year = car.year.toString();
            cars.push(car);
        }
        return cars;
    }
}

See the live example.

Scrolling

Table supports both horizontal and vertical scrolling as well as frozen columns and rows. Additionally, virtualScroll mode enables dealing with large datasets by rendering data on demand during scrolling.

Sample below uses vertical scrolling where headers are fixed and data is scrollable.

<p-table [columns]="cols" [value]="cars" [scrollable]="true" scrollHeight="200px">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

Flex Scroll

In cases where viewport should adjust itself according to the table parent's height instead of a fixed viewport height, set scrollHeight option as flex. In example below, table is inside a Dialog where viewport size dynamically responds to the dialog size changes such as resizing or maximizing.

<button type="button" (click)="showDialog()" pButton icon="pi pi-external-link" label="View"></button>
<p-dialog header="Flexible ScrollHeight" [(visible)]="dialogVisible" [style]="{width: '50vw'}" [baseZIndex]="10000" [maximizable]="true" [modal]="true" [resizable]="true" [contentStyle]="{height: '300px'}">
    <p-table [columns]="cols" [value]="cars1" [scrollable]="true" scrollHeight="flex">
        <ng-template pTemplate="header" let-columns>
            <tr>
                <th *ngFor="let col of columns">
                    {{col.header}}
                </th>
            </tr>
        </ng-template>
        <ng-template pTemplate="body" let-rowData let-columns="columns">
            <tr>
                <td *ngFor="let col of columns">
                    {{rowData[col.field]}}
                </td>
            </tr>
        </ng-template>
    </p-table>
    <p-footer>
        <button type="button" pButton icon="pi pi-check" (click)="dialogVisible=false" label="Yes"></button>
        <button type="button" pButton icon="pi pi-times" (click)="dialogVisible=false" label="No" class="ui-button-secondary"></button>
    </p-footer>
</p-dialog>

Full Page Scroll

FlexScroll can also be used for cases where scrollable viewport should be responsive with respect to the window size. See the Full Page demo for an example.

<div class="page-content" style="height: calc(100vh - 100px)">
    <p-table [columns]="cols" [value]="virtualCars" [scrollable]="true" [rows]="100" scrollHeight="flex"
        [virtualScroll]="true" [virtualRowHeight]="34" [lazy]="true" (onLazyLoad)="loadCarsLazy($event)">
        <ng-template pTemplate="caption">
            Virtual Scrolling with Lazy Loading and Full Page Viewport
        </ng-template>
        <ng-template pTemplate="header" let-columns>
            <tr>
                <th *ngFor="let col of columns">
                    {{col.header}}
                </th>
            </tr>
        </ng-template>
        <ng-template pTemplate="body" let-rowData let-columns="columns">
            <tr style="height:34px">
                <td *ngFor="let col of columns">
                    {{rowData[col.field]}}
                </td>
            </tr>
        </ng-template>
        <ng-template pTemplate="loadingbody" let-columns="columns">
            <tr style="height:34px">
                <td *ngFor="let col of columns">
                    <div class="loading-text"></div>
                </td>
            </tr>
        </ng-template>
    </p-table>
</div>

Horizontal Scroll

In horizontal scrolling, it is required to give fixed widths to columns. In general when customizing the column widths of scrollable tables, use colgroup as below to avoid misalignment issues as it will apply both the header, body and footer sections which are different separate elements internally.

<p-table [columns]="cols" [value]="cars" [scrollable]="true" [style]="{width:'500px'}">
    <ng-template pTemplate="colgroup" let-columns>
        <colgroup>
            <col *ngFor="let col of columns" style="width:250px">
        </colgroup>
    </ng-template>
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

Horizontal and Vertical scrolling can be combined as well on the same table.

<p-table [columns]="cols" [value]="cars3" [scrollable]="true" [style]="{width:'500px'}" scrollHeight="200px">
    <ng-template pTemplate="colgroup" let-columns>
        <colgroup>
            <col *ngFor="let col of columns" style="width:250px">
        </colgroup>
    </ng-template>
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

Frozen Rows and Columns

Certain rows can be fixed by using the frozenValue property along with the "frozenrows" template.

<p-table [columns]="cols" [value]="cars4" [frozenValue]="frozenCars" [scrollable]="true" scrollHeight="200px">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="frozenrows" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                <b>{{rowData[col.field]}}</b>
            </td>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

Particular columns can be made fixed where others remain scrollable, there are two ways to implement this functionality, either define a frozenColumns property if your frozen columns are dynamic or use frozenbody template. The width of the frozen section also must be defined with frozenWidth property. Templates including header, body and footer apply to the frozen section as well, however if require different content for the frozen section use frozenheader, frozenbody and frozenfooter instead. First example below uses dynamic frozen columns and second one demonstrates how to use frozen templates with column grouping.

<p-table [columns]="scrollableCols" [frozenColumns]="frozenCols" [value]="cars5" [scrollable]="true" scrollHeight="200px" frozenWidth="200px">
    <ng-template pTemplate="colgroup" let-columns>
        <colgroup>
            <col *ngFor="let col of columns" style="width:200px">
        </colgroup>
    </ng-template>
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

<p-table [value]="sales" [scrollable]="true" scrollHeight="150px" frozenWidth="200px">
    <ng-template pTemplate="frozenheader">
        <tr>
            <th style="width:200px;height:84px">Brand</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="frozenbody" let-sale>
        <tr>
            <td>{{sale.brand}}</td>
        </tr>
    </ng-template>
    <ng-template pTemplate="header">
        <tr>
            <th colspan="4">Sale Rate</th>
        </tr>
        <tr>
            <th colspan="2">Sales</th>
            <th colspan="2">Profits</th>
        </tr>
        <tr>
            <th>Last Year</th>
            <th>This Year</th>
            <th>Last Year</th>
            <th>This Year</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-sale>
        <tr>
            <td>{{sale.lastYearSale}}</td>
            <td>{{sale.thisYearSale}}</td>
            <td>{{sale.lastYearProfit}}</td>
            <td>{{sale.thisYearProfit}}</td>
        </tr>
    </ng-template>
</p-table>

When frozen columns are enabled, frozen and scrollable cells may have content with varying height which leads to misalignment. To avoid a performance hit, Table avoids expensive calculations to align the row heights as it can be easily done with templating.

<ng-template pTemplate="body" let-rowData let-columns="columns">
    <tr style="30px">
        <td *ngFor="let col of columns">
            {{rowData[col.field]}}
        </td>
    </tr>
</ng-template>

When column widths need to vary or resizable columns is activated, use colgroup template to avoid misalignment issues and apply percentage values since table width is 100%.

<p-table [columns]="cols" [value]="cars" [scrollable]="true" scrollHeight="200px">
    <ng-template pTemplate="colgroup" let-columns>
        <colgroup>
            <col *ngFor="let col of columns" [style.width]="col.width">
        </colgroup>
    </ng-template>
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

Virtual Scrolling

VirtualScroller is a performant approach to handle huge data efficiently. Setting virtualScroll property as true and providing a virtualRowHeight in pixels would be enough to enable this functionality. It is also suggested to use the same virtualRowHeight value on the tr element inside the body template.

<p-table [columns]="cols" [value]="cars" [scrollable]="true" [rows]="100" scrollHeight="250px"
    [virtualScroll]="true" [virtualRowHeight]="34">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr style="height:34px">
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>
export class TableVirtualScrollDemo implements OnInit {

    cars: Car[];

    cols: any[];

    constructor(private carService: CarService) {}

    ngOnInit() {
        this.cols = [
            {field: 'vin', header: 'Vin'},
            {field: 'year', header: 'Year'},
            {field: 'brand', header: 'Brand'},
            {field: 'color', header: 'Color'}
        ];

        this.cars = Array.from({length: 10000}).map(() => this.carService.generateCar());
    }
}

Example above uses preloaded data, in order to load data on demand, lazy mode should be enabled where chunks of data are loaded from a datasource on scroll. To implement lazy loading, enable lazy attribute, initialize your data as a placeholder collection with a length and finally implement a method callback using onLazyLoad that actually loads a chunk from a datasource. onLazyLoad gets a LazyLoadEvent object that contains information about the chunk of data to load such as the index and number of items to load. Notice that a special "loadingbody" template is available to provide feedback to the users about the loading status of a scroll event.

<p-table [columns]="cols" [value]="virtualCars" [scrollable]="true" [rows]="100" scrollHeight="250px"
    [virtualScroll]="true" [virtualRowHeight]="34" [lazy]="true" (onLazyLoad)="loadCarsLazy($event)">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr style="height:34px">
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
    <ng-template pTemplate="loadingbody" let-columns="columns">
        <tr style="height:34px">
            <td *ngFor="let col of columns">
                <div class="loading-text"></div>
            </td>
        </tr>
    </ng-template>
</p-table>
export class TableVirtualScrollDemo implements OnInit {

    cars: Car[];

    virtualCars: Car[];

    cols: any[];

    constructor(private carService: CarService) {}

    ngOnInit() {
        this.cols = [
            {field: 'vin', header: 'Vin'},
            {field: 'year', header: 'Year'},
            {field: 'brand', header: 'Brand'},
            {field: 'color', header: 'Color'}
        ];

        this.cars = Array.from({length: 10000}).map(() => this.carService.generateCar());
        this.virtualCars = Array.from({length: 10000});
    }

    loadCarsLazy(event: LazyLoadEvent) {
        //simulate remote connection with a timeout
        setTimeout(() => {
            //load data of required page
            let loadedCars = this.cars.slice(event.first, (event.first + event.rows));

            //populate page of virtual cars
            Array.prototype.splice.apply(this.virtualCars, [...[event.first, event.rows], ...loadedCars]);

            //trigger change detection
            this.virtualCars = [...this.virtualCars];
        }, Math.random() * 1000 + 250);
    }
}

See the scroll and virtual scroll examples.

Lazy Loading

Lazy mode is handy to deal with large datasets, instead of loading the entire data, small chunks of data is loaded by invoking onLazyLoad callback everytime paging, sorting and filtering happens. To implement lazy loading, enable lazy attribute and provide a method callback using onLazyLoad that actually loads the data from a remote datasource. onLazyLoad gets an event object that contains information about how the data should be loaded. It is also important to assign the logical number of rows to totalRecords by doing a projection query for paginator configuration so that paginator displays the UI assuming there are actually records of totalRecords size although in reality they aren't as in lazy mode, only the records that are displayed on the current page exist.

<p-table [columns]="cols" [value]="cars" [lazy]="true" (onLazyLoad)="loadCarsLazy($event)" [paginator]="true" [rows]="10" [totalRecords]="totalRecords">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                 {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>
loadData(event: LazyLoadEvent) {
    //event.first = First row offset
    //event.rows = Number of rows per page
    //event.sortField = Field name to sort in single sort mode
    //event.sortOrder = Sort order as number, 1 for asc and -1 for dec in single sort mode
    //multiSortMeta: An array of SortMeta objects used in multiple columns sorting. Each SortMeta has field and order properties.
    //filters: Filters object having field as key and filter value, filter matchMode as value
    //globalFilter: Value of the global filter if available
    this.cars = //do a request to a remote datasource using a service and return the cars that match the lazy load criteria
}

See the live example.

TableState

Stateful table allows keeping the state such as page, sort and filtering either at local storage or session storage so that when the page is visited again, table would render the data using its last settings. Enabling state is easy as defining a unique stateKey, the storage to keep the state is defined with the stateStorage property that accepts session for sessionStorage and local for localStorage. Currently following features are supported by TableState; paging, sorting, filtering, column resizing, column reordering, row expansion and row selection.

<p-table #dt1 [columns]="cols" [value]="cars" [paginator]="true" [rows]="10" dataKey="vin" [resizableColumns]="true" [reorderableColumns]="true"
    selectionMode="single" [(selection)]="selectedCar" stateStorage="session" stateKey="statedemo-session">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns" [pSortableColumn]="col.field" pResizableColumn pReorderableColumn>
                {{col.header}}
                <p-sortIcon [field]="col.field"></p-sortIcon>
            </th>
        </tr>
        <tr>
            <th *ngFor="let col of columns" [ngSwitch]="col.field" class="ui-fluid">
                <input pInputText type="text" (input)="dt1.filter($event.target.value, col.field, col.filterMatchMode)" [value]="dt1.filters[col.field]?.value">
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr [pSelectableRow]="rowData">
            <td *ngFor="let col of columns">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

See the live example.

Responsive

Table columns are displayed as stacked in responsive mode if the screen size becomes smaller than a certain breakpoint value. This feature is enabled by setting responsive to true and adding an element whose class name is "p-column-title" to the body cells.

<p-table [columns]="cols" [value]="cars" [responsive]="true">
    <ng-template pTemplate="caption">
        List of Cars
    </ng-template>
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                <span class="p-column-title">{{col.header}}</span>
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
    <ng-template pTemplate="summary">
        There are {{cars?.length}} cars
    </ng-template>
</p-table>

See the live example.

EmptyMessage

When there is no data, emptymessage template can be used to display a message.

<p-table [columns]="cols" [value]="cars">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr>
            <td *ngFor="let col of columns">
                    {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
    <ng-template pTemplate="emptymessage" let-columns>
        <tr>
            <td [attr.colspan]="columns.length">
                No records found
            </td>
        </tr>
    </ng-template>
</p-table>

Loading Status

Table has a loading property, when enabled a spinner icon is displayed to indicate data load. An optional loadingIcon property can be passed in case you'd like a different loading icon.

<p-table [value]="cars" [loading]="loading">
    <ng-template pTemplate="header">
        <tr>
            <th>Vin</th>
            <th>Year</th>
            <th>Brand</th>
            <th>Color</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-car>
        <tr>
            <td>{{car.vin}}</td>
            <td>{{car.year}}</td>
            <td>{{car.brand}}</td>
            <td>{{car.color}}</td>
        </tr>
    </ng-template>
</ng-template>
</p-table>
export class TableDemo implements OnInit {

    loading: boolean;

    cars: Car[];

    constructor(private carService: CarService) { }

    ngOnInit() {
        this.loading = true;
        setTimeout(() => {
            this.carService.getCarsSmall().then(cars => this.cars = cars);
            this.loading = false;
        }, 1000);
    }
}

Styling Certain Rows and Columns

Certain rows and cells can easily be styled using templating features. In example below, the row whose vin property is '123' will get the 'success' style class. Example here paint the background of the last cell using a colgroup and highlights rows whose year is older than 2000.

<p-table [columns]="cols" [value]="cars">
    <ng-template pTemplate="colgroup" let-columns>
        <colgroup>
            <col>
            <col>
            <col>
            <col style="background-color:#FFD54F !important">
        </colgroup>
    </ng-template>
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th *ngFor="let col of columns">
                {{col.header}}
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-rowData let-columns="columns">
        <tr [ngClass]="rowData.year > 2010 ? 'old-car' : null">
            <td *ngFor="let col of columns" [ngClass]="rowData[col.field] < 2000 ? 'very-old-car' : null">
                {{rowData[col.field]}}
            </td>
        </tr>
    </ng-template>
</p-table>

See the live example.

Performance Tips

  • When selection is enabled use dataKey to avoid deep checking when comparing objects.
  • Use rowTrackBy to avoid unnecessary dom operations.
  • Prefer lazy loading for large datasets.

Theming

Table supports various themes featuring Material, Bootstrap, Fluent as well as your own custom themes via the Designer tool.

Resources

Visit the PrimeNG Table showcase for demos and documentation.