typescript 66 lines · 4 tabs

Global Loading Spinner via In-Flight Request Tracking in an Angular HttpInterceptor

Shared by codesnips Aug 2026
4 tabs
import { Injectable, computed, signal } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class LoadingService {
  private readonly activeRequests = signal(0);

  readonly loading = computed(() => this.activeRequests() > 0);

  show(): void {
    this.activeRequests.update((count) => count + 1);
  }

  hide(): void {
    this.activeRequests.update((count) => Math.max(0, count - 1));
  }
}
4 files · typescript Explain with highlit

This snippet wires up a global loading indicator that reflects whether any HTTP request is currently in flight, without every component having to manage its own isLoading flag. The pattern centralizes request counting in a service and lets an HttpInterceptor increment on request start and decrement on completion, so the UI reacts automatically.

In loading.service.ts, the count of active requests is held in a private signal, and a public computed loading signal exposes whether that count is greater than zero. Using a counter rather than a boolean is deliberate: multiple concurrent requests may overlap, so a naive boolean would be flipped off by the first response even while others are still pending. show() bumps the counter and hide() decrements it, guarded with Math.max(0, ...) so an unexpected double-completion can never drive the count negative and leave the spinner stuck or falsely hidden.

In loading.interceptor.ts, a functional interceptor (HttpInterceptorFn) calls loading.show() before delegating to next(req), then uses RxJS finalize to call loading.hide() exactly once when the stream terminates — whether it completes, errors, or is unsubscribed. finalize is the key operator here because it fires on every terminal path, which is what makes the counter reliably balanced. The interceptor also inspects a custom header via req.headers.has('X-Skip-Loading') so that background polling or silent requests can opt out of the global spinner, and it strips that header before forwarding the request so it never leaks to the server.

In app.config.ts, the interceptor is registered with provideHttpClient(withInterceptors([loadingInterceptor])), the modern standalone way to compose interceptors.

Finally, app.component.ts consumes loadingService.loading directly in the template with the @if control-flow block, rendering an overlay only while requests are active. Because everything flows through signals, change detection stays cheap and no manual subscription management is needed. The main trade-off is that this couples all HTTP traffic to one spinner; the opt-out header keeps that manageable, and per-feature indicators can still be layered on top when finer control is required.


Related snips

python
class Product(models.Model):
    name = models.CharField(max_length=200)
    slug = models.SlugField(blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    cost = models.DecimalField(max_digits=10, decimal_places=2)
    margin = models.DecimalField(max_digits=5, decimal_places=2, blank=True)

Django model signals vs overriding save

django python models
by Priya Sharma 2 tabs
kotlin
package com.example.myapp

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp

Dependency injection with Hilt

kotlin android hilt
by Alex Chen 3 tabs
swift
import Combine
import Foundation

class SearchViewModel: ObservableObject {
    @Published var searchQuery = ""
    @Published var results: [SearchResult] = []

Combine operators for data transformation

swift combine reactive
by Sofia Martinez 1 tab
javascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["form"]
  static values = { delay: { type: Number, default: 250 } }

Debounced live search with Stimulus + Turbo Streams

rails hotwire stimulus
by codesnips 4 tabs
php
<?php

namespace App\Providers;

use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;

Laravel service container and dependency injection

laravel dependency-injection service-container
by Carlos Mendez 2 tabs
typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface FilterState {
  search: string
  category: string | null

Zustand for lightweight state management

react zustand state-management
by Maya Patel 2 tabs

Share this code

Here's the card — post it anywhere.

Global Loading Spinner via In-Flight Request Tracking in an Angular HttpInterceptor — share card
Link copied