Skip to content

Directive Usage

Using RequestLockDirective for single interactive elements. Each directive instance generates a requestId, tags the HTTP request via createRequestLockContext(id), and unlocks automatically when the request settles. For multi-element or multi-request scenarios, see Flow Lock Examples

The directive enriches the native click event with the generated requestId and a ready-to-use HttpContext, so the handler needs no template reference or manual context creation

RequestLockMouseEvent

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

Live demo

  • Type the handler parameter as RequestLockMouseEvent.
  • Use event.context directly in the HttpClient request.
  • This works when the (click) binding is on the element carrying ngxRequestLock.

Minimal setup for a single interaction: element, directive, template reference requestLock, and an HTTP request using createRequestLockContext(id)

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

Live demo

  • #lock="requestLock" exposes the directive instance to the template.
  • lock.requestId() provides a stable UUID for the instance.
  • The element remains disabled until the HTTP request settles.

The pattern applies to any HTTP action by changing the verb and payload while keeping the directive, exported reference, and context helper identical

Save (POST)

typescript
          
            
              import { Component, inject } from '@angular/core';
            
              import { HttpClient } from '@angular/common/http';
            
              import { FormBuilder } from '@angular/forms';
            
              import {
            
                RequestLockDirective,
            
                createRequestLockContext,
            
              } from 'ngx-request-lock';
            
              
            
              @Component({
            
                selector: 'ngx-save-user',
            
                imports: [RequestLockDirective],
            
                template: `
            
                  <button ngxRequestLock #lock="requestLock" (click)="save(lock.requestId())">
            
                    Save
            
                  </button>
            
                `,
            
              })
            
              export class SaveUser {
            
                private readonly http = inject(HttpClient);
            
                protected readonly form = inject(FormBuilder).nonNullable.group({
            
                  name: [''],
            
                  email: [''],
            
                });
            
              
            
                protected save(id: string): void {
            
                  this.http
            
                    .post('/api/users', this.form.getRawValue(), {
            
                      context: createRequestLockContext(id),
            
                    })
            
                    .subscribe();
            
                }
            
              }
            
        

Live demo

Delete (DELETE)

typescript
          
            
              import { Component, inject, input } from '@angular/core';
            
              import { HttpClient } from '@angular/common/http';
            
              import {
            
                RequestLockDirective,
            
                createRequestLockContext,
            
              } from 'ngx-request-lock';
            
              
            
              @Component({
            
                selector: 'ngx-delete-user',
            
                imports: [RequestLockDirective],
            
                template: `
            
                  <button
            
                    ngxRequestLock
            
                    #lock="requestLock"
            
                    class="text-red-600"
            
                    (click)="remove(lock.requestId())"
            
                  >
            
                    Delete
            
                  </button>
            
                `,
            
              })
            
              export class DeleteUser {
            
                private readonly http = inject(HttpClient);
            
                protected readonly userId = input.required<string>();
            
              
            
                protected remove(id: string): void {
            
                  this.http
            
                    .delete(`/api/users/${this.userId()}`, {
            
                      context: createRequestLockContext(id),
            
                    })
            
                    .subscribe();
            
                }
            
              }
            
        

Live demo

Reactive form submit

typescript
          
            
              import { Component, inject } from '@angular/core';
            
              import { HttpClient } from '@angular/common/http';
            
              import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
            
              import {
            
                RequestLockDirective,
            
                createRequestLockContext,
            
              } from 'ngx-request-lock';
            
              
            
              @Component({
            
                selector: 'ngx-signup-form',
            
                imports: [ReactiveFormsModule, RequestLockDirective],
            
                template: `
            
                  <form [formGroup]="form">
            
                    <input formControlName="email" type="email" />
            
                    <input formControlName="password" type="password" />
            
              
            
                    <button
            
                      ngxRequestLock
            
                      #lock="requestLock"
            
                      [disabled]="form.invalid"
            
                      (click)="submit(lock.requestId())"
            
                    >
            
                      Sign up
            
                    </button>
            
                  </form>
            
                `,
            
              })
            
              export class SignupForm {
            
                private readonly http = inject(HttpClient);
            
                protected readonly form = inject(FormBuilder).nonNullable.group({
            
                  email: ['', [Validators.required, Validators.email]],
            
                  password: ['', [Validators.required, Validators.minLength(8)]],
            
                });
            
              
            
                protected submit(id: string): void {
            
                  if (this.form.invalid) return;
            
              
            
                  this.http
            
                    .post('/api/signup', this.form.getRawValue(), {
            
                      context: createRequestLockContext(id),
            
                    })
            
                    .subscribe();
            
                }
            
              }
            
        

Live demo

To update labels or display spinners during request execution (e.g., swapping Save to Saving...), use the reactive signal provided by the service. Read RequestLockService.isPending(lock.requestId()) inside a computed() signal to update template state

Live demo

Signal-driven pending state

