admin-request-activity.service.ts 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. import { Injectable, signal } from '@angular/core';
  2. const SHOW_DELAY_MS = 180;
  3. const MIN_VISIBLE_MS = 360;
  4. /** Coordinates one unobtrusive loading indicator for all admin HttpClient requests. */
  5. @Injectable({ providedIn: 'root' })
  6. export class AdminRequestActivityService {
  7. private readonly pendingState = signal(0);
  8. private readonly visibleState = signal(false);
  9. private showTimer: ReturnType<typeof setTimeout> | undefined;
  10. private hideTimer: ReturnType<typeof setTimeout> | undefined;
  11. private shownAt = 0;
  12. readonly pending = this.pendingState.asReadonly();
  13. readonly visible = this.visibleState.asReadonly();
  14. begin(): () => void {
  15. if (this.hideTimer) {
  16. clearTimeout(this.hideTimer);
  17. this.hideTimer = undefined;
  18. }
  19. this.pendingState.update(count => count + 1);
  20. if (!this.visibleState() && !this.showTimer) {
  21. this.showTimer = setTimeout(() => {
  22. this.showTimer = undefined;
  23. if (this.pendingState() > 0) {
  24. this.shownAt = Date.now();
  25. this.visibleState.set(true);
  26. }
  27. }, SHOW_DELAY_MS);
  28. }
  29. let completed = false;
  30. return () => {
  31. if (completed) return;
  32. completed = true;
  33. this.pendingState.update(count => Math.max(0, count - 1));
  34. if (this.pendingState() > 0) return;
  35. if (this.showTimer) {
  36. clearTimeout(this.showTimer);
  37. this.showTimer = undefined;
  38. }
  39. if (!this.visibleState()) return;
  40. const delay = Math.max(0, MIN_VISIBLE_MS - (Date.now() - this.shownAt));
  41. this.hideTimer = setTimeout(() => {
  42. this.hideTimer = undefined;
  43. if (this.pendingState() === 0) this.visibleState.set(false);
  44. }, delay);
  45. };
  46. }
  47. }