Table

Table displays data in tabular format.


import { TableModule } from 'primeng/table';

DataTable requires a collection to display along with column components for the representation of the data.

CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories24
nvklal433Black WatchAccessories61
zz21cz3c1Blue BandFitness2
244wgerg2Blue T-ShirtClothing25
h456wer53BraceletAccessories73

<p-table [value]="products" [tableStyle]="{ 'min-width': '50rem' }">
    <ng-template pTemplate="header">
        <tr>
            <th>Code</th>
            <th>Name</th>
            <th>Category</th>
            <th>Quantity</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>{{ product.code }}</td>
            <td>{{ product.name }}</td>
            <td>{{ product.category }}</td>
            <td>{{ product.quantity }}</td>
        </tr>
    </ng-template>
</p-table>

Columns can be defined dynamically using the *ngFor directive.

Code Name Category Quantity
f230fh0g3 Bamboo Watch Accessories 24
nvklal433 Black Watch Accessories 61
zz21cz3c1 Blue Band Fitness 2
244wgerg2 Blue T-Shirt Clothing 25
h456wer53 Bracelet Accessories 73

<p-table [columns]="cols" [value]="products" [tableStyle]="{ 'min-width': '50rem' }">
    <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>

Custom content at header, body and footer sections are supported via templating.

Products
NameImagePriceCategoryReviewsStatus
Bamboo WatchBamboo Watch$65.00Accessories
INSTOCK
Black WatchBlack Watch$72.00Accessories
INSTOCK
Blue BandBlue Band$79.00Fitness
LOWSTOCK
Blue T-ShirtBlue T-Shirt$29.00Clothing
INSTOCK
BraceletBracelet$15.00Accessories
INSTOCK

<p-table [value]="products" [tableStyle]="{'min-width': '60rem'}">
    <ng-template pTemplate="caption">
        <div class="flex align-items-center justify-content-between">
            Products
            <p-button icon="pi pi-refresh"></p-button>
        </div>
    </ng-template>
    <ng-template pTemplate="header">
        <tr>
            <th>Name</th>
            <th>Image</th>
            <th>Price</th>
            <th>Category</th>
            <th>Reviews</th>
            <th>Status</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>{{product.name}}</td>
            <td><img [src]="'https://primefaces.org/cdn/primeng/images/demo/product/' + product.image" [alt]="product.name" width="100" class="shadow-4" /></td>
            <td>{{product.price | currency:'USD'}}</td>
            <td>{{product.category}}</td>
            <td><p-rating [ngModel]="product.rating" [readonly]="true" [cancel]="false"></p-rating></td>
            <td><p-tag [value]="product.inventoryStatus" [severity]="getSeverity(product.inventoryStatus)"></p-tag></td>
        </tr>
    </ng-template>
    <ng-template pTemplate="summary">
        <div class="flex align-items-center justify-content-between">
            In total there are {{products ? products.length : 0 }} products.
        </div>
    </ng-template>
</p-table>

In addition to a regular table, alternatives with alternative sizes are available.

Small
Normal
Large
CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories24
nvklal433Black WatchAccessories61
zz21cz3c1Blue BandFitness2
244wgerg2Blue T-ShirtClothing25
h456wer53BraceletAccessories73

<div class="flex justify-content-center mb-3">
    <p-selectButton [options]="sizes" [(ngModel)]="selectedSize" [multiple]="false" optionLabel="name" optionValue="class"></p-selectButton>
</div>
<p-table [value]="products" [tableStyle]="{ 'min-width': '50rem' }" [styleClass]="selectedSize.class">
    <ng-template pTemplate="header">
        <tr>
            <th>Code</th>
            <th>Name</th>
            <th>Category</th>
            <th>Quantity</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>{{ product.code }}</td>
            <td>{{ product.name }}</td>
            <td>{{ product.category }}</td>
            <td>{{ product.quantity }}</td>
        </tr>
    </ng-template>
</p-table>

Adding p-datatable-gridlines class displays grid lines.

Header
CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories24
nvklal433Black WatchAccessories61
zz21cz3c1Blue BandFitness2
244wgerg2Blue T-ShirtClothing25
h456wer53BraceletAccessories73

<p-table [value]="products" styleClass="p-datatable-gridlines" [tableStyle]="{ 'min-width': '50rem' }">
    <ng-template pTemplate="caption"> Header </ng-template>
    <ng-template pTemplate="header">
        <tr>
            <th>Code</th>
            <th>Name</th>
            <th>Category</th>
            <th>Quantity</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>{{ product.code }}</td>
            <td>{{ product.name }}</td>
            <td>{{ product.category }}</td>
            <td>{{ product.quantity }}</td>
        </tr>
    </ng-template>
    <ng-template pTemplate="summary"> Footer </ng-template>
</p-table>

Adding p-datatable-striped class displays striped rows.

CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories24
nvklal433Black WatchAccessories61
zz21cz3c1Blue BandFitness2
244wgerg2Blue T-ShirtClothing25
h456wer53BraceletAccessories73

<p-table [value]="products" styleClass="p-datatable-striped" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th>Code</th>
            <th>Name</th>
            <th>Category</th>
            <th>Quantity</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>{{product.code}}</td>
            <td>{{product.name}}</td>
            <td>{{product.category}}</td>
            <td>{{product.quantity}}</td>
        </tr>
    </ng-template>
</p-table>

Certain rows or cells can easily be styled based on conditions.

CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories
24
nvklal433Black WatchAccessories
61
zz21cz3c1Blue BandFitness
2
244wgerg2Blue T-ShirtClothing
25
h456wer53BraceletAccessories
73

<p-table [value]="products" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th>Code</th>
            <th>Name</th>
            <th>Category</th>
            <th>Quantity</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr [ngClass]="{'row-accessories': product.category === 'Accessories'}">
            <td>{{product.code}}</td>
            <td>{{product.name}}</td>
            <td>{{product.category}}</td>
            <td>
                <div [ngClass]="{'outofstock': product.quantity === 0, 'lowstock': (product.quantity > 0 && product.quantity < 10),'instock': product.quantity > 10}">
                    {{product.quantity}}
                </div>
            </td>
        </tr>
    </ng-template>
</p-table>

When there is not enough space for the table to fit all the content efficiently, table displays a horizontal scrollbar. It is suggested to give a min-width to the table to avoid design issues due wrapping of cell contents.

Following table displays a horizontal scrollbar when viewport is smaller than 50rem.

NamePriceCategoryQuantityStatusReviews
Bamboo Watch$65.00Accessories24INSTOCK
Black Watch$72.00Accessories61INSTOCK
Blue Band$79.00Fitness2LOWSTOCK
Blue T-Shirt$29.00Clothing25INSTOCK
Bracelet$15.00Accessories73INSTOCK

