Skip to content

Fix/primeng render performance regression - #19597

Open
devidevio wants to merge 7 commits into
primefaces:masterfrom
devidevio:fix/primeng-render-performance-regression
Open

Fix/primeng render performance regression#19597
devidevio wants to merge 7 commits into
primefaces:masterfrom
devidevio:fix/primeng-render-performance-regression

Conversation

@devidevio

Copy link
Copy Markdown

This PR reduces PrimeNG table rendering overhead in component-heavy rows. Spent a lot of time on it, because it really annoyed me.

The issue is most visible in large, non-virtualized p-table instances where each row contains nested PrimeNG components like p-button, p-chip, and p-tag. In v21, these components all go through the newer BaseComponent, PassThrough, metadata, style, and theme-reactivity infrastructure. That infrastructure is useful, but in dense tables the per-instance cost multiplies quickly.

The changes keep default behavior backwards compatible and add explicit opt-out paths for performance-sensitive use cases.

New global config options:

providePrimeNG({
    ptMetadata: false,
    ptBinding: false
});

New component-level attributes:

<p-table
    [themeReactive]="false"
    [scopedTokens]="false"
/>

All options default to the current behavior (enabled / true).


The Root Cause

PrimeNG v21 does more work per component instance than v17:

  • BaseComponent prepares theme and scoped token handling
  • pBind applies PassThrough attributes/classes/styles/events
  • metadata attributes like data-pc-section are generated even when no custom PT is used
  • static classes/styles are recomputed repeatedly
  • many identical theme-change listeners can be registered across repeated components

For a table with many rows, this becomes a large amount of repeated setup and DOM work.


File-by-file Changes

packages/primeng/src/base/base.ts

Adds shared theme-change wiring and grouped theme listeners.

Before, many component instances could register equivalent theme-change listeners. Now shared style invalidation is wired once, and listeners can be grouped by style name.

Impact:

  • fewer duplicate theme listeners
  • less listener cleanup work
  • less memory pressure in large component trees

packages/primeng/src/basecomponent/basecomponent.ts

Main performance work.

Changes:

  • caches $style
  • caches $params
  • caches static cx() class values
  • caches static sx() style values
  • caches metadata-only ptm() / ptms() results
  • short-circuits ptm(), ptms(), and ptmo() when PT binding is disabled
  • skips metadata generation when configured
  • adds local inputs for theme/scoped-token opt-out:
    • themeReactive
    • scopedTokens
  • adds PERFORMANCE_CONTEXT so those options can flow through PrimeNG component subtrees

Impact:

  • fewer object allocations
  • fewer repeated class/style computations
  • less PT work in large tables
  • optional per-table opt-out for runtime theme/scoped-token handling

packages/primeng/src/bind/bind.ts

Makes pBind cheaper.

Changes include:

  • pBind becomes a no-op when ptBinding is disabled
  • repeated identical attribute/property writes are skipped
  • repeated identical setAttrs() calls avoid deep equality checks when object identity is unchanged
  • event listeners are cleaned up when binding is disabled

Impact:

  • less DOM attribute churn
  • cheaper PassThrough handling
  • significant reduction when many internal elements use [pBind]

packages/primeng/src/button/button.ts

Adds performance-context propagation through:

  • p-button
  • pButton
  • pButtonLabel
  • pButtonIcon

Also fixes a bug exposed by disabling PT metadata.

The legacy dynamic pButton path searched for generated icon/label elements using data-pc-section. When metadata was disabled, those lookups failed and repeated icon updates could create duplicate icons. This affected expandable table row toggles.

The fix stores direct references to dynamically-created icon/label elements and only uses the metadata selector as a fallback.

Impact:

  • button-related components inherit table-level performance settings
  • expandable row toggle icons work correctly with PT metadata disabled

packages/primeng/src/chip/chip.ts
packages/primeng/src/tag/tag.ts

Adds PERFORMANCE_CONTEXT provider.

Impact:

  • p-chip and p-tag can inherit table-level themeReactive / scopedTokens settings
  • useful because chips are commonly rendered repeatedly in table cells
  • useful for status-heavy table columns

packages/primeng/src/config/primeng.ts

Adds two global config signals:

  • ptMetadata
  • ptBinding

These are wired through providePrimeNG.

Impact:

  • apps can disable only automatic metadata
  • apps can fully disable PassThrough binding when they do not use PT

packages/primeng/src/config/primeng.types.ts

Adds the public config typings:

  • ptMetadata?: boolean
  • ptBinding?: boolean

Usage:

providePrimeNG({
    ptMetadata: false,
    ptBinding: false
});

Defaults are true.


packages/primeng/src/table/table.ts

Adds PERFORMANCE_CONTEXT provider to p-table.

This allows a table to act as the local performance boundary:

<p-table
    [themeReactive]="false"
    [scopedTokens]="false"
/>

Impact:

  • no need to disable these behaviors globally
  • large tables can opt out locally while the rest of the app keeps normal runtime theme behavior

New Options

ptMetadata

Disables automatic PrimeNG metadata attributes such as:

  • data-pc-name
  • data-pc-section
  • data-pc-extend
  • generated pc-* attrs

Use when the app does not rely on these attributes for tests, styling, or tooling.

ptBinding

Disables PassThrough binding entirely.

This disables:

  • global pt
  • local [pt]
  • PT classes/styles/attributes
  • PT event listeners
  • PT hooks

Use only when the app does not use PassThrough.

themeReactive

Component-level opt-out for runtime theme reactivity.

Use when a component/subtree does not need to react to theme changes after initial render.

scopedTokens

Component-level opt-out for scoped dt token handling.

Use when the component/subtree does not use scoped design tokens.


Compatibility Notes

Default behavior is unchanged.

The new performance options are opt-in.

Caveats:

  • ptMetadata: false can break tests or selectors relying on data-pc-*
  • ptBinding: false disables PassThrough completely
  • themeReactive: false means runtime theme changes are not applied reactively in that subtree
  • scopedTokens: false disables scoped dt handling in that subtree

Validation

Checked with:

node_modules/.bin/prettier --check ...
git diff --check ...
node_modules/.bin/tsc -p packages/primeng/tsconfig.json --noEmit
node_modules/.bin/tsc -p apps/showcase/tsconfig.json --noEmit

The local PrimeNG package build completed successfully.


Performance

Based on the Stackblitz demo: https://stackblitz.com/edit/ypa916vr-a8fnitry?file=package.json

  • Officla avg. rendering time for 10 runs: (420 + 388 + 382 + 368 + 386 + 386 + 376 + 377 + 377 + 389)/10 = ~ 384ms
  • Patched avg. rendering time for 10 runs: (209 + 218 + 207 + 212 + 197 + 180 + 200 + 206 + 221 + 207)/10 = ~ 205ms

So the performance of the patch is about 1.87x faster / 87% speed increase.

I'm providing also two demo cases:

You can also check the package out and try it in your own application:

Or via:

"dependencies": {
    "primeng": "https://github.com/devidevio/primeng/releases/download/21.1.8/primeng-21.1.8.tgz"
}

Clone the repo locally, run npm install & npm start - open the browser and play around (open dev tools / console to see the performance logs).

Additional note
I've only added the PERFORMANCE_CONTEXT to a few components, the most common used in an table. May this needs to be extended too.

As this includes an change on the base component level, it needs some more testing and more eyes on it without breaking anything else, as this is an fundamental change.


Official.mov
Patched.mov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant