feat(carousel): enhance image loading strategy and improve product display in carousel

This commit is contained in:
2026-07-23 14:44:52 -03:00
parent b04c33bbff
commit 67702ffdd7
8 changed files with 127 additions and 6 deletions

View File

@@ -2,10 +2,12 @@ import {
ChangeDetectionStrategy,
Component,
PLATFORM_ID,
WritableSignal,
computed,
effect,
inject,
input,
output,
signal,
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@@ -20,11 +22,15 @@ export class MainCarouselComponent {
private readonly platformId = inject(PLATFORM_ID);
private readonly requestedIndex = signal(0);
private readonly paused = signal(false);
private readonly loadedIndexes = signal<ReadonlySet<number>>(new Set());
private readonly settledIndexes = signal<ReadonlySet<number>>(new Set());
private firstImageReadyEmitted = false;
readonly images = input.required<readonly string[]>();
readonly imageAlts = input<readonly string[]>([]);
readonly autoSlideInterval = input(5000);
readonly ariaLabel = input('Imágenes destacadas');
readonly firstImageReady = output<void>();
protected readonly activeIndex = computed(() => {
const imageCount = this.images().length;
@@ -32,8 +38,27 @@ export class MainCarouselComponent {
return imageCount ? this.requestedIndex() % imageCount : 0;
});
protected readonly hasMultipleImages = computed(() => this.images().length > 1);
protected readonly sequentialLoadIndex = computed(() => {
const settledIndexes = this.settledIndexes();
for (let index = 0; index < this.images().length; index += 1) {
if (!settledIndexes.has(index)) {
return index;
}
}
return -1;
});
constructor() {
effect(() => {
this.images();
this.requestedIndex.set(0);
this.loadedIndexes.set(new Set());
this.settledIndexes.set(new Set());
this.firstImageReadyEmitted = false;
});
effect(() => {
const imageCount = this.images().length;
const interval = Math.max(this.autoSlideInterval(), 1000);
@@ -44,7 +69,11 @@ export class MainCarouselComponent {
const timer = window.setInterval(() => {
if (!this.paused()) {
this.requestedIndex.update((index) => (index + 1) % imageCount);
this.requestedIndex.update((index) => {
const nextIndex = (index + 1) % imageCount;
return this.loadedIndexes().has(nextIndex) ? nextIndex : index;
});
}
}, interval);
@@ -62,6 +91,27 @@ export class MainCarouselComponent {
this.paused.set(paused);
}
protected imageSource(image: string, index: number): string | null {
const shouldLoad =
this.loadedIndexes().has(index) ||
index === this.sequentialLoadIndex() ||
index === this.activeIndex();
return shouldLoad ? image : null;
}
protected handleImageLoad(index: number): void {
this.updateIndexSet(this.loadedIndexes, index, true);
this.updateIndexSet(this.settledIndexes, index, true);
this.notifyFirstImageReady(index);
}
protected handleImageError(index: number): void {
this.updateIndexSet(this.loadedIndexes, index, false);
this.updateIndexSet(this.settledIndexes, index, true);
this.notifyFirstImageReady(index);
}
protected handleKeydown(event: KeyboardEvent): void {
if (!this.hasMultipleImages()) {
return;
@@ -83,4 +133,26 @@ export class MainCarouselComponent {
protected imageAlt(index: number): string {
return this.imageAlts()[index] || `Imagen destacada ${index + 1}`;
}
private updateIndexSet(
target: WritableSignal<ReadonlySet<number>>,
index: number,
present: boolean,
): void {
target.update((current) => {
const updated = new Set(current);
present ? updated.add(index) : updated.delete(index);
return updated;
});
}
private notifyFirstImageReady(index: number): void {
if (index !== 0 || this.firstImageReadyEmitted) {
return;
}
this.firstImageReadyEmitted = true;
this.firstImageReady.emit();
}
}