<p-table [value]="products" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th>Name </th>
            <th>Price</th>
            <th>Category</th>
            <th>Quantity</th>
            <th>Status</th>
            <th>Reviews</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product let-columns="columns">
        <tr>
            <td>{{product.name}}</td>
            <td>{{product.price | currency:'USD'}}</td>
            <td>{{product.category}}</td>
            <td>{{product.quantity}}</td>
            <td><p-tag [value]="product.inventoryStatus" [severity]="getSeverity(product.inventoryStatus)"></p-tag></td>
            <td><p-rating [ngModel]="product.rating" [readonly]="true" [cancel]="false"></p-rating></td>
        </tr>
    </ng-template>
</p-table>

In stack layout, columns are displayed as stacked after a certain breakpoint. Default is '960px' as max-width. This feature is enabled by setting responsiveLayout to stack and adding an element with p-column-title style class to the body cells.

NamePriceCategoryQuantityStatusReviews
NameBamboo WatchPrice$65.00CategoryAccessoriesQuantity24INSTOCKReviews
NameBlack WatchPrice$72.00CategoryAccessoriesQuantity61INSTOCKReviews
NameBlue BandPrice$79.00CategoryFitnessQuantity2LOWSTOCKReviews
NameBlue T-ShirtPrice$29.00CategoryClothingQuantity25INSTOCKReviews
NameBraceletPrice$15.00CategoryAccessoriesQuantity73INSTOCKReviews

<p-table [value]="products" responsiveLayout="stack" [breakpoint]="'960px'" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header" let-columns>
        <tr>
            <th>Name</th>
            <th>Price</th>
            <th>Category</th>
            <th>Quantity</th>
            <th>Status</th>
            <th>Reviews</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product let-columns="columns">
        <tr>
            <td><span class="p-column-title">Name</span>{{product.name}}</td>
            <td><span class="p-column-title">Price</span>{{product.price | currency:'USD'}}</td>
            <td><span class="p-column-title">Category</span>{{product.category}}</td>
            <td><span class="p-column-title">Quantity</span>{{product.quantity}}</td>
            <td><p-tag [value]="product.inventoryStatus" [severity]="getSeverity(product.inventoryStatus)"></p-tag></td>
            <td><span class="p-column-title">Reviews</span><p-rating [ngModel]="product.rating" [readonly]="true" [cancel]="false"></p-rating></td>
        </tr>
    </ng-template>
</p-table>

Pagination is enabled by setting paginator property to true and defining a rows property to specify the number of rows per page. For server side pagination, see the lazy loading example.

NameCountryCompanyRepresentative
James ButtAlgeriaBenton, John B JrIoni Bowcher
Josephine DarakjyEgyptChanay, Jeffrey A EsqAmy Elsner
Art VenerePanamaChemel, James L CpaAsiya Javayant
Lenna PaprockiSloveniaFeltz Printing ServiceXuxue Feng
Donette FollerSouth AfricaPrinting DimensionsAsiya Javayant
Showing 1 to 5 of 200 entries
5

<p-table
    [value]="customers"
    [paginator]="true"
    [rows]="5"
    [showCurrentPageReport]="true"
    [tableStyle]="{ 'min-width': '50rem' }"
    currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries"
    [rowsPerPageOptions]="[5, 10, 20]"
>
    <ng-template pTemplate="header">
        <tr>
            <th style="width:25%">Name</th>
            <th style="width:25%">Country</th>
            <th style="width:25%">Company</th>
            <th style="width:25%">Representative</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-customer>
        <tr>
            <td>{{ customer.name }}</td>
            <td>{{ customer.country.name }}</td>
            <td>{{ customer.company }}</td>
            <td>{{ customer.representative.name }}</td>
        </tr>
    </ng-template>
    <ng-template pTemplate="paginatorleft">
        <p-button type="button" icon="pi pi-plus" styleClass="p-button-text"></p-button>
    </ng-template>
    <ng-template pTemplate="paginatorright">
        <p-button type="button" icon="pi pi-cloud" styleClass="p-button-text"></p-button>
    </ng-template>
</p-table>

paginator localization information such as page numbers and rows per page options are defined with the paginatorLocale property which defaults to the user locale.

NameCountryCompanyRepresentative
James ButtAlgeriaBenton, John B JrIoni Bowcher
Josephine DarakjyEgyptChanay, Jeffrey A EsqAmy Elsner
Art VenerePanamaChemel, James L CpaAsiya Javayant
Lenna PaprockiSloveniaFeltz Printing ServiceXuxue Feng
Donette FollerSouth AfricaPrinting DimensionsAsiya Javayant
Showing 1 to 5 of 200 entries
 

<p-table
    [value]="customers"
    [paginator]="true"
    [rows]="5"
    [showCurrentPageReport]="true"
    [tableStyle]="{ 'min-width': '50rem' }"
    currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries"
    [rowsPerPageOptions]="[10, 25, 50]"
    paginatorLocale="fa-IR"
>
    <ng-template pTemplate="header">
        <tr>
            <th style="width:25%">Name</th>
            <th style="width:25%">Country</th>
            <th style="width:25%">Company</th>
            <th style="width:25%">Representative</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-customer>
        <tr>
            <td>{{ customer.name }}</td>
            <td>{{ customer.country.name }}</td>
            <td>{{ customer.company }}</td>
            <td>{{ customer.representative.name }}</td>
        </tr>
    </ng-template>
    <ng-template pTemplate="paginatorleft">
        <p-button type="button" icon="pi pi-plus" styleClass="p-button-text"></p-button>
    </ng-template>
    <ng-template pTemplate="paginatorright">
        <p-button type="button" icon="pi pi-cloud" styleClass="p-button-text"></p-button>
    </ng-template>
</p-table>

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

NameCountryCompanyRepresentative
James ButtAlgeriaBenton, John B JrIoni Bowcher
Josephine DarakjyEgyptChanay, Jeffrey A EsqAmy Elsner
Art VenerePanamaChemel, James L CpaAsiya Javayant
Lenna PaprockiSloveniaFeltz Printing ServiceXuxue Feng
Donette FollerSouth AfricaPrinting DimensionsAsiya Javayant
Simona MorascaEgyptChapman, Ross E EsqIvan Magalhaes
Mitsue TollnerParaguayMorlong AssociatesIvan Magalhaes
Leota DilliardSerbiaCommercial PressOnyama Limba
Sage WieserEgyptTruhlar And Truhlar AttysIvan Magalhaes
Kris MarrierMexicoKing, Christopher A EsqOnyama Limba
Showing 1 to 10 of 200 entries
10

<div class="mb-3">
    <p-button type="button" icon="pi pi-chevron-left" (click)="prev()" [disabled]="isFirstPage()" styleClass="p-button-text"></p-button>
    <p-button type="button" icon="pi pi-refresh" (click)="reset()" styleClass="p-button-text"></p-button>
    <p-button type="button" icon="pi pi-chevron-right" (click)="next()" [disabled]="isLastPage()" styleClass="p-button-text"></p-button>
