perf(ui): enable OnPush on portal channels list, fix EPG track keys

PortalChannelsListComponent renders a virtual-scrolled channel list (Xtream
live/VOD/series, can be 1000+ items). It was using default change detection,
so every CD cycle in the parent tree (EPG ticks, progress updates) re-checked
every binding. Switch to OnPush — the existing cdr.detectChanges() calls
already cover the async update paths, and signal-based state marks for check
automatically.

Replace track $index with track program.start in two EPG @for loops
(epg-list, multi-epg-container search results). $index causes Angular to
re-create DOM nodes whenever the list reorders (e.g. on EPG refresh or
timezone correction). program.start is the ISO datetime — unique per
program within a channel and stable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 3d6901072049
This commit is contained in:
4grayandClaude Opus 4.7 committed 2026-05-01 21:18:22 +02:00
1 parent 313230ab16
commit 0d79466c84
3 files changed
+12 -4

No files matched your search

@@ -4,6 +4,7 @@ import {
} from '@angular/cdk/scrolling';
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
@@ -55,6 +56,7 @@ interface XtreamCategoryLike {
selector: 'app-portal-channels-list',
templateUrl: './portal-channels-list.component.html',
styleUrls: ['./portal-channels-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ChannelListItemComponent,
MatIcon,
@@ -35,7 +35,7 @@
<!-- program list -->
<div id="program-list" class="app-scrollbar" #programList>
@if (items.length > 0) {
@for (program of items; track $index) {
@for (program of items; track program.start) {
@if (!canActivateProgram(program)) {
<div
class="program-item"
@@ -105,8 +105,14 @@
}
}
} @else {
<div class="epg-not-available">
{{ 'EPG.EPG_NOT_AVAILABLE_DATE' | translate }}
<div class="epg-empty-state">
<mat-icon class="empty-state-icon">event_busy</mat-icon>
<div class="empty-state-title">
{{ 'EPG.EPG_NOT_AVAILABLE_DATE' | translate }}
</div>
<div class="empty-state-hint">
{{ 'EPG.NO_GUIDE_HINT' | translate }}
</div>
</div>
}
</div>
@@ -83,7 +83,7 @@
</div>
@if (programSearchResults().length > 0) {
<div class="program-search-results">
@for (program of programSearchResults(); track $index) {
@for (program of programSearchResults(); track program.start) {
<button class="search-result-item" (click)="showProgramDetails(program)">
<div class="result-info">
<span class="result-title">{{ program.title }}</span>