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?
- Open the app in Chrome with Angular DevTools installed
- Navigate to any library in the book browser
- Start a profiler recording in the Angular DevTools panel
- Scroll through ~2 pages of book cards
- Navigate to the Series browser and scroll briefly
- Navigate to the Dashboard
- 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 use
takeUntilDestroyed / 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
What Went Wrong?
With
ChangeDetectionStrategy.Defaultapplied to all but 3 of 152 components, Angular runs change detection across the entire component tree on every event includingrequestAnimationFramecallbacks, image load events, and XHR responses.Profiling on the current
developbranch shows:requestAnimationFramealone (1,165ms)BookCardComponentchecked 4,480 times per session (250ms) despite being the most-rendered component in the appSeriesCardComponentchecked 2,334 times (100ms) a pure display component that only renders@Input()dataThemeConfiguratorComponent(Signal-based) checked 395 times (45ms) once per cycle, every cycle, despite never needing re-evaluationTooltipaccumulating 237ms in lifecycle hook costs (ngDoCheck,ngAfterViewChecked) as a side effect of the high cycle rateHow Can We Reproduce This?
Observe: 300–400 change detection cycles, with
requestAnimationFrameas 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. WithChangeDetectionStrategy.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.OnPushto presentational components that meet all of the following criteria:@Input()or Angular Signal inputstakeUntilDestroyed/takeUntil)BehaviorSubject/SubjectstateProfiler comparison (develop vs. patched):
Your Setup
Before Submitting