</div>
<p-table
    [value]="customers"
    [paginator]="true"
    [rows]="5"
    [showCurrentPageReport]="true"
    [tableStyle]="{ 'min-width': '50rem' }"
    currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries"
    (onPage)="pageChange($event)"
    [rowsPerPageOptions]="[10, 25, 50]"
>
    <ng-template pTemplate="header">
        <tr>
            <th style="width:25%">Name</th>
            <th style="width:25%">Country</th>
            <th style="width:25%">Company</th>
            <th style="width:25%">Representative</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-customer>
        <tr>
            <td>{{ customer.name }}</td>
            <td>{{ customer.country.name }}</td>
            <td>{{ customer.company }}</td>
            <td>{{ customer.representative.name }}</td>
        </tr>
    </ng-template>
    <ng-template pTemplate="paginatorleft">
        <p-button type="button" icon="pi pi-plus" styleClass="p-button-text"></p-button>
    </ng-template>
    <ng-template pTemplate="paginatorright">
        <p-button type="button" icon="pi pi-cloud" styleClass="p-button-text"></p-button>
    </ng-template>
</p-table>

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.

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.

Code Name Category Quantity Price
f230fh0g3Bamboo WatchAccessories24$65.00
nvklal433Black WatchAccessories61$72.00
zz21cz3c1Blue BandFitness2$79.00
244wgerg2Blue T-ShirtClothing25$29.00
h456wer53BraceletAccessories73$15.00

<p-table [value]="products" [tableStyle]="{'min-width': '60rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th pSortableColumn="code" style="width:20%">Code <p-sortIcon field="code"></p-sortIcon></th>
            <th pSortableColumn="name" style="width:20%">Name <p-sortIcon field="name"></p-sortIcon></th>
            <th pSortableColumn="category" style="width:20%">Category <p-sortIcon field="category"></p-sortIcon></th>
            <th pSortableColumn="quantity" style="width:20%">Quantity <p-sortIcon field="quantity"></p-sortIcon></th>
            <th pSortableColumn="price" style="width:20%">Price <p-sortIcon field="price"></p-sortIcon></th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>{{product.code}}</td>
            <td>{{product.name}}</td>
            <td>{{product.category}}</td>
            <td>{{product.quantity}}</td>
            <td>{{product.price | currency: 'USD'}}</td>
        </tr>
    </ng-template>
</p-table>

Multiple columns can be sorted by defining sortMode as multiple. This mode requires metaKey (e.g. ) to be pressed when clicking a header.

Code Name Category Quantity Price
f230fh0g3Bamboo WatchAccessories24$65.00
nvklal433Black WatchAccessories61$72.00
zz21cz3c1Blue BandFitness2$79.00
244wgerg2Blue T-ShirtClothing25$29.00
h456wer53BraceletAccessories73$15.00

<p-table [value]="products1" [tableStyle]="{'min-width': '60rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th pSortableColumn="code" style="width:20%">Code <p-sortIcon field="code"></p-sortIcon></th>
            <th pSortableColumn="name" style="width:20%">Name <p-sortIcon field="name"></p-sortIcon></th>
            <th pSortableColumn="category" style="width:20%">Category <p-sortIcon field="category"></p-sortIcon></th>
            <th pSortableColumn="quantity" style="width:20%">Quantity <p-sortIcon field="quantity"></p-sortIcon></th>
            <th pSortableColumn="price" style="width:20%">Price <p-sortIcon field="price"></p-sortIcon></th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>{{product.code}}</td>
            <td>{{product.name}}</td>
            <td>{{product.category}}</td>
            <td>{{product.quantity}}</td>
            <td>{{product.price | currency: 'USD'}}</td>
        </tr>
    </ng-template>
</p-table>

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.

Code Name Category Quantity Price
f230fh0g3Bamboo WatchAccessories24$65.00
nvklal433Black WatchAccessories61$72.00
zz21cz3c1Blue BandFitness2$79.00
244wgerg2Blue T-ShirtClothing25$29.00
h456wer53BraceletAccessories73$15.00

<p-table [value]="products" (sortFunction)="customSort($event)" [customSort]="true" [tableStyle]="{'min-width': '60rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th pSortableColumn="code" style="width:20%">Code <p-sortIcon field="code"></p-sortIcon></th>
            <th pSortableColumn="name" style="width:20%">Name <p-sortIcon field="name"></p-sortIcon></th>
            <th pSortableColumn="category" style="width:20%">Category <p-sortIcon field="category"></p-sortIcon></th>
            <th pSortableColumn="quantity" style="width:20%">Quantity <p-sortIcon field="quantity"></p-sortIcon></th>
            <th pSortableColumn="price" style="width:20%">Price <p-sortIcon field="price"></p-sortIcon></th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>{{product.code}}</td>
            <td>{{product.name}}</td>
            <td>{{product.category}}</td>
            <td>{{product.quantity}}</td>
            <td>{{product.price | currency: 'USD'}}</td>
        </tr>
    </ng-template>
</p-table>

Filters are displayed in an overlay.

Name
Country
Agent
Date
Balance
Status
Activity
Verified
James Butt AlgeriaIoni BowcherIoni Bowcher 09/13/2015 $70,663.00 unqualified
Josephine Darakjy EgyptAmy ElsnerAmy Elsner 02/09/2019 $82,429.00 proposal
Art Venere PanamaAsiya JavayantAsiya Javayant 05/13/2017 $28,334.00 qualified
Lenna Paprocki SloveniaXuxue FengXuxue Feng 09/15/2020 $88,521.00 new
Donette Foller South AfricaAsiya JavayantAsiya Javayant 05/20/2016 $93,905.00 proposal
Simona Morasca EgyptIvan MagalhaesIvan Magalhaes 02/16/2018 $50,041.00 qualified
Mitsue Tollner ParaguayIvan MagalhaesIvan Magalhaes 02/19/2018 $58,706.00 renewal
Leota Dilliard SerbiaOnyama LimbaOnyama Limba 08/13/2019 $26,640.00 renewal
Sage Wieser EgyptIvan MagalhaesIvan Magalhaes 11/21/2018 $65,369.00 unqualified
Kris Marrier MexicoOnyama LimbaOnyama Limba 07/07/2015 $63,451.00 proposal
Showing 1 to 10 of 200 entries
10

<p-table
    #dt1
    [value]="customers"
    dataKey="id"
    [rows]="10"
    [showCurrentPageReport]="true"
    [rowsPerPageOptions]="[10, 25, 50]"
    [loading]="loading"
    [paginator]="true"
    currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries"
    [globalFilterFields]="['name', 'country.name', 'representative.name', 'status']"