typescript
          
            
              import { Component, computed, inject, signal, viewChild } from '@angular/core';
            
              import { HttpClient } from '@angular/common/http';
            
              import {
            
                RequestLockDirective,
            
                RequestLockService,
            
                createRequestLockContext,
            
              } from 'ngx-request-lock';
            
              
            
              @Component({
            
                selector: 'ngx-pending-save',
            
                imports: [RequestLockDirective],
            
                template: `
            
                  <button
            
                    ngxRequestLock
            
                    #lock="requestLock"
            
                    type="button"
            
                    [attr.aria-busy]="isPending() ? 'true' : null"
            
                    (click)="save(lock.requestId())"
            
                  >
            
                    @if (isPending()) {
            
                      <span class="spinner" aria-hidden="true"></span>
            
                      <span>Saving...</span>
            
                    } @else {
            
                      <span>Save</span>
            
                    }
            
                  </button>
            
                `,
            
              })
            
              export class PendingSave {
            
                private readonly http = inject(HttpClient);
            
                private readonly lockService = inject(RequestLockService);
            
                private readonly lock = viewChild.required(RequestLockDirective);
            
              
            
                // Reactive pending flag derived from the shared service.
            
                protected readonly isPending = computed(() =>
            
                  this.lockService.isPending(this.lock().requestId)(),
            
                );
            
              
            
                protected save(id: string): void {
            
                  this.http
            
                    .post('/api/users', {}, { context: createRequestLockContext(id) })
            
                    .subscribe();
            
                }
            
              }
            
        
  • viewChild.required(RequestLockDirective) accesses the directive instance from the component class.
  • computed(() => service.isPending(lock().requestId)()) converts service state into a boolean signal.
  • Use @if (isPending()) in the template to swap labels or icons without managing manual loading flags or finalize callbacks.
  • Set [attr.aria-busy] during active requests to inform assistive technologies.

RequestLockDirective only toggles the native disabled attribute on target buttons. To add custom spinners or animations, extend the directive and override setBlockStatus

LoadingRequestLockDirective

typescript
          
            
              import { Directive, Renderer2, inject } from '@angular/core';
            
              import { RequestLockDirective } from 'ngx-request-lock';
            
              
            
              /**
            
               * Docs-app-only example.
            
               * Extends RequestLockDirective and overrides setBlockStatus to render
            
               * a custom loading animation instead of toggling [disabled].
            
               */
            
              @Directive({
            
                selector: '[ngxLoadingRequestLock]',
            
                exportAs: 'loadingRequestLock',
            
              })
            
              export class LoadingRequestLockDirective extends RequestLockDirective {
            
                private readonly localRenderer = inject(Renderer2);
            
                private spinner: HTMLElement | null = null;
            
              
            
                protected override setBlockStatus(): void {
            
                  if (!this.button) return;
            
              
            
                  if (this.isBlocked) {
            
                    this.localRenderer.setAttribute(this.button, 'aria-disabled', 'true');
            
                    this.localRenderer.setAttribute(this.button, 'aria-busy', 'true');
            
                    this.localRenderer.addClass(this.button, 'ngx-lock-loading');
            
                    this.attachSpinner();
            
                    return;
            
                  }
            
              
            
                  this.localRenderer.removeAttribute(this.button, 'aria-disabled');
            
                  this.localRenderer.removeAttribute(this.button, 'aria-busy');
            
                  this.localRenderer.removeClass(this.button, 'ngx-lock-loading');
            
                  this.detachSpinner();
            
                }
            
              
            
                private attachSpinner(): void {
            
                  if (this.spinner || !this.button) return;
            
                  const el = this.localRenderer.createElement('span') as HTMLElement;
            
                  this.localRenderer.addClass(el, 'ngx-lock-spinner');
            
                  this.localRenderer.setAttribute(el, 'aria-hidden', 'true');
            
                  this.localRenderer.appendChild(this.button, el);
            
                  this.spinner = el;
            
                }
            
              
            
                private detachSpinner(): void {
            
                  if (!this.spinner || !this.button) return;
            
                  this.localRenderer.removeChild(this.button, this.spinner);
            
                  this.spinner = null;
            
                }
            
              }
            
        

Usage matches the base directive; only the selector and exported name change

Usage

typescript
          
            
              @Component({
            
                selector: 'ngx-fancy-save',
            
                imports: [LoadingRequestLockDirective],
            
                template: `
            
                  <button
            
                    ngxLoadingRequestLock
            
                    #lock="loadingRequestLock"
            
                    (click)="save(lock.requestId())"
            
                  >
            
                    <span>Save</span>
            
                  </button>
            
                `,
            
              })
            
              export class FancySave {
            
                private readonly http = inject(HttpClient);
            
              
            
                protected save(id: string): void {
            
                  this.http
            
                    .post('/api/users', {}, { context: createRequestLockContext(id) })
            
                    .subscribe();
            
                }
            
              }
            
        

Consumer styles

css
          
            
              /* consumer styles */
            
              .ngx-lock-loading {
            
                position: relative;
            
                cursor: not-allowed;
            
                opacity: 0.75;
            
                pointer-events: none;
            
              }
            
              
            
              .ngx-lock-spinner {
            
                display: inline-block;
            
                width: 0.9em;
            
                height: 0.9em;
            
                margin-left: 0.5em;
            
                border-radius: 9999px;
            
                border: 2px solid currentColor;
            
                border-top-color: transparent;
            
                animation: ngx-lock-spin 0.6s linear infinite;
            
              }
            
              
            
              @keyframes ngx-lock-spin {
            
                to { transform: rotate(360deg); }
            
              }
            
        

Live demo

Coordinating multiple elements and requests with one shared requestId