Skip to content

Installation and Setup

Minimal setup for ngx-request-lock: package installation, provider registration, attaching a shared requestId to HTTP requests, and applying the directive to target UI elements

  • Angular v22 or newer, declaring peer dependencies @angular/common and @angular/core at ^22.0.0.
  • Standalone APIs, functional HTTP interceptors, and signals enabled (default in v22).
  • The default ID generator uses crypto.randomUUID(), requiring a secure browser context (HTTPS or localhost).

Install the package from npm. No runtime dependencies exist other than tslib

bash
          
            
              npm install ngx-request-lock
            
        

provideRequestLock() is an environment provider factory that registers the functional interceptor. Add it to the providers array in ApplicationConfig

app.config.ts

typescript
          
            
              import { ApplicationConfig } from '@angular/core';
            
              import { provideRouter } from '@angular/router';
            
              import { provideRequestLock } from 'ngx-request-lock';
            
              
            
              import { routes } from './app.routes';
            
              
            
              export const appConfig: ApplicationConfig = {
            
                providers: [
            
                  provideRouter(routes),
            
                  provideRequestLock(),
            
                ],
            
              };
            
        

If the application configures provideHttpClient with existing interceptors, omit provideRequestLock() and register requestLockInterceptor directly

app.config.ts

typescript
          
            
              import { ApplicationConfig } from '@angular/core';
            
              import { provideHttpClient, withInterceptors } from '@angular/common/http';
            
              import { requestLockInterceptor } from 'ngx-request-lock';
            
              
            
              import { authInterceptor } from './core/http/auth.interceptor';
            
              
            
              export const appConfig: ApplicationConfig = {
            
                providers: [
            
                  provideHttpClient(
            
                    withInterceptors([authInterceptor, requestLockInterceptor]),
            
                  ),
            
                ],
            
              };
            
        

The interceptor processes only requests carrying a value under REQUEST_LOCK_ID in their HttpContext. Use createRequestLockContext(id) to build this context

typescript
          
            
              import { HttpClient } from '@angular/common/http';
            
              import { inject } from '@angular/core';
            
              import { createRequestLockContext } from 'ngx-request-lock';
            
              
            
              const http = inject(HttpClient);
            
              
            
              http.post('/api/users', payload, {
            
                context: createRequestLockContext(id),
            
              }).subscribe();
            
        
  • REQUEST_LOCK_ID is an HttpContextToken<string | null> defaulting to null.
  • createRequestLockContext(id) returns an HttpContext with the specified token. If id is falsy, an empty context is returned and the interceptor skips the request.
  • Untracked HTTP requests pass through unmodified.

RequestLockDirective is a standalone directive with selector [ngxRequestLock] and exportAs: 'requestLock'. Import it into the component's imports array and apply it to target elements

typescript
          
            
              import { Component, inject } from '@angular/core';
            
              import { HttpClient } from '@angular/common/http';
            
              import {
            
                RequestLockDirective,
            
                createRequestLockContext,
            
              } from 'ngx-request-lock';
            
              
            
              @Component({
            
                selector: 'ngx-save-button',
            
                imports: [RequestLockDirective],
            
                template: `
            
                  <button ngxRequestLock #lock="requestLock" (click)="save(lock.requestId())">
            
                    Save
            
                  </button>
            
                `,
            
              })
            
              export class SaveButton {
            
                private readonly http = inject(HttpClient);
            
              
            
                protected save(id: string): void {
            
                  this.http
            
                    .post('/api/users', {}, { context: createRequestLockContext(id) })
            
                    .subscribe();
            
                }
            
              }
            
        
  • Each directive instance generates a unique requestId via crypto.randomUUID(). Bind [requestId] explicitly to share an ID across multiple elements and coordinate a flow.
  • Export the instance via #lock="requestLock" to read lock.requestId() for single-element locks. For shared flows, manage the ID in the component (e.g., signal(crypto.randomUUID())) and bind it to directives and createRequestLockContext(...) calls.
  • The directive locates the button via elementRef.closest('button') or the first child <button>, toggling its disabled attribute. Non-button host elements receive no DOM modifications, but their lock state still updates the shared signal for wrapper components.

UI unlocking occurs automatically as HTTP request lifecycles complete

  1. On click, the target directive sets isBlocked = true and disables its resolved button.
  2. requestLockInterceptor reads the ID from HttpContext and calls RequestLockService.start(id). Each response pipes through finalize(() => service.end(id)), decrementing the counter on success, error, or unsubscription.
  3. RequestLockService maintains a reference counter signal signal<Record<string, number>> and exposes isPending(id).
  4. Directives and components reading isPending(id) observe the signal inside an effect, re-enabling controls once the counter reaches zero.

Safety timeouts

Two timeouts prevent elements from staying disabled indefinitely:

  • 500 ms: unblocks the directive if no pending HTTP requests are detected (handling actions that fire no network calls).
  • 10 s: unconditional ceiling that unlocks elements if requests hang.

Complete provider configuration in application bootstrap and directive usage in a component

app.config.ts

typescript
          
            
              import { ApplicationConfig } from '@angular/core';
            
              import { provideRouter } from '@angular/router';
            
              import { provideRequestLock } from 'ngx-request-lock';
            
              
            
              import { routes } from './app.routes';
            
              
            
              export const appConfig: ApplicationConfig = {
            
                providers: [
            
                  provideRouter(routes),
            
                  provideRequestLock(),
            
                ],
            
              };
            
        

ping.component.ts

typescript
          
            
              import { Component, inject } from '@angular/core';
            
              import { HttpClient } from '@angular/common/http';
            
              import {
            
                RequestLockDirective,
            
                createRequestLockContext,
            
              } from 'ngx-request-lock';
            
              
            
              @Component({
            
                selector: 'ngx-ping',
            
                imports: [RequestLockDirective],
            
                template: `
            
                  <button
            
                    ngxRequestLock
            
                    #lock="requestLock"
            
                    type="button"
            
                    (click)="ping(lock.requestId())"
            
                  >
            
                    Ping
            
                  </button>
            
                `,
            
              })
            
              export class Ping {
            
                private readonly http = inject(HttpClient);
            
              
            
                protected ping(id: string): void {
            
                  this.http
            
                    .get('/api/ping', { context: createRequestLockContext(id) })
            
                    .subscribe();
            
                }
            
              }
            
        

ngx-request-lock

UI locking bound to HTTP request lifecycle for Angular applications

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