>
    <ng-template pTemplate="caption">
        <div class="flex">
            <button pButton label="Clear" class="p-button-outlined" icon="pi pi-filter-slash" (click)="clear(dt1)"></button>
            <span class="p-input-icon-left ml-auto">
                <i class="pi pi-search"></i>
                <input pInputText type="text" (input)="dt1.filterGlobal($event.target.value, 'contains')" placeholder="Search keyword" />
            </span>
        </div>
    </ng-template>
    <ng-template pTemplate="header">
        <tr>
            <th style="min-width:15rem">
                <div class="flex align-items-center">
                    Name
                    <p-columnFilter type="text" field="name" display="menu"></p-columnFilter>
                </div>
            </th>
            <th style="min-width:15rem">
                <div class="flex align-items-center">
                    Country
                    <p-columnFilter type="text" field="country.name" display="menu"></p-columnFilter>
                </div>
            </th>
            <th style="min-width:15rem">
                <div class="flex align-items-center">
                    Agent
                    <p-columnFilter field="representative" matchMode="in" display="menu" [showMatchModes]="false" [showOperator]="false" [showAddButton]="false">
                        <ng-template pTemplate="header">
                            <div class="px-3 pt-3 pb-0">
                                <span class="font-bold">Agent Picker</span>
                            </div>
                        </ng-template>
                        <ng-template pTemplate="filter" let-value let-filter="filterCallback">
                            <p-multiSelect [ngModel]="value" [options]="representatives" placeholder="Any" (onChange)="filter($event.value)" optionLabel="name">
                                <ng-template let-option pTemplate="item">
                                    <div class="inline-block vertical-align-middle">
                                        <img [alt]="option.label" src="https://primefaces.org/cdn/primeng/images/demo/avatar/{{ option.image }}" width="24" class="vertical-align-middle" />
                                        <span class="ml-1 mt-1">{{ option.name }}</span>
                                    </div>
                                </ng-template>
                            </p-multiSelect>
                        </ng-template>
                    </p-columnFilter>
                </div>
            </th>
            <th style="min-width:10rem">
                <div class="flex align-items-center">
                    Date
                    <p-columnFilter type="date" field="date" display="menu"></p-columnFilter>
                </div>
            </th>
            <th style="min-width:10rem">
                <div class="flex align-items-center">
                    Balance
                    <p-columnFilter type="numeric" field="balance" display="menu" currency="USD"></p-columnFilter>
                </div>
            </th>
            <th style="min-width:10rem">
                <div class="flex align-items-center">
                    Status
                    <p-columnFilter field="status" matchMode="equals" display="menu">
                        <ng-template pTemplate="filter" let-value let-filter="filterCallback">
                            <p-dropdown [ngModel]="value" [options]="statuses" (onChange)="filter($event.value)" placeholder="Any">
                                <ng-template let-option pTemplate="item">
                                    <p-tag [value]="option.value" [severity]="getSeverity(option.label)"></p-tag>
                                </ng-template>
                            </p-dropdown>
                        </ng-template>
                    </p-columnFilter>
                </div>
            </th>
            <th style="min-width:10rem">
                <div class="flex align-items-center">
                    Activity
                    <p-columnFilter field="activity" matchMode="between" display="menu" [showMatchModes]="false" [showOperator]="false" [showAddButton]="false">
                        <ng-template pTemplate="filter" let-filter="filterCallback">
                            <p-slider [ngModel]="activityValues" [range]="true" (onSlideEnd)="filter($event.values)" styleClass="m-3"></p-slider>
                            <div class="flex align-items-center px-2">
                                <span>{{ activityValues[0] }}</span>
                                <span>{{ activityValues[1] }}</span>
                            </div>
                        </ng-template>
                    </p-columnFilter>
                </div>
            </th>
            <th style="width: 3rem">
                <div class="flex align-items-center">
                    Verified
                    <p-columnFilter type="boolean" field="verified" display="menu"></p-columnFilter>
                </div>
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-customer>
        <tr>
            <td>
                {{ customer.name }}
            </td>
            <td>
                <img src="https://primefaces.org/cdn/primeng/images/demo/flag/flag_placeholder.png" [class]="'flag flag-' + customer.country.code" style="width: 20px" />
                <span class="ml-1 vertical-align-middle">{{ customer.country.name }}</span>
            </td>
            <td>
                <img [alt]="customer.representative.name" src="https://primefaces.org/cdn/primeng/images/demo/avatar/{{ customer.representative.image }}" width="32" style="vertical-align: middle" />
                <span class="ml-1 vertical-align-middle">{{ customer.representative.name }}</span>
            </td>
            <td>
                {{ customer.date | date: 'MM/dd/yyyy' }}
            </td>
            <td>
                {{ customer.balance | currency: 'USD':'symbol' }}
            </td>
            <td>
                <p-tag [value]="customer.status" [severity]="getSeverity(customer.status)"></p-tag>
            </td>
            <td>
                <p-progressBar [value]="customer.activity" [showValue]="false"></p-progressBar>
            </td>
            <td class="text-center">
                <i class="pi" [ngClass]="{ 'text-green-500 pi-check-circle': customer.verified, 'text-red-500 pi-times-circle': !customer.verified }"></i>
            </td>
        </tr>
    </ng-template>
    <ng-template pTemplate="emptymessage">
        <tr>
            <td colspan="7">No customers found.</td>
        </tr>
    </ng-template>
</p-table>

Filters are displayed inline within a separate row.

NameCountryAgentStatusVerified
Any
Any
James Butt AlgeriaIoni BowcherIoni Bowcherunqualified
Josephine Darakjy EgyptAmy ElsnerAmy Elsnerproposal
Art Venere PanamaAsiya JavayantAsiya Javayantqualified
Lenna Paprocki SloveniaXuxue FengXuxue Fengnew
Donette Foller South AfricaAsiya JavayantAsiya Javayantproposal
Simona Morasca EgyptIvan MagalhaesIvan Magalhaesqualified
Mitsue Tollner ParaguayIvan MagalhaesIvan Magalhaesrenewal
Leota Dilliard SerbiaOnyama LimbaOnyama Limbarenewal
Sage Wieser EgyptIvan MagalhaesIvan Magalhaesunqualified
Kris Marrier MexicoOnyama LimbaOnyama Limbaproposal
Showing 1 to 10 of 200 entries
10

