Render metrics
Opt-in row render diagnostics
Render metrics are optional diagnostics from ng-advanced-table/render-metrics. They measure row render timing, add a synthetic metrics column, and expose a compact panel and filter. Enable them when you are tuning a heavy table or building an internal performance view.
Install
Install the table package and Angular companion peers:
pnpm add ng-advanced-table @angular/aria @angular/cdk
# or: npm install ng-advanced-table @angular/aria @angular/cdk
# or: yarn add ng-advanced-table @angular/aria @angular/cdk
Keep @angular/core and @angular/common in your Angular app dependencies.
Basic Wiring
Create one NatTableRenderMetricsStore, enable row render events on the table, and record each event.
import { Component, viewChild } from '@angular/core';
import { NatTable } from 'ng-advanced-table';
import { NatTableSurface } from 'ng-advanced-table/components';
import { NatRenderMetricsPanel, NatTableRenderMetricsStore, type NatTableRenderMetricsEvent } from 'ng-advanced-table/render-metrics';
@Component({
selector: 'app-positions-table',
imports: [NatTable, NatTableSurface, NatRenderMetricsPanel],
template: `
<nat-table-surface>
<nat-render-metrics-panel [controller]="metricsTable()" [store]="metricsStore" />
<nat-table
#metricsTable
[data]="rows()"
[columns]="columns"
[emitRowRenderEvents]="true"
accessibleName="Position render metrics"
(rowRendered)="onRowRendered($event)" />
</nat-table-surface>
`
})
export class PositionsTable {
readonly metricsStore = new NatTableRenderMetricsStore();
readonly metricsTable = viewChild<NatTable<PositionRow>>('metricsTable');
readonly columns = [];
protected onRowRendered(event: NatTableRenderMetricsEvent): void {
this.metricsStore.record(event);
}
}
emitRowRenderEvents is off by default because it adds per-row render instrumentation. Keep it disabled in ordinary tables.
Metrics Column
Use withRenderMetricsColumn(...) to append a synthetic render-time column. Wrap with header actions after adding synthetic columns.
import { withNatTableHeaderActions } from 'ng-advanced-table/components';
import { NatTableRenderMetricsStore, withRenderMetricsColumn } from 'ng-advanced-table/render-metrics';
readonly metricsStore = new NatTableRenderMetricsStore();
readonly columns = withNatTableHeaderActions(
withRenderMetricsColumn(baseColumns, this.metricsStore, {
header: 'Render',
pendingLabel: 'Pending',
unitSuffix: ' ms',
size: 112,
minSize: 88,
}),
);
The generated column:
- Uses id
__rowRenderMetricunlesscolumnIdis provided. - Aligns values to the end edge.
- Does not sort, hide, pin, or globally filter.
- Adds a column filter function driven by render tone.
- Shows a pending label until a row has been measured.
Panel And Filter
The panel summarizes the latest render cycle. The filter targets the synthetic metrics column.
<nat-table-surface>
<div class="render-metrics-controls">
<nat-render-metrics-filter [controller]="metricsTable()" [store]="metricsStore" />
<nat-render-metrics-panel [controller]="metricsTable()" [store]="metricsStore" />
</div>
<nat-table
#metricsTable
[data]="rows()"
[columns]="columns"
[emitRowRenderEvents]="true"
accessibleName="Position render metrics"
(rowRendered)="metricsStore.record($event)" />
</nat-table-surface>
NatRenderMetricsFilter patches columnFilters for the metrics column and resets pagination to the first page. Pass the NatTable instance, or any object matching NatTableRenderMetricsController, through [controller].
Keep NatRenderMetricsFilter outside <nat-table-toolbar> because it renders its own internal chip buttons as a labeled button group. Use <nat-table-toolbar> for controls that can register each interactive element with natToolbarItem or NatToolbarGroup.
Reading Measurements
The store exposes read-only row-level and cycle-level metrics. Row history is bounded to the newest 1000 row ids by default so long-lived tables with row churn do not retain unbounded metrics.
readonly metricsStore = new NatTableRenderMetricsStore({
maxRetainedRowMetrics: 2000,
});
readonly latestMeasurement = computed(() => this.metricsStore.measurement());
readonly retainedRowMetrics = computed(() => this.metricsStore.rowMetrics());
protected resetMetrics(): void {
this.metricsStore.reset();
}
protected metricFor(rowId: string): RowRenderMetric | undefined {
return this.metricsStore.rowMetric(rowId);
}
The latest measurement includes:
| Field | Meaning |
|---|---|
durationMs |
Total visible render duration for the latest cycle |
averageRowDurationMs |
Mean row duration |
rowCount |
Number of visible rows sampled |
rowsPerSecond |
Approximate rendered rows per second |
Row metrics include durationMs, measuredAt, and a derived tone: fast up to 12ms, watch above 12ms, or slow above 16.66ms.
Set maxRetainedRowMetrics to a higher finite value when a table needs a longer diagnostic window. Set it to Infinity only when the row-id space is known to be bounded.
Locale And Labels
Render-metrics copy can come from ng-advanced-table/locale.
import { provideNatTableRenderMetricsLocales } from 'ng-advanced-table/locale';
providers: [
provideNatTableRenderMetricsLocales({
en: {
renderMetrics: {
column: {
header: 'Render',
pendingLabel: 'Pending'
},
panel: {
ariaLabel: 'Row render timing'
},
filter: {
heading: 'Render speed',
groupAriaLabel: 'Filter rows by render speed'
}
}
}
})
];
withRenderMetricsColumn(...) creates static column definitions. If the locale or render-metrics provider copy can change at runtime, capture the live config in an injection context and pass it into a computed(...) column builder.
import { computed } from '@angular/core';
import { injectNatTableRenderMetricsIntl } from 'ng-advanced-table/locale';
import { withRenderMetricsColumn } from 'ng-advanced-table/render-metrics';
private readonly renderMetricsIntl = injectNatTableRenderMetricsIntl();
readonly columns = computed(() =>
withRenderMetricsColumn(baseColumns(), this.metricsStore, {
locale: this.localeId(),
intlConfig: this.renderMetricsIntl,
}),
);
injectNatTableRenderMetricsIntl() returns a stable live config. Reading it through the helper inside the computed tracks provider-signal changes, so the column's header, meta.label, pending label, formatter, and cell closure are rebuilt together. Without a computed rebuild, an existing column definition remains a snapshot.
Production Guidance
Use render metrics when they answer a specific performance question:
- Which rows are expensive to render?
- Did a custom cell renderer become slow?
- Did a live update pattern increase visible render time?
- Does a page-size change make row rendering unacceptable?
Remove or gate metrics UI when it is no longer useful to the user. Keep emitRowRenderEvents off for normal production tables unless product requirements need the measurements.