From 8cfb8ab2517021c399dcedd636b8313a5c7822b4 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 4 Oct 2026 10:08:01 +0200 Subject: [PATCH] perf(ui): make the libs/ui Eager components OnPush Plan item C6 step 3 for libs/ui/components and libs/ui/epg: eleven components (twelve with the EPG trust dialog) set ChangeDetectionStrategy.Eager and were checked on every tick, among them the always-mounted EPG progress panel the idle audit found re-rendering on every idle tick. Their template state is already signals, signal inputs, immutable dialog data or fields written from template events, so they switch to OnPush without other changes. The epg-item-description spec mutated dialog data after creation and marked only the fixture's host view; it now marks the component's own view, which OnPush requires. The libs/ui playback and remote-control components stay Eager for their own PRs. Co-Authored-By: Claude Opus 5.5 --- docs/architecture/zoneless-migration.md | 22 +++++++++---------- .../confirm-dialog.component.ts | 3 +-- .../content-hero/content-hero.component.ts | 3 +-- .../expandable-text.component.ts | 3 +-- .../content-about.component.ts | 3 +-- .../portal-detail-shell.component.ts | 3 +-- .../progress-capsule.component.ts | 3 +-- .../episode-info-dialog.component.ts | 3 +-- .../watched-badge/watched-badge.component.ts | 3 +-- .../epg-item-description.component.spec.ts | 10 ++++++--- .../epg-item-description.component.ts | 2 +- .../epg-progress-panel.component.ts | 4 ++-- .../epg-source-status.component.ts | 2 +- 13 files changed, 30 insertions(+), 34 deletions(-) diff --git a/docs/architecture/zoneless-migration.md b/docs/architecture/zoneless-migration.md index d3c7d8015..630d51ff2 100644 --- a/docs/architecture/zoneless-migration.md +++ b/docs/architecture/zoneless-migration.md @@ -85,17 +85,17 @@ files that still contain `ChangeDetectionStrategy.Eager`. ### libs/ui (20 files, 21 components) -- [ ] `libs/ui/components/src/lib/confirm-dialog/confirm-dialog.component.ts` -- [ ] `libs/ui/components/src/lib/content-hero/content-hero.component.ts` -- [ ] `libs/ui/components/src/lib/expandable-text/expandable-text.component.ts` -- [ ] `libs/ui/components/src/lib/portal-detail-shell/content-about.component.ts` -- [ ] `libs/ui/components/src/lib/portal-detail-shell/portal-detail-shell.component.ts` -- [ ] `libs/ui/components/src/lib/progress-capsule/progress-capsule.component.ts` -- [ ] `libs/ui/components/src/lib/season-container/episode-info-dialog.component.ts` -- [ ] `libs/ui/components/src/lib/watched-badge/watched-badge.component.ts` -- [ ] `libs/ui/epg/src/lib/epg-item-description/epg-item-description.component.ts` -- [ ] `libs/ui/epg/src/lib/epg-progress-panel/epg-progress-panel.component.ts` (idle audit root; also `EpgTrustConfirmDialogComponent`) -- [ ] `libs/ui/epg/src/lib/epg-source-status/epg-source-status.component.ts` +- [x] `libs/ui/components/src/lib/confirm-dialog/confirm-dialog.component.ts` +- [x] `libs/ui/components/src/lib/content-hero/content-hero.component.ts` +- [x] `libs/ui/components/src/lib/expandable-text/expandable-text.component.ts` +- [x] `libs/ui/components/src/lib/portal-detail-shell/content-about.component.ts` +- [x] `libs/ui/components/src/lib/portal-detail-shell/portal-detail-shell.component.ts` +- [x] `libs/ui/components/src/lib/progress-capsule/progress-capsule.component.ts` +- [x] `libs/ui/components/src/lib/season-container/episode-info-dialog.component.ts` +- [x] `libs/ui/components/src/lib/watched-badge/watched-badge.component.ts` +- [x] `libs/ui/epg/src/lib/epg-item-description/epg-item-description.component.ts` +- [x] `libs/ui/epg/src/lib/epg-progress-panel/epg-progress-panel.component.ts` (idle audit root; also `EpgTrustConfirmDialogComponent`) +- [x] `libs/ui/epg/src/lib/epg-source-status/epg-source-status.component.ts` - [ ] `libs/ui/remote-control/src/lib/remote-control/remote-control.component.ts` (`apps/remote-control-web` only) - [ ] `libs/ui/playback/src/lib/art-player/art-player.component.ts` - [ ] `libs/ui/playback/src/lib/audio-player/audio-player.component.ts` diff --git a/libs/ui/components/src/lib/confirm-dialog/confirm-dialog.component.ts b/libs/ui/components/src/lib/confirm-dialog/confirm-dialog.component.ts index 937c06e54..375f073d2 100644 --- a/libs/ui/components/src/lib/confirm-dialog/confirm-dialog.component.ts +++ b/libs/ui/components/src/lib/confirm-dialog/confirm-dialog.component.ts @@ -29,8 +29,7 @@ export interface ConfirmDialogData { @Component({ imports: [MatButtonModule, MatDialogModule, TranslateModule], - // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade. - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, template: `