<p-table
    #dt2
    [value]="customers"
    dataKey="id"
    [rows]="10"
    [showCurrentPageReport]="true"
    [rowsPerPageOptions]="[10, 25, 50]"
    [loading]="loading"
    [paginator]="true"
    currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries"
    [globalFilterFields]="['name', 'country.name', 'representative.name', 'status']"
    [tableStyle]="{ 'min-width': '75rem' }"
    >
    <ng-template pTemplate="caption">
        <div class="flex">
            <span class="p-input-icon-left ml-auto">
                <i class="pi pi-search"></i>
                <input pInputText type="text" (input)="dt2.filterGlobal($event.target.value, 'contains')" placeholder="Search keyword" />
            </span>
        </div>
    </ng-template>
    <ng-template pTemplate="header">
        <tr>
            <th style="width:22%">Name</th>
            <th style="width:22%">Country</th>
            <th style="width:22%">Agent</th>
            <th style="width:22%">Status</th>
            <th style="width:12%">Verified</th>
        </tr>
        <tr>
            <th>
                <p-columnFilter type="text" field="name"></p-columnFilter>
            </th>
            <th>
                <p-columnFilter type="text" field="country.name"></p-columnFilter>
            </th>
            <th>
                <p-columnFilter field="representative" matchMode="in" [showMenu]="false">
                    <ng-template pTemplate="filter" let-value let-filter="filterCallback">
                        <p-multiSelect [ngModel]="value" [options]="representatives" placeholder="Any" (onChange)="filter($event.value)" optionLabel="name">
                            <ng-template let-option pTemplate="item">
                                <div class="inline-block vertical-align-middle">
                                    <img [alt]="option.label" src="https://primefaces.org/cdn/primeng/images/demo/avatar/{{ option.image }}" width="24" class="vertical-align-middle" />
                                    <span class="ml-1 mt-1">{{ option.name }}</span>
                                </div>
                            </ng-template>
                        </p-multiSelect>
                    </ng-template>
                </p-columnFilter>
            </th>
            <th>
                <p-columnFilter field="status" matchMode="equals" [showMenu]="false">
                    <ng-template pTemplate="filter" let-value let-filter="filterCallback">
                        <p-dropdown [ngModel]="value" [options]="statuses" (onChange)="filter($event.value)" placeholder="Any" [showClear]="true">
                            <ng-template let-option pTemplate="item">
                                <p-tag [value]="option.value" [severity]="getSeverity(option.label)"></p-tag>
                            </ng-template>
                        </p-dropdown>
                    </ng-template>
                </p-columnFilter>
            </th>
            <th>
                <p-columnFilter type="boolean" field="verified"></p-columnFilter>
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-customer>
        <tr>
            <td>
                {{ customer.name }}
            </td>
            <td>
                <img src="https://primefaces.org/cdn/primeng/images/demo/flag/flag_placeholder.png" [class]="'flag flag-' + customer.country.code" style="width: 20px" />
                <span class="ml-1 vertical-align-middle">{{ customer.country.name }}</span>
            </td>
            <td>
                <img [alt]="customer.representative.name" src="https://primefaces.org/cdn/primeng/images/demo/avatar/{{ customer.representative.image }}" width="32" style="vertical-align: middle" />
                <span class="ml-1 vertical-align-middle">{{ customer.representative.name }}</span>
            </td>
            <td>
                <p-tag [value]="customer.status" [severity]="getSeverity(customer.status)"></p-tag>
            </td>
            <td>
                <i class="pi" [ngClass]="{ 'text-green-500 pi-check-circle': customer.verified, 'text-red-500 pi-times-circle': !customer.verified }"></i>
            </td>
        </tr>
    </ng-template>
    <ng-template pTemplate="emptymessage">
        <tr>
            <td colspan="5">No customers found.</td>
        </tr>
    </ng-template>
</p-table>

Single row selection is enabled by defining selectionMode as single along with a value binding using selection property. When available, it is suggested to provide a unique identifier of a row with dataKey to optimize performance.

By default, metaKey press (e.g. ) is necessary to unselect a row however this can be configured with disabling the metaKeySelection property. In touch enabled devices this option has no effect and behavior is same as setting it to false.

CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories24
nvklal433Black WatchAccessories61
zz21cz3c1Blue BandFitness2
244wgerg2Blue T-ShirtClothing25
h456wer53BraceletAccessories73

<p-table [value]="products" selectionMode="single" [(selection)]="selectedProduct" dataKey="code" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th>Code</th>
            <th>Name</th>
            <th>Category</th>
            <th>Quantity</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr [pSelectableRow]="product">
            <td>{{product.code}}</td>
            <td>{{product.name}}</td>
            <td>{{product.category}}</td>
            <td>{{product.quantity}}</td>
        </tr>
    </ng-template>
</p-table>

In multiple mode, selection binding should be an array. For touch enabled devices, selection is managed by tapping and for other devices metakey or shiftkey are required. Setting metaKeySelection property as false enables multiple selection without meta key.

MetaKey
Multiple Selection with MetaKey
CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories24
nvklal433Black WatchAccessories61
zz21cz3c1Blue BandFitness2
244wgerg2Blue T-ShirtClothing25
h456wer53BraceletAccessories73

<div class="flex justify-content-center align-items-center gap-2 mb-3">
    <p-inputSwitch inputId="metakey" [(ngModel)]="metaKeySelection" label="MetaKey"></p-inputSwitch>
    <span>MetaKey</span>
</div>
<p-table [value]="products" selectionMode="multiple" [(selection)]="selectedProducts" [metaKeySelection]="metaKeySelection" dataKey="code" [tableStyle]="{ 'min-width': '50rem' }">
    <ng-template pTemplate="caption"> Multiple Selection with MetaKey </ng-template>
    <ng-template pTemplate="header">
        <tr>
            <th>Code</th>
            <th>Name</th>
            <th>Category</th>
            <th>Quantity</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product let-rowIndex="rowIndex">
        <tr [pSelectableRow]="product" [pSelectableRowIndex]="rowIndex">
            <td>{{ product.code }}</td>
            <td>{{ product.name }}</td>
            <td>{{ product.category }}</td>
            <td>{{ product.quantity }}</td>
        </tr>
    </ng-template>
</p-table>

Multiple selection can also be handled using checkboxes by enabling the selectionMode property of column as multiple.

CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories24
nvklal433Black WatchAccessories61
zz21cz3c1Blue BandFitness2
244wgerg2Blue T-ShirtClothing25
h456wer53BraceletAccessories73

<p-table [value]="products" [(selection)]="selectedProducts" dataKey="code" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th style="width: 4rem">
                <p-tableHeaderCheckbox></p-tableHeaderCheckbox>
            </th>
            <th>Code</th>
            <th>Name</th>
            <th>Category</th>
            <th>Quantity</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>
                <p-tableCheckbox [value]="product"></p-tableCheckbox>
            </td>
            <td>{{product.code}}</td>
            <td>{{product.name}}</td>
            <td>{{product.category}}</td>
            <td>{{product.quantity}}</td>
        </tr>
    </ng-template>
</p-table>

Single selection can also be handled using radio buttons.

CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories24
nvklal433Black WatchAccessories61
zz21cz3c1Blue BandFitness2
244wgerg2Blue T-ShirtClothing25
h456wer53BraceletAccessories73

<p-table [value]="products" [(selection)]="selectedProduct" dataKey="code" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th style="width: 4rem"></th>
            <th>Code</th>
            <th>Name</th>
            <th>Category</th>
            <th>Quantity</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>
                <p-tableRadioButton [value]="product"></p-tableRadioButton>
            </td>
            <td>{{product.code}}</td>
            <td>{{product.name}}</td>
            <td>{{product.category}}</td>
            <td>{{product.quantity}}</td>
        </tr>
    </ng-template>
</p-table>

Row selection can be controlled by utilizing rowSelectable and disabled properties.

CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories24
nvklal433Black WatchAccessories61
zz21cz3c1Blue BandFitness2
244wgerg2Blue T-ShirtClothing25
h456wer53BraceletAccessories73

<p-table [value]="products" [(selection)]="selectedProducts" dataKey="code" [rowSelectable]="isRowSelectable" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th style="width: 4rem">
                <p-tableHeaderCheckbox></p-tableHeaderCheckbox>
            </th>
            <th style="min-width:200px">Code</th>
            <th style="min-width:200px">Name</th>
            <th style="min-width:200px">Category</th>
            <th style="min-width:200px">Quantity</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>
                <p-tableCheckbox [value]="product" [disabled]="isOutOfStock(product)"></p-tableCheckbox>
            </td>
            <td>{{product.code}}</td>
            <td>{{product.name}}</td>
            <td>{{product.category}}</td>
            <td>{{product.quantity}}</td>
        </tr>
    </ng-template>
</p-table>

CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories24
nvklal433Black WatchAccessories61
zz21cz3c1Blue BandFitness2
244wgerg2Blue T-ShirtClothing25
h456wer53BraceletAccessories73

<p-table [value]="products" [(selection)]="selectedProducts" dataKey="code" [paginator]="true" [rows]="5" [selectionPageOnly]="true" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th style="width: 4rem">
                <p-tableHeaderCheckbox></p-tableHeaderCheckbox>
            </th>
            <th style="min-width:200px">Code</th>
            <th style="min-width:200px">Name</th>
            <th style="min-width:200px">Category</th>
            <th style="min-width:200px">Quantity</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>
                <p-tableCheckbox [value]="product"></p-tableCheckbox>
            </td>
            <td>{{product.code}}</td>
            <td>{{product.name}}</td>
            <td>{{product.category}}</td>
            <td>{{product.quantity}}</td>
        </tr>
    </ng-template>
</p-table>

Table provides onRowSelect and onRowUnselect events to listen selection events.

CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories24
nvklal433Black WatchAccessories61
zz21cz3c1Blue BandFitness2
244wgerg2Blue T-ShirtClothing25
h456wer53BraceletAccessories73

<p-toast></p-toast>
<p-table [value]="products" selectionMode="single" [(selection)]="selectedProduct" dataKey="code"
    (onRowSelect)="onRowSelect($event)" (onRowUnselect)="onRowUnselect($event)" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th>Code</th>
            <th>Name</th>
            <th>Category</th>
            <th>Quantity</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr [pSelectableRow]="product">
            <td>{{product.code}}</td>
            <td>{{product.name}}</td>
            <td>{{product.category}}</td>
            <td>{{product.quantity}}</td>
        </tr>
    </ng-template>
</p-table>

Selection using custom elements.

CodeNameCategoryQuantity
f230fh0g3Bamboo WatchAccessories24
nvklal433Black WatchAccessories61
zz21cz3c1Blue BandFitness2
244wgerg2Blue T-ShirtClothing25
h456wer53BraceletAccessories73

<p-toast></p-toast>
<p-table [value]="products" [tableStyle]="{ 'min-width': '50rem' }">
    <ng-template pTemplate="header">
        <tr>
            <th>Code</th>
            <th>Name</th>
            <th>Category</th>
            <th>Quantity</th>
            <th style="width: 5rem"></th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product>
        <tr>
            <td>{{ product.code }}</td>
            <td>{{ product.name }}</td>
            <td>{{ product.category }}</td>
            <td>{{ product.quantity }}</td>
            <td>
                <button type="button" pButton pRipple icon="pi pi-plus" (click)="selectProduct(product)"></button>
            </td>
        </tr>
    </ng-template>
</p-table>

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.

Name ImagePrice Category Reviews Status
Bamboo WatchBamboo Watch$65.00Accessories
INSTOCK
Black WatchBlack Watch$72.00Accessories
INSTOCK
Blue BandBlue Band$79.00Fitness
LOWSTOCK
Blue T-ShirtBlue T-Shirt$29.00Clothing
INSTOCK
BraceletBracelet$15.00Accessories
INSTOCK
Brown PurseBrown Purse$120.00Accessories
OUTOFSTOCK
Chakra BraceletChakra Bracelet$32.00Accessories
LOWSTOCK
Galaxy EarringsGalaxy Earrings$34.00Accessories
INSTOCK
Game ControllerGame Controller$99.00Electronics
LOWSTOCK
Gaming SetGaming Set$299.00Electronics
INSTOCK

