diff --git a/libs/portal/xtream/feature/src/lib/portal-channels-list/portal-channels-list.component.html b/libs/portal/xtream/feature/src/lib/portal-channels-list/portal-channels-list.component.html
index 17cf69250..57166dd1e 100644
--- a/libs/portal/xtream/feature/src/lib/portal-channels-list/portal-channels-list.component.html
+++ b/libs/portal/xtream/feature/src/lib/portal-channels-list/portal-channels-list.component.html
@@ -1,5 +1,23 @@
- @if (filteredChannels().length > 0) {
+ @if (isSelectedTypeContentLoading()) {
+
+
+
+ {{ 'CHANNELS.LOADING' | translate }}
+
+
+
+
+ @for (row of loadingRows; track row) {
+
+
+
+
+
+ }
+
+
+ } @else if (filteredChannels().length > 0) {
{
+ let fixture: ComponentFixture;
+ const selectedChannels = signal([]);
+ const selectedItem = signal(null);
+ const epgItems = signal([]);
+ const selectedTypeContentLoading = signal(true);
+ const selectedContentType = signal('live');
+ const currentPlaylist = signal(null);
+ const selectedCategoryId = signal(1);
+ const storeSignals = {
+ selectItemsFromSelectedCategory: selectedChannels,
+ selectedItem,
+ epgItems,
+ selectedTypeContentLoading,
+ selectedContentType,
+ currentPlaylist,
+ selectedCategoryId,
+ setSelectedCategory: jest.fn(),
+ };
+ const epgResults$ = new Subject<{ streamId: number; items: unknown[] }>();
+
+ beforeEach(async () => {
+ storeSignals.setSelectedCategory.mockClear();
+ selectedChannels.set([]);
+ selectedItem.set(null);
+ epgItems.set([]);
+ selectedTypeContentLoading.set(true);
+ selectedContentType.set('live');
+ currentPlaylist.set(null);
+ selectedCategoryId.set(1);
+
+ await TestBed.configureTestingModule({
+ imports: [PortalChannelsListComponent, NoopAnimationsModule],
+ providers: [
+ {
+ provide: TranslateService,
+ useValue: {
+ instant: (key: string) =>
+ key === 'CHANNELS.LOADING'
+ ? 'Loading channels...'
+ : key,
+ get: (key: string) =>
+ of(
+ key === 'CHANNELS.LOADING'
+ ? 'Loading channels...'
+ : key
+ ),
+ stream: (key: string) =>
+ of(
+ key === 'CHANNELS.LOADING'
+ ? 'Loading channels...'
+ : key
+ ),
+ onLangChange: of(null),
+ onTranslationChange: of(null),
+ onDefaultLangChange: of(null),
+ currentLang: 'en',
+ defaultLang: 'en',
+ },
+ },
+ {
+ provide: XtreamStore,
+ useValue: storeSignals,
+ },
+ {
+ provide: FavoritesService,
+ useValue: {
+ getFavorites: jest.fn().mockReturnValue(of([])),
+ },
+ },
+ {
+ provide: EpgQueueService,
+ useValue: {
+ epgResult$: epgResults$,
+ getCached: jest.fn().mockReturnValue(null),
+ enqueue: jest.fn(),
+ },
+ },
+ {
+ provide: ActivatedRoute,
+ useValue: {
+ snapshot: {
+ params: {},
+ },
+ },
+ },
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(PortalChannelsListComponent);
+ });
+
+ it('renders a loading placeholder instead of the empty state while xtream live content is still loading', () => {
+ fixture.detectChanges();
+
+ expect(
+ fixture.nativeElement.querySelector('.channels-loading-state')
+ ).not.toBeNull();
+ expect(
+ fixture.nativeElement.querySelector('.empty-search-state')
+ ).toBeNull();
+ });
+
+ it('renders the empty state once loading has finished and the selected category has no channels', () => {
+ selectedTypeContentLoading.set(false);
+
+ fixture.detectChanges();
+
+ expect(
+ fixture.nativeElement.querySelector('.channels-loading-state')
+ ).toBeNull();
+ expect(
+ fixture.nativeElement.querySelector('.empty-search-state')
+ ).not.toBeNull();
+ });
+});
diff --git a/libs/portal/xtream/feature/src/lib/portal-channels-list/portal-channels-list.component.ts b/libs/portal/xtream/feature/src/lib/portal-channels-list/portal-channels-list.component.ts
index 570d367c5..e3386a1af 100644
--- a/libs/portal/xtream/feature/src/lib/portal-channels-list/portal-channels-list.component.ts
+++ b/libs/portal/xtream/feature/src/lib/portal-channels-list/portal-channels-list.component.ts
@@ -69,6 +69,9 @@ export class PortalChannelsListComponent implements AfterViewInit, OnDestroy {
private readonly favoritesService = inject(FavoritesService);
private readonly epgQueueService = inject(EpgQueueService);
private readonly route = inject(ActivatedRoute);
+ readonly isSelectedTypeContentLoading =
+ this.xtreamStore.selectedTypeContentLoading;
+ readonly loadingRows = Array.from({ length: 9 }, (_, index) => index);
readonly channels = computed(() => {
const override = this.channelsOverride();
if (Array.isArray(override)) {
@@ -261,11 +264,13 @@ export class PortalChannelsListComponent implements AfterViewInit, OnDestroy {
toggleFavorite(event: Event, item: XtreamChannelListItem) {
event.stopPropagation();
+ const playlistId = this.xtreamStore.currentPlaylist()?.id;
+ if (!playlistId) {
+ return;
+ }
+
this.xtreamStore
- .toggleFavorite(
- item.xtream_id,
- this.xtreamStore.currentPlaylist().id
- )
+ .toggleFavorite(item.xtream_id, playlistId)
.then((result: boolean) => {
if (result) {
this.favorites.set(item.xtream_id, true);