{{ dialogData.title }} diff --git a/libs/ui/components/src/lib/content-hero/content-hero.component.ts b/libs/ui/components/src/lib/content-hero/content-hero.component.ts index 5917e374e..6413a66b5 100644 --- a/libs/ui/components/src/lib/content-hero/content-hero.component.ts +++ b/libs/ui/components/src/lib/content-hero/content-hero.component.ts @@ -34,8 +34,7 @@ export type ContentHeroLayout = 'stage' | 'compact'; TranslateModule, ], templateUrl: './content-hero.component.html', - // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade. - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['./content-hero.component.scss'], }) export class ContentHeroComponent { diff --git a/libs/ui/components/src/lib/expandable-text/expandable-text.component.ts b/libs/ui/components/src/lib/expandable-text/expandable-text.component.ts index a35f18b95..1b50a3fcf 100644 --- a/libs/ui/components/src/lib/expandable-text/expandable-text.component.ts +++ b/libs/ui/components/src/lib/expandable-text/expandable-text.component.ts @@ -47,8 +47,7 @@ import { TranslateModule } from '@ngx-translate/core'; } `, - // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade. - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, styles: [ ` :host { diff --git a/libs/ui/components/src/lib/portal-detail-shell/content-about.component.ts b/libs/ui/components/src/lib/portal-detail-shell/content-about.component.ts index 1d55c39f6..71328b8bd 100644 --- a/libs/ui/components/src/lib/portal-detail-shell/content-about.component.ts +++ b/libs/ui/components/src/lib/portal-detail-shell/content-about.component.ts @@ -56,8 +56,7 @@ import { TranslateModule } from '@ngx-translate/core'; `, - // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade. - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['./content-about.component.scss'], }) export class ContentAboutComponent { diff --git a/libs/ui/components/src/lib/portal-detail-shell/portal-detail-shell.component.ts b/libs/ui/components/src/lib/portal-detail-shell/portal-detail-shell.component.ts index de069c8e7..a11e88986 100644 --- a/libs/ui/components/src/lib/portal-detail-shell/portal-detail-shell.component.ts +++ b/libs/ui/components/src/lib/portal-detail-shell/portal-detail-shell.component.ts @@ -44,8 +44,7 @@ import { imports: [ContentHeroComponent, ContentAboutComponent, NgTemplateOutlet], templateUrl: './portal-detail-shell.component.html', styleUrls: ['./portal-detail-shell.component.scss'], - // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade. - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, host: { tabindex: '0', role: 'region', diff --git a/libs/ui/components/src/lib/progress-capsule/progress-capsule.component.ts b/libs/ui/components/src/lib/progress-capsule/progress-capsule.component.ts index c1394d165..253703482 100644 --- a/libs/ui/components/src/lib/progress-capsule/progress-capsule.component.ts +++ b/libs/ui/components/src/lib/progress-capsule/progress-capsule.component.ts @@ -19,8 +19,7 @@ import { > `, - // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade. - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, styles: [ ` .progress-capsule { diff --git a/libs/ui/components/src/lib/season-container/episode-info-dialog.component.ts b/libs/ui/components/src/lib/season-container/episode-info-dialog.component.ts index 59c78ca4c..e08e4dbf4 100644 --- a/libs/ui/components/src/lib/season-container/episode-info-dialog.component.ts +++ b/libs/ui/components/src/lib/season-container/episode-info-dialog.component.ts @@ -105,8 +105,7 @@ export const EPISODE_INFO_PLAY = 'play' as const; `, - // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade. - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, styles: [ ` :host { diff --git a/libs/ui/components/src/lib/watched-badge/watched-badge.component.ts b/libs/ui/components/src/lib/watched-badge/watched-badge.component.ts index cba3fe79a..582e7dcb5 100644 --- a/libs/ui/components/src/lib/watched-badge/watched-badge.component.ts +++ b/libs/ui/components/src/lib/watched-badge/watched-badge.component.ts @@ -12,8 +12,7 @@ import { MatIcon } from '@angular/material/icon'; } `, - // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -- Preserve pre-Angular 22 eager checking during the framework upgrade. - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, styles: [ ` .watched-badge { diff --git a/libs/ui/epg/src/lib/epg-item-description/epg-item-description.component.spec.ts b/libs/ui/epg/src/lib/epg-item-description/epg-item-description.component.spec.ts index ce7dd3c79..bd8c5ec1f 100644 --- a/libs/ui/epg/src/lib/epg-item-description/epg-item-description.component.spec.ts +++ b/libs/ui/epg/src/lib/epg-item-description/epg-item-description.component.spec.ts @@ -1,4 +1,4 @@ -import { signal } from '@angular/core'; +import { ChangeDetectorRef, signal } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import { By } from '@angular/platform-browser'; @@ -72,8 +72,12 @@ describe('EpgItemDescriptionComponent', () => { }); it('offers archive URL copying only with explicit capability and explains credentials', () => { + // The component is OnPush and dialog data is fixed once the dialog + // opens, so the test marks the component's own view after mutating it. + const componentView = + fixture.debugElement.injector.get(ChangeDetectorRef); Object.assign(component.dialogData, { archiveUrlAvailable: true }); - fixture.changeDetectorRef.markForCheck(); + componentView.markForCheck(); fixture.detectChanges(); expect( fixture.nativeElement.querySelector( @@ -84,7 +88,7 @@ describe('EpgItemDescriptionComponent', () => { 'EPG.PROGRAM_DIALOG.ARCHIVE_URL_NOTICE' ); Object.assign(component.dialogData, { archiveUrlAvailable: false }); - fixture.changeDetectorRef.markForCheck(); + componentView.markForCheck(); fixture.detectChanges(); expect( fixture.nativeElement.querySelector( diff --git a/libs/ui/epg/src/lib/epg-item-description/epg-item-description.component.ts b/libs/ui/epg/src/lib/epg-item-description/epg-item-description.component.ts index de527bda8..98ad0ff77 100644 --- a/libs/ui/epg/src/lib/epg-item-description/epg-item-description.component.ts +++ b/libs/ui/epg/src/lib/epg-item-description/epg-item-description.component.ts @@ -38,7 +38,7 @@ export type EpgItemDialogData = EpgProgram & { selector: 'app-epg-item-description', templateUrl: './epg-item-description.component.html', styleUrls: ['./epg-item-description.component.scss'], - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, imports: [DatePipe, MatDialogModule, MatIcon, TranslatePipe], }) export class EpgItemDescriptionComponent { diff --git a/libs/ui/epg/src/lib/epg-progress-panel/epg-progress-panel.component.ts b/libs/ui/epg/src/lib/epg-progress-panel/epg-progress-panel.component.ts index d0e47b8e9..a1011039b 100644 --- a/libs/ui/epg/src/lib/epg-progress-panel/epg-progress-panel.component.ts +++ b/libs/ui/epg/src/lib/epg-progress-panel/epg-progress-panel.component.ts @@ -32,7 +32,7 @@ interface EpgTrustConfirmDialogData { @Component({ selector: 'app-epg-trust-confirm-dialog', imports: [MatButtonModule, MatDialogModule, TranslatePipe], - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, template: `

{{ data.title }}

@@ -64,7 +64,7 @@ class EpgTrustConfirmDialogComponent { TranslatePipe, ], templateUrl: './epg-progress-panel.component.html', - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, styleUrl: './epg-progress-panel.component.scss', }) export class EpgProgressPanelComponent { diff --git a/libs/ui/epg/src/lib/epg-source-status/epg-source-status.component.ts b/libs/ui/epg/src/lib/epg-source-status/epg-source-status.component.ts index e5fb3777d..12d5fc93d 100644 --- a/libs/ui/epg/src/lib/epg-source-status/epg-source-status.component.ts +++ b/libs/ui/epg/src/lib/epg-source-status/epg-source-status.component.ts @@ -30,7 +30,7 @@ type BadgeStatus = TranslatePipe, ], templateUrl: './epg-source-status.component.html', - changeDetection: ChangeDetectionStrategy.Eager, + changeDetection: ChangeDetectionStrategy.OnPush, styleUrl: './epg-source-status.component.scss', }) export class EpgSourceStatusComponent implements OnInit {