fix(frontend): annule le chargement en cours lors d'un changement rapide de site

Sans switchMap sur le flux externe, une réponse HTTP en retard pouvait écraser
l'affichage du site actuellement sélectionné après une navigation rapide entre
deux sites.
This commit is contained in:
Johan LEROY
2026-09-17 16:47:40 +02:00
parent 41acdb54b6
commit e26948932e
@@ -1,7 +1,7 @@
import { Component, computed, effect, inject, signal } from '@angular/core'; import { Component, DestroyRef, computed, inject, signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute, RouterLink } from '@angular/router'; import { ActivatedRoute, RouterLink } from '@angular/router';
import { catchError, EMPTY, map, Observable, switchMap } from 'rxjs'; import { catchError, EMPTY, filter, map, Observable, switchMap } from 'rxjs';
import { SitesService } from '../../../core/services/sites.service'; import { SitesService } from '../../../core/services/sites.service';
import { ReadingsService } from '../../../core/services/readings.service'; import { ReadingsService } from '../../../core/services/readings.service';
import { Site } from '../../../shared/models/site.model'; import { Site } from '../../../shared/models/site.model';
@@ -83,6 +83,7 @@ export class SiteDetail {
private route = inject(ActivatedRoute); private route = inject(ActivatedRoute);
private sitesService = inject(SitesService); private sitesService = inject(SitesService);
private readingsService = inject(ReadingsService); private readingsService = inject(ReadingsService);
private destroyRef = inject(DestroyRef);
siteId = toSignal(this.route.paramMap.pipe(map((params) => params.get('siteId') ?? ''))); siteId = toSignal(this.route.paramMap.pipe(map((params) => params.get('siteId') ?? '')));
@@ -105,11 +106,19 @@ export class SiteDetail {
}); });
constructor() { constructor() {
effect(() => { toObservable(this.siteId)
const siteId = this.siteId(); .pipe(
if (siteId) { filter((siteId): siteId is string => !!siteId),
this.load(siteId); // Piège : switchMap sur le flux externe annule le chargement en cours dès qu'un
} // nouveau siteId arrive, sinon une réponse en retard peut écraser le site affiché.
switchMap((siteId) => this.load(siteId)),
takeUntilDestroyed(this.destroyRef),
)
.subscribe((result) => {
this.error.set(null);
this.site.set(result.site);
this.latestReading.set(result.latest);
this.history.set(result.history);
}); });
} }
@@ -117,10 +126,8 @@ export class SiteDetail {
return status ? (TON_PAR_STATUT[status] ?? 'neutral') : 'neutral'; return status ? (TON_PAR_STATUT[status] ?? 'neutral') : 'neutral';
} }
private load(siteId: string): void { private load(siteId: string) {
this.sitesService return this.sitesService.getSite(siteId).pipe(
.getSite(siteId)
.pipe(
switchMap((site) => switchMap((site) =>
this.readingsService.getLatest(siteId).pipe(map((latest) => ({ site, latest }))), this.readingsService.getLatest(siteId).pipe(map((latest) => ({ site, latest }))),
), ),
@@ -137,13 +144,7 @@ export class SiteDetail {
.pipe(map((history) => ({ site, latest, history }))); .pipe(map((history) => ({ site, latest, history })));
}), }),
catchError(() => this.reportUnavailable()), catchError(() => this.reportUnavailable()),
) );
.subscribe((result) => {
this.error.set(null);
this.site.set(result.site);
this.latestReading.set(result.latest);
this.history.set(result.history);
});
} }
private reasonFor(field: MetricKey, reading: Reading | null): string { private reasonFor(field: MetricKey, reading: Reading | null): string {