<p-table [value]="products" dataKey="name" [tableStyle]="{ 'min-width': '60rem' }">
    <ng-template pTemplate="header">
        <tr>
            <th style="width: 5rem"></th>
            <th pSortableColumn="name">Name <p-sortIcon field="name"></p-sortIcon></th>
            <th>Image</th>
            <th pSortableColumn="price">Price <p-sortIcon field="price"></p-sortIcon></th>
            <th pSortableColumn="category">Category <p-sortIcon field="category"></p-sortIcon></th>
            <th pSortableColumn="rating">Reviews <p-sortIcon field="rating"></p-sortIcon></th>
            <th pSortableColumn="inventoryStatus">Status <p-sortIcon field="inventoryStatus"></p-sortIcon></th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product let-expanded="expanded">
        <tr>
            <td>
                <button type="button" pButton pRipple [pRowToggler]="product" class="p-button-text p-button-rounded p-button-plain" [icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></button>
            </td>
            <td>{{ product.name }}</td>
            <td><img [src]="'https://primefaces.org/cdn/primeng/images/demo/product/' + product.image" [alt]="product.name" width="50" class="shadow-4" /></td>
            <td>{{ product.price | currency: 'USD' }}</td>
            <td>{{ product.category }}</td>
            <td><p-rating [ngModel]="product.rating" [readonly]="true" [cancel]="false"></p-rating></td>
            <td>
                <p-tag [value]="product.inventoryStatus" [severity]="getSeverity(product.inventoryStatus)"></p-tag>
            </td>
        </tr>
    </ng-template>
    <ng-template pTemplate="rowexpansion" let-product>
        <tr>
            <td colspan="7">
                <div class="p-3">
                    <p-table [value]="product.orders" dataKey="id">
                        <ng-template pTemplate="header">
                            <tr>
                                <th pSortableColumn="id">Id <p-sortIcon field="price"></p-sortIcon></th>
                                <th pSortableColumn="customer">Customer <p-sortIcon field="customer"></p-sortIcon></th>
                                <th pSortableColumn="date">Date <p-sortIcon field="date"></p-sortIcon></th>
                                <th pSortableColumn="amount">Amount <p-sortIcon field="amount"></p-sortIcon></th>
                                <th pSortableColumn="status">Status <p-sortIcon field="status"></p-sortIcon></th>
                                <th style="width: 4rem"></th>
                            </tr>
                        </ng-template>
                        <ng-template pTemplate="body" let-order>
                            <tr>
                                <td>{{ order.id }}</td>
                                <td>{{ order.customer }}</td>
                                <td>{{ order.date }}</td>
                                <td>{{ order.amount | currency: 'USD' }}</td>
                                <td>
                                    <p-tag [value]="order.status" [severity]="getStatusSeverity(order.status)"></p-tag>
                                </td>
                                <td><p-button type="button" icon="pi pi-plus"></p-button></td>
                            </tr>
                        </ng-template>
                        <ng-template pTemplate="emptymessage">
                            <tr>
                                <td colspan="6">There are no order for this product yet.</td>
                            </tr>
                        </ng-template>
                    </p-table>
                </div>
            </td>
        </tr>
    </ng-template>
</p-table>

Row editing toggles the visibility of all the 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 the editingRowKeys property which is a map whose key is the dataKey of the record where the value is any arbitrary number greater than zero.

CodeNameInventory StatusPrice
f230fh0g3 Bamboo Watch INSTOCK $65.00
nvklal433 Black Watch INSTOCK $72.00
zz21cz3c1 Blue Band LOWSTOCK $79.00
244wgerg2 Blue T-Shirt INSTOCK $29.00
h456wer53 Bracelet INSTOCK $15.00

<p-toast></p-toast>
<p-table [value]="products" dataKey="id" editMode="row" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th style="width:20%">Code</th>
            <th style="width:20%">Name</th>
            <th style="width:20%">Inventory Status</th>
            <th style="width:20%">Price</th>
            <th style="width:20%"></th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product let-editing="editing" let-ri="rowIndex">
        <tr [pEditableRow]="product">
            <td>
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input pInputText type="text" [(ngModel)]="product.code">
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{product.code}}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td>
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input pInputText type="text" [(ngModel)]="product.name" required>
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{product.name}}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td>
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <p-dropdown [options]="statuses" appendTo="body" [(ngModel)]="product.inventoryStatus" [style]="{'width':'100%'}"></p-dropdown>
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{product.inventoryStatus}}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td>
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input pInputText type="text" [(ngModel)]="product.price">
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{product.price | currency: 'USD'}}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td>
                <div class="flex align-items-center justify-content-center gap-2">
                    <button *ngIf="!editing" pButton pRipple type="button" pInitEditableRow icon="pi pi-pencil" (click)="onRowEditInit(product)" class="p-button-rounded p-button-text"></button>
                    <button *ngIf="editing" pButton pRipple type="button" pSaveEditableRow icon="pi pi-check" (click)="onRowEditSave(product)" class="p-button-rounded p-button-text p-button-success mr-2"></button>
                    <button *ngIf="editing" pButton pRipple type="button" pCancelEditableRow icon="pi pi-times" (click)="onRowEditCancel(product, ri)" class="p-button-rounded p-button-text p-button-danger"></button>
                </div>
            </td>
        </tr>
    </ng-template>
</p-table>

In-cell 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.

CodeNameInventory StatusPrice
f230fh0g3 Bamboo Watch INSTOCK $65.00
nvklal433 Black Watch INSTOCK $72.00
zz21cz3c1 Blue Band LOWSTOCK $79.00
244wgerg2 Blue T-Shirt INSTOCK $29.00
h456wer53 Bracelet INSTOCK $15.00

<p-table [value]="products" dataKey="id" [tableStyle]="{ 'min-width': '50rem' }">
    <ng-template pTemplate="header">
        <tr>
            <th style="width:25%">Code</th>
            <th style="width:25%">Name</th>
            <th style="width:25%">Inventory Status</th>
            <th style="width:25%">Price</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-product let-editing="editing">
        <tr>
            <td [pEditableColumn]="product.code" pEditableColumnField="code">
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input pInputText type="text" [(ngModel)]="product.code" />
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{ product.code }}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td [pEditableColumn]="product.name" pEditableColumnField="name">
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input pInputText type="text" [(ngModel)]="product.name" required />
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{ product.name }}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td [pEditableColumn]="product.inventoryStatus" pEditableColumnField="inventoryStatus">
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input pInputText [(ngModel)]="product.inventoryStatus" />
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{ product.inventoryStatus }}
                    </ng-template>
                </p-cellEditor>
            </td>
            <td [pEditableColumn]="product.price" pEditableColumnField="price">
                <p-cellEditor>
                    <ng-template pTemplate="input">
                        <input pInputText type="text" [(ngModel)]="product.price" />
                    </ng-template>
                    <ng-template pTemplate="output">
                        {{ product.price | currency: 'USD' }}
                    </ng-template>
                </p-cellEditor>
            </td>
        </tr>
    </ng-template>
</p-table>

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. Sample here loads the data from remote datasource efficiently using lazy loading. Also, the implementation of checkbox selection in lazy tables is left entirely to the user. Since the table component does not know what will happen to the data on the next page or whether there are instant data changes, the selection array can be implemented in several ways. One of them is as in the example below.

Name Country Company Representative
Any
James ButtAlgeriaBenton, John B JrIoni Bowcher
Josephine DarakjyEgyptChanay, Jeffrey A EsqAmy Elsner
Art VenerePanamaChemel, James L CpaAsiya Javayant
Lenna PaprockiSloveniaFeltz Printing ServiceXuxue Feng
Donette FollerSouth AfricaPrinting DimensionsAsiya Javayant
Simona MorascaEgyptChapman, Ross E EsqIvan Magalhaes
Mitsue TollnerParaguayMorlong AssociatesIvan Magalhaes
Leota DilliardSerbiaCommercial PressOnyama Limba
Sage WieserEgyptTruhlar And Truhlar AttysIvan Magalhaes
Kris MarrierMexicoKing, Christopher A EsqOnyama Limba

<p-table
    [value]="customers"
    [lazy]="true"
    (onLazyLoad)="loadCustomers($event)"
    dataKey="id"
    [tableStyle]="{ 'min-width': '75rem' }"
    [selection]="selectedCustomers"
    (selectionChange)="onSelectionChange($event)"
    [selectAll]="selectAll"
    (selectAllChange)="onSelectAllChange($event)"
    [paginator]="true"
    [rows]="10"
    [totalRecords]="totalRecords"
    [loading]="loading"
    [globalFilterFields]="['name', 'country.name', 'company', 'representative.name']"
