Skip to content

Some common components are re-rendered frequently which could lead to performance issues #157

@ZYancey

Description

@ZYancey

What Went Wrong?

With ChangeDetectionStrategy.Default applied to all but 3 of 152 components, Angular runs change detection across the entire component tree on every event including requestAnimationFrame callbacks, image load events, and XHR responses.

Profiling on the current develop branch shows:

  • 395 change detection cycles in a single navigation session
  • 252 of those cycles triggered by requestAnimationFrame alone (1,165ms)
  • Average tree size of 950 directive nodes walked per cycle (~375k total visits)
  • BookCardComponent checked 4,480 times per session (250ms) despite being the most-rendered component in the app
  • SeriesCardComponent checked 2,334 times (100ms) a pure display component that only renders @Input() data
  • ThemeConfiguratorComponent (Signal-based) checked 395 times (45ms) once per cycle, every cycle, despite never needing re-evaluation
  • PrimeNG Tooltip accumulating 237ms in lifecycle hook costs (ngDoCheck, ngAfterViewChecked) as a side effect of the high cycle rate

How Can We Reproduce This?

  1. Open the app in Chrome with Angular DevTools installed
  2. Navigate to any library in the book browser
  3. Start a profiler recording in the Angular DevTools panel
  4. Scroll through ~2 pages of book cards
  5. Navigate to the Series browser and scroll briefly
  6. Navigate to the Dashboard
  7. Stop the recording

Observe: 300–400 change detection cycles, with requestAnimationFrame as the dominant trigger. BookCardComponent, SeriesCardComponent, and layout components will show thousands of checks despite receiving no new data.

What Should Have Happened?

Presentational components that receive data exclusively via @Input() book cards, series cards, tag chips, icon renderers, dashboard scrollers should only be checked when their inputs change. With ChangeDetectionStrategy.OnPush, Angular skips the subtree entirely when no new input reference has arrived, reducing unnecessary work proportionally to how often those components appear in the tree.

Components already migrated to Angular Signals (e.g. ThemeConfiguratorComponent, TagComponent) are particularly wasteful under Default CD since signals already provide fine-grained reactivity, OnPush is the natural complement.

Screenshots or Error Messages (Optional)

No response

Any Ideas on How to Fix This? (Optional)

I have a PR drafted already, mentioned this offhand in the discord at one point.

Add ChangeDetectionStrategy.OnPush to presentational components that meet all of the following criteria:

  • Receive data only via @Input() or Angular Signal inputs
  • Have no unmanaged RxJS subscriptions (or all subscriptions usetakeUntilDestroyed / takeUntil)
  • Have no internal BehaviorSubject / Subject state
  • Mutate local state only in response to template events (which always trigger CD in OnPush) or Angular lifecycle hooks called during the initial render

Profiler comparison (develop vs. patched):

  • CD cycles: 395 → 313 (-21%)
  • Total CD time: 2,397ms → 1,750ms (-27%)
  • rAF-triggered cycles: 252 → 166 (-34%)
  • BookCardComponent CD cost: 250ms → 27ms (-89%)
  • SeriesCardComponent CD cost: 100ms → 0ms (-100%)

Your Setup

Before Submitting

  • I've searched existing issues and confirmed this bug hasn't been reported yet

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions