ShowcaseDocs and examples
Docs

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 __rowRenderMetric unless columnId is 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.