Typing Angular PrimeNg Table templates columns

Alain Boudard
3 min readFeb 1, 2023

How to enforce typing on additional properties of ng-templates from PrimeNg.

Here is the GitHub repo for this article :

In the previous article, we saw how to bring typing into a simple PrimeNg p-table component, working on the [values] input.

This input is related to the default ngTemplateOutlet $implicit passed context object. But as we can see in the PrimeNg documentation, the different components expose other context data, which can then be consumed inside our ng-template code.

Different types of templates for PrimeNg p-table

For now, we only have the $implicit attribute in our interface :

interface TableRowTemplateContext<TItem extends object> {
$implicit: TItem;
}

So, we will add a parameter matching the columns in the p-table component :

interface TableRowTemplateContext<TItem extends object,
TColumn extends object> {
$implicit: TItem;
columns: TColumn[];
}

Then we need to add an input to the directive itself that we previously created :

@Directive({
selector: 'ng-template[appTableRow]'
})
export class TableRowDirective<TItem extends object, TColumn extends object> {

@Input('appTableRow') items!: TItem[];
@Input() rowColumns!: TColumn[];

static ngTemplateContextGuard<TContextItem extends object,
TContextColumn extends object>(
dir: TableRowDirective<TContextItem, TContextColumn>,
ctx: unknown
): ctx is TableRowTemplateContext<TContextItem, TContextColumn> {
return true;
}

}

In the component template we add the columns specifics : an input on the p-table that represents a list of columns and the directive input on the ng-template :

<p-table [columns]="columns" 
[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
[appTableRow]="products"
[rowColumns]="columns"
pTemplate="body"
let-columns="columns"
let-product>

In the component, we declare the columns variable :

export interface ProductColumn {
field?: string;
header?: string;
}

columns: ProductColumn[];

this.columns = [
{ field: 'code', header: 'Code' },
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
];

Now we can loop over the columns to display the data of each row :

<ng-template [appTableRow]="products" [rowColumns]="columns" pTemplate="body" let-columns="columns" let-product>
<tr>
<td *ngFor="let col of columns">
{{product[col.field!]}}
</td>
</tr>
</ng-template>

The problem is we have a typechecking on the Product that doesn’t want to match with a simple string as declared in the ProductColumn interface. The type of the column is OK, the “field” property is recognized, but its type is not enough :

So we have to declare the ProductColumn field as a key of Product :

export interface ProductColumn {
field?: keyof Product;
header?: string;
}

This way, we have type checking on the Product row data and on the fields exposed via the columns ! How great is that ?

Typing the wrong column field in template
Typing the wrong field definition

There are many templates in PrimeNg components, not all may deserve so much attention, depending on the work we give into them. Obviously the header could benefit some typing too as it’s consuming the same columns input.

--

--