>
    <ng-template pTemplate="header">
        <tr>
            <th style="width: 4rem"></th>
            <th pSortableColumn="name">Name <p-sortIcon field="name"></p-sortIcon></th>
            <th pSortableColumn="country.name">Country <p-sortIcon field="country.name"></p-sortIcon></th>
            <th pSortableColumn="company">Company <p-sortIcon field="company"></p-sortIcon></th>
            <th pSortableColumn="representative.name">Representative <p-sortIcon field="representative.name"></p-sortIcon></th>
        </tr>
        <tr>
            <th style="width: 4rem">
                <p-tableHeaderCheckbox></p-tableHeaderCheckbox>
            </th>
            <th>
                <p-columnFilter type="text" field="name"></p-columnFilter>
            </th>
            <th>
                <p-columnFilter type="text" field="country.name"></p-columnFilter>
            </th>
            <th>
                <p-columnFilter type="text" field="company"></p-columnFilter>
            </th>
            <th>
                <p-columnFilter field="representative" matchMode="in" [showMenu]="false">
                    <ng-template pTemplate="filter" let-value let-filter="filterCallback">
                        <p-multiSelect [ngModel]="value" appendTo="body" [options]="representatives" placeholder="Any" (onChange)="filter($event.value)" optionLabel="name" [maxSelectedLabels]="1" [selectedItemsLabel]="'{0} items'">
                            <ng-template let-option pTemplate="item">
                                <div class="inline-block vertical-align-middle">
                                    <img [alt]="option.label" src="https://primefaces.org/cdn/primeng/images/demo/avatar/{{ option.image }}" width="24" class="vertical-align-middle" />
                                    <span class="ml-1 mt-1">{{ option.name }}</span>
                                </div>
                            </ng-template>
                        </p-multiSelect>
                    </ng-template>
                </p-columnFilter>
            </th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-customer>
        <tr>
            <td>
                <p-tableCheckbox [value]="customer"></p-tableCheckbox>
            </td>
            <td>{{ customer.name }}</td>
            <td>{{ customer.country.name }}</td>
            <td>{{ customer.company }}</td>
            <td>{{ customer.representative.name }}</td>
        </tr>
    </ng-template>
</p-table>

Adding scrollable property along with a scrollHeight for the data viewport enables vertical scrolling with fixed headers.

NameCountryCompanyRepresentative
James ButtAlgeriaBenton, John B JrIoni Bowcher
Josephine DarakjyEgyptChanay, Jeffrey A EsqAmy Elsner
Art VenerePanamaChemel, James L CpaAsiya Javayant
Lenna PaprockiSloveniaFeltz Printing ServiceXuxue Feng
Donette FollerSouth AfricaPrinting DimensionsAsiya Javayant
Simona MorascaEgyptChapman, Ross E EsqIvan Magalhaes
Mitsue TollnerParaguayMorlong AssociatesIvan Magalhaes
Leota DilliardSerbiaCommercial PressOnyama Limba
Sage WieserEgyptTruhlar And Truhlar AttysIvan Magalhaes
Kris MarrierMexicoKing, Christopher A EsqOnyama Limba
Minna AmigonRomaniaDorl, James J EsqAnna Fali
Abel MacleadSingaporeRangoni Of FlorenceBernardo Dominic
Kiley CaldareraSerbiaFeiner BrosOnyama Limba
Graciela RutaChileBuckley Miller & WrightAmy Elsner
Cammy AlbaresPhilippinesRousseaux, Michael EsqAsiya Javayant
Mattie PoquetteVenezuelaCentury CommunicationsAnna Fali
Meaghan GarufiMalaysiaBolton, Wilbur EsqIvan Magalhaes
Gladys RimNetherlandsT M Byxbee Company PcStephen Shaw
Yuki WhobreyIsraelFarmers Insurance GroupBernardo Dominic
Fletcher FlosiArgentinaPost Box Services PlusXuxue Feng
Bette NickaParaguaySport En ArtOnyama Limba
Veronika InouyeEcuadorC 4 Network IncIoni Bowcher
Willard KolmetzTunisiaIngalls, Donald R EsqAsiya Javayant
Maryann RoysterBelarusFranklin, Peter L EsqElwin Sharvill
Alisha SlusarskiIcelandWtlz Power 107 FmStephen Shaw
Allene IturbideItalyLedecky, David EsqIvan Magalhaes
Chanel CaudyArgentinaProfessional Image IncIoni Bowcher
Ezekiel ChuiIrelandSider, Donald C EsqAmy Elsner
Willow KuskoRomaniaU Pull ItOnyama Limba
Bernardo FigeroaIsraelClark, Richard CpaIoni Bowcher
Ammie CorrioHungaryMoskowitz, Barry SAsiya Javayant
Francine VocelkaHondurasCascade Realty Advisors IncIoni Bowcher
Ernie StensethAustraliaKnwz NewsradioXuxue Feng
Albina GlickUkraineGiampetro, Anthony DBernardo Dominic
Alishia SergiQatarMilford Enterprises IncIvan Magalhaes
Solange ShinkoCameroonMosocco, Ronald AOnyama Limba
Jose StockhamItalyTri State Refueler CoAmy Elsner
Rozella OstroskyVenezuelaParkway CompanyAmy Elsner
Valentine GillianParaguayFbs Business FinanceBernardo Dominic
Kati RulapaughPuerto RicoEder Assocs Consltng Engrs PcIoni Bowcher
Youlanda SchemmerBoliviaTri M Tool IncXuxue Feng
Dyan OldroydArgentinaInternational Eyelets IncAmy Elsner
Roxane CampainFranceRapid Trading IntlAnna Fali
Lavera PerinVietnamAbc Enterprises IncStephen Shaw
Erick FerenczBelgiumCindy Turner AssociatesAmy Elsner
Fatima SaylorsCanadaStanton, James D EsqOnyama Limba
Jina BriddickMexicoGrace Pastries IncXuxue Feng
Kanisha WaycottEcuadorSchroer, Gene E EsqXuxue Feng
Emerson BowleyFinlandKnights InnStephen Shaw
Blair MaletFinlandBollinger Mach Shp & ShipyardAsiya Javayant

<p-table [value]="customers" [scrollable]="true" scrollHeight="400px" [tableStyle]="{'min-width': '50rem'}">
    <ng-template pTemplate="header">
        <tr>
            <th>Name</th>
            <th>Country</th>
            <th>Company</th>
            <th>Representative</th>
        </tr>
    </ng-template>
    <ng-template pTemplate="body" let-customer>
        <tr>
            <td>{{customer.name}}</td>
            <td>{{customer.country.name}}</td>
            <td>{{customer.company}}</td>
            <td>{{customer.representative.name}}</td>
        </tr>
    </ng-template>
</p-table>

Horizontal and vertical scroll can be used together to enable double axis scrolling.

IdNameCountryDateBalanceCompanyStatusActivityRepresentative
1000James ButtAlgeria2015-09-13$70,663.00Benton, John B Jrunqualified17Ioni Bowcher
1001Josephine DarakjyEgypt2019-02-09$82,429.00Chanay, Jeffrey A Esqproposal0Amy Elsner
1002Art VenerePanama2017-05-13$28,334.00Chemel, James L Cpaqualified63Asiya Javayant
1003Lenna PaprockiSlovenia2020-09-15$88,521.00Feltz Printing Servicenew37Xuxue Feng
1004Donette FollerSouth Africa2016-05-20$93,905.00Printing Dimensionsproposal33Asiya Javayant
1005Simona MorascaEgypt2018-02-16$50,041.00Chapman, Ross E Esqqualified68Ivan Magalhaes