Skip to content

Architecture

The library consists of four native Angular building blocks: an HttpContextToken, a functional HttpInterceptorFn, a signal service, and a directive. Coordination uses a shared requestId to group requests and UI elements without external state managers or custom RxJS logic

  • REQUEST_LOCK_ID: an HttpContextToken<string | null> that tags a request with a lock identifier. Multiple requests can share the same ID to form a single flow.
  • createRequestLockContext(id): constructs the HttpContext attached to the request.
  • requestLockInterceptor: functional interceptor that reads the ID from the context and updates the service.
  • RequestLockService: singleton service holding a reference-counted map of pending requests, exposed via the boolean signal isPending(id). Any component can consume this signal to render custom in-flight states.
  • RequestLockDirective ([ngxRequestLock]): attaches to any element containing or matching a <button>, observes isPending(requestId), and updates the disabled attribute. All instances sharing a requestId lock and unlock together.
  • provideRequestLock(): registers provideHttpClient(withInterceptors([requestLockInterceptor])) in a single call.

Data flow
  1. A user action initiates the flow. Directives bound to the flow's requestId set isBlocked = true and disable the button.
  2. The application sends HTTP requests whose HttpContext includes the requestId via createRequestLockContext(id). Follow-up refreshes or chained calls can reuse the same ID.
  3. For each request, requestLockInterceptor reads REQUEST_LOCK_ID, calls RequestLockService.start(id), and appends finalize(() => end(id)). The service reference-counts pending requests per ID.
  4. isPending(id) remains true until every request with that ID completes. Directives and wrappers unblock only when all calls settle.
  5. Two safety timeouts unblock the directive: at 500 ms if no requests are tracked, and at 10 s as an unconditional ceiling.

RequestLockService increments a counter per ID on start and decrements it on end. Multiple concurrent or sequential requests (such as a mutation and its follow-up fetch) can share the lock ID; isPending(id) returns false only when the final request settles

ngx-request-lock

UI locking bound to HTTP request lifecycle for Angular applications

© 2026 Salvatore Di Genua - Built with Angular and Tailwind CSS