Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/nigrosimone/ng-lock
Angular decorator for lock a function and user interface while a task running.
https://github.com/nigrosimone/ng-lock
angular angular2 decorator lock locked-functions
Last synced: about 1 month ago
JSON representation
Angular decorator for lock a function and user interface while a task running.
- Host: GitHub
- URL: https://github.com/nigrosimone/ng-lock
- Owner: nigrosimone
- License: mit
- Created: 2021-04-10T06:25:45.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-09-22T13:56:44.000Z (3 months ago)
- Last Synced: 2024-11-01T11:34:24.637Z (about 1 month ago)
- Topics: angular, angular2, decorator, lock, locked-functions
- Language: TypeScript
- Homepage: http://npmjs.com/package/ng-lock
- Size: 1.4 MB
- Stars: 6
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
- awesome-angular - ng-lock - Angular decorator for locking functions / user interface while task are running (Uncategorized / Uncategorized)
README
# NgLock [![Build Status](https://travis-ci.com/nigrosimone/ng-lock.svg?branch=main)](https://travis-ci.com/nigrosimone/ng-lock) [![Coverage Status](https://coveralls.io/repos/github/nigrosimone/ng-lock/badge.svg?branch=main)](https://coveralls.io/github/nigrosimone/ng-lock?branch=main) [![NPM version](https://img.shields.io/npm/v/ng-lock.svg)](https://www.npmjs.com/package/ng-lock)
Angular decorator for lock a function and user interface while a task running.
## Description
Ever faced the issue where users click a button multiple times, causing chaos in your application? Meet `ng-lock`, the Angular library designed to save the day. It offers a straightforward way to lock functions and the user interface while a task is running.
### Key Benefits:
1. *Prevents Multiple Clicks*: Ensures a function executes only once until it completes, avoiding redundant operations;
2. *User Interface Locking*: Disables UI elements to signal an ongoing process, enhancing user experience;
3. *Easy Integration*: Simple decorators to lock and unlock functions, reducing boilerplate code.See the [stackblitz demo](https://stackblitz.com/edit/demo-ng-lock?file=src%2Fapp%2Fapp.component.ts).
## Get Started
*Step 1*: install `ng-lock`
```bash
npm i ng-lock
```*Step 2*: Provide `NgLock` into your `bootstrapApplication`, eg.:
```ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { provideNgLock } from 'ng-lock';bootstrapApplication(AppComponent, {
providers: [
provideNgLock(),
provideHttpClient(withInterceptorsFromDi())
]
});
```*Step 3*: Decorate a function with `@ngLock()` decorator, eg.:
```ts
import { Component, isDevMode, Signal, CommonModule } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { delay, Observable, of } from 'rxjs';
import {
ngLock,
ngUnlock,
withNgLockContext,
ngLockChanges,
NgLockDirective,
ngLockSignal,
ngLockObservable,
} from 'ng-lock';const sleep = (time: number) => new Promise(resolve => setTimeout(resolve, time));
const WAIT_TIME = 1500;@Component({
selector: 'app-root',
imports: [NgLockDirective, CommonModule],
template: `
Examples
Sometime there is a need to lock the user interface while a task is running.
Disable the button on click and enable when ngUnlock is called
Click me
Disable the button on click and enable when HTTP request is completed
Click me
Disable the button on click and enable when Observable changes
Click me
Disable the button on click and enable when Promise is resolved
Click me
Signal: {{ sigAsyncMethod() }}, Observable: {{ asyncMethod$ | async }}
Disable the button on click and enable when Subscription changes
Click me
Disable the button on click and enable when unlockTimeout expire
Click me
This input also depends on the lock state, see ngLock directive
`,
styles: [`
button.ng-lock-locked {
pointer-events: none; // disable all event on element
border: 1px solid #999999;
background-color: #cccccc;
color: #666666;
user-select: none;
}
`]
})
export class AppComponent {// if you want more control over the lock status of a decorated method,
// you can get a Signal and/or an Observable of a given method,
// the status: when true is locked; when false is unlocked.
public sigAsyncMethod: Signal = ngLockSignal(this.onAsync);
public asyncMethod$: Observable = ngLockObservable(this.onAsync);
constructor(private http: HttpClient) { }/**
* @ngLock() apply "ng-lock-locked" class on first call and remove it on `ngUnlock(this.onClick)`
*/
@ngLock({ debug: isDevMode() })
onClick(e: MouseEvent) {
setTimeout(() => {
ngUnlock(this.onClick);
console.log('onClick', 'done');
}, WAIT_TIME);
}/**
* @ngLock() apply "ng-lock-locked" class on first call and remove it on HTTP response (@see withNgLockContext)
*/
@ngLock({ debug: isDevMode() })
onHttpRequest(e: MouseEvent) {
this.http
.get('https://my-json-server.typicode.com/typicode/demo/db', {
context: withNgLockContext(this.onHttpRequest),
})
.subscribe((response) => console.log('onHttpRequest', response));
}/**
* @ngLock() apply "ng-lock-locked" class on first call and remove it on observable changes (@see ngLockChanges)
*/
@ngLock({ debug: isDevMode() })
onObservable(e: MouseEvent) {
of('done')
.pipe(delay(WAIT_TIME), ngLockChanges(this.onObservable))
.subscribe((response) => console.log('onObservable', response));
}/**
* @ngLock() apply "ng-lock-locked" class on first call and remove it on promise resolve
*/
@ngLock({ debug: isDevMode() })
async onAsync(e: MouseEvent) {
// async method or that return a Promise is handled, automatic unlock when resolve
await sleep(WAIT_TIME);
console.log('onAsync', 'done');
}/**
* @ngLock() apply "ng-lock-locked" class on first call and remove on subscription changes
*/
@ngLock({ debug: isDevMode() })
onSubscription(e: MouseEvent) {
// method that return a Subscription is handled, automatic unlock when changes
return of('done')
.pipe(delay(WAIT_TIME))
.subscribe((response) => console.log('onSubscription', response));
}/**
* @ngLock() apply "ng-lock-locked" class on first call and remove it after unlockTimeout milliseconds
*/
@ngLock({ debug: isDevMode(), unlockTimeout: WAIT_TIME })
onTimeout(e: MouseEvent) {
console.log('onTimeout', 'done');
}
}
```## NgLock options
There are some optional options can be injected into the `@ngLock()` decorator. This is an example with the default configuration:
```ts
import { Component } from '@angular/core';
import { ngLock, ngUnlock } from 'ng-lock';@Component({
selector: 'app-root',
template: `Click me!`,
styles: [`
button.ng-lock-locked {
pointer-events: none; // disable all event on element
border: 1px solid #999999;
background-color: #cccccc;
color: #666666;
}
`]
})
export class AppComponent {/**
* @ngLock() apply lock on method and "ng-lock-locked" class on first call and remove it on "ngUnlock(this.onClick)"
*/
@ngLock({
maxCall: 1,
unlockTimeout: null,
lockElementFunction: ngLockElementByTargetEventArgument(),
lockClass: 'ng-lock-locked',
returnLastResultWhenLocked: false,
unlockOnPromiseResolve: true,
unlockOnObservableChanges: true,
debug: false
})
onClick(event: MouseEvent){
// ...simulate async long task
setTimeout(() => {
console.log("task executed");
// unlock the method and remove "ng-lock-locked" class on the button
ngUnlock(this.onClick);
}, 3000);
}}
```The options are:
| Option | Description | Default |
| ---------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------- |
| *maxCall* | Max number of the calls beyond which the method is locked | `1` |
| *unlockTimeout* | Max time (in millisecond) to lock function | `null` _(no timeout)_ |
| *lockClass* | CSS class applied when the method is locked | `'ng-lock-locked'` |
| *lockElementFunction* | function for find the HTML element for apply the *lockClass* | `ngLockElementByTargetEventArgument()` |
| *returnLastResultWhenLocked* | if `true`, when the method is locked the last result is returned, otherwise return `undefined` | `false` |
| *unlockOnPromiseResolve* | if `true`, when a locked method return a Promise, the method is automatically unlock when the Promise is resolved| `true` |
| *unlockOnObservableChanges* | if `true`, when a locked method return a subscription, the method is automatically unlock when the observable changes| `true`
| *debug* | if `true`, the decorator log into the console some info | `false` |### Available lockElementFunction
The *lockElementFunction* is function used for find into the HTML the element for apply the *lockClass* (default class is `'ng-lock-locked'`).
#### ngLockElementByQuerySelector(selector: string)
Uses the provided `selector` to find with `document.querySelector()` and apply the *lockClass* on the founded element. The `selector` is a `DOMString` containing a selector to match. Eg.:
```ts
import { Component } from '@angular/core';
import { ngLock, ngUnlock } from 'ng-lock';@Component({
selector: 'app-root',
template: `Click me!`,
styles: [`
button.ng-lock-locked {
pointer-events: none; // disable all event on element
border: 1px solid #999999;
background-color: #cccccc;
color: #666666;
}
`]
})
export class AppComponent {/**
* @ngLock() apply lock on method and "ng-lock-locked" class on the html element with the class "my-class"
*/
@ngLock({
lockElementFunction: ngLockElementByQuerySelector('.my-class')
})
onClick(){
// ...simulate async long task
setTimeout(() => {
console.log("task executed");
// unlock the method and remove "ng-lock-locked" class on the button
ngUnlock(this.onClick);
}, 3000);
}}
```#### ngLockElementByTargetEventArgument(argsIndex?: number)
Uses a function argument for apply the *lockClass*. If provided a `argsIndex` use the specific argument (index of the argument), otherwise search an argument with a target property (o currentTarget) that is a `HTMLElement`. Eg.:
```ts
import { Component } from '@angular/core';
import { ngLock, ngUnlock } from 'ng-lock';@Component({
selector: 'app-root',
template: `Click me!`,
styles: [`
button.ng-lock-locked {
pointer-events: none; // disable all event on element
border: 1px solid #999999;
background-color: #cccccc;
color: #666666;
}
`]
})
export class AppComponent {/**
* @ngLock() apply lock on method and "ng-lock-locked" class on the html element provided into the target element of the second argument (index 1) of onClick() method
*/
@ngLock({
lockElementFunction: ngLockElementByTargetEventArgument(1)
})
onClick(value: number, event: MouseEvent){
// ...simulate async long task
setTimeout(() => {
console.log("task executed", value);
// unlock the method and remove "ng-lock-locked" class on the button
ngUnlock(this.onClick);
}, 3000);
}}
```#### ngLockElementByComponentProperty(property: string)
Apply *lockClass* to a component property that must be a `HTMLElement` or element with Angular `nativeElement` (also a `HTMLElement`). Eg.:
```ts
import { Component, ViewChild } from '@angular/core';
import { ngLock, ngUnlock } from 'ng-lock';@Component({
selector: 'app-root',
template: `Click me!`,
styles: [`
button.ng-lock-locked {
pointer-events: none; // disable all event on element
border: 1px solid #999999;
background-color: #cccccc;
color: #666666;
}
`]
})
export class AppComponent {@ViewChild("button") button: ElementRef;
/**
* @ngLock() apply lock on method and "ng-lock-locked" class on the html element provided into the "button" property of the component
*/
@ngLock({
lockElementFunction: ngLockElementByComponentProperty('button')
})
onClick(){
// ...simulate async long task
setTimeout(() => {
console.log("task executed");
// unlock the method and remove "ng-lock-locked" class on the button
ngUnlock(this.onClick);
}, 3000);
}}
```#### Write a custom lockElementFunction
You can write a custom *lockElementFunction*. Eg.:
```ts
import { Component } from '@angular/core';
import { ngLock, ngUnlock, NgLockElementFunction, NgLockElementFinder } from 'ng-lock';const myLockElementFunction: NgLockElementFunction = (): NgLockElementFinder => {
/**
* @param self Is a component instance (in this example AppComponent).
* @param args Is a @ngLock() decorated function arguments (in this example onClick()).
*/
return (self: any, args: any[]): Element => {
// Write your logic here ...
};
};@Component({
selector: 'app-root',
template: `Click me!`,
styles: [`
button.ng-lock-locked {
pointer-events: none; // disable all event on element
border: 1px solid #999999;
background-color: #cccccc;
color: #666666;
}
`]
})
export class AppComponent {@ngLock({
lockElementFunction: myLockElementFunction()
})
onClick(){
// ...simulate async long task
setTimeout(() => {
console.log("task executed");
// unlock the method and remove "ng-lock-locked" class on the button
ngUnlock(this.onClick);
}, 3000);
}}
```## Utils function
Utils function exported by `ng-lock` library
### ngLock(options?: NgLockOption): MethodDecoratorLock the provided function. Usage as decorator eg.:
```ts
@ngLock()
onClick(event: MouseEvent){
// ...
}
```### ngUnlock(methodToUnlock: NgLockFunction): void
Unlock a locked function by `ngLock()` decorator. Usage, eg.:
```ts
@ngLock()
onClick(event: MouseEvent){
// ...
ngUnlock(this.onClick);
}
```
### ngIsLock(methodToCheck: NgLockFunction): booleanReturn `true` if the provided function is locked by `ngLock()` decorator. Usage, eg.:
```ts
@ngLock()
onClick(event: MouseEvent){
// ...
console.log('onClick is locked?', ngIsLock(this.onClick) );
}
```### ngUnlockAll(component: any): void
Unlock all locked functions by `ngLock()` decorator. Argument `component` is the component instance (`this`). Usage, eg.:
```ts
@ngLock()
onClick(event: MouseEvent){
// ...
ngUnlockAll(this);
}
```### ngLockSignal(method: NgLockFunction): Signal
Return a Signal for the given function on the lock status (locked/unlocked), eg.:
```ts
public myMethodSignal: Signal = ngLockSignal(this.myMethod);
```### ngLockObservable(method: NgLockFunction): Observable
Return an Observable for the given function on the lock status (locked/unlocked), eg.:
```ts
public myMethod$: Observable = ngLockObservable(this.myMethod);
```### ngLockChanges(methodToUnlock: NgLockFunction): (source$: Observable) => Observable
RxJS Operator that unlock the method when Observable changes, eg.:
```ts
@ngLock()
onClick(event: MouseEvent) {
of(true).pipe(ngLockChanges(this.onClick)).subscription();
}
```### withNgLockContext(methodToUnlock: NgLockFunction, context: HttpContext = new HttpContext()): HttpContext
Return a HttpContext that unlock the method when HTTP respond, eg.:
```ts
@ngLock()
onClick(event: MouseEvent) {
this.http.get('https://my-json-server.typicode.com/typicode/demo/db', {
context: withNgLockContext(this.onClick),
}).subscribe();
}
```### ngLock directive
The `ngLock` directive it's a Angular directive lock html element when a decorated method with `@ngLock` is running a task, eg.:
```ts
import { Component } from '@angular/core';
import { ngLock, NgLockDirective } from 'ng-lock';@Component({
selector: 'app-root',
imports: [NgLockDirective],
template: `
Send
`
})
export class AppComponent {
@ngLock()
myMethod(event: MouseEvent){
return new Promise(resolve => setTimeout(resolve, 5000));
}
}
```## Examples
Below there are some examples of use case.
### Example: unlockTimeout
Example of use with `unlockTimeout` option, eg.:
```ts
import { Component } from '@angular/core';
import { ngLock, ngIsLock } from 'ng-lock';@Component({
selector: 'app-root',
template: `
Click me!
Check
`,
styles: [`
button.ng-lock-locked {
pointer-events: none; // disable all event on element
border: 1px solid #999999;
background-color: #cccccc;
color: #666666;
}
`]
})
export class AppComponent {/**
* @ngLock() apply lock on method and "ng-lock-locked" class on first call and remove it after 3 seconds
*/
@ngLock({
unlockTimeout: 3000
})
onClick(event: MouseEvent){
console.log("task executed");
}onCheck(){
console.log('onClick lock state:', ngIsLock(this.onClick));
}
}
```
### Example: maxCallExample of use with `maxCall` option, eg.:
```ts
import { Component } from '@angular/core';
import { ngLock, ngIsLock, ngUnlock } from 'ng-lock';@Component({
selector: 'app-root',
template: `
Click me!
Check
Unlock
`,
styles: [`
button.ng-lock-locked {
pointer-events: none; // disable all event on element
border: 1px solid #999999;
background-color: #cccccc;
color: #666666;
}
`]
})
export class AppComponent {/**
* @ngLock() apply lock on method and "ng-lock-locked" class after 3 call
*/
@ngLock({
maxCall: 3
})
onClick(event: MouseEvent){
console.log("task executed");
}onCheck(){
console.log('onClick lock state:', ngIsLock(this.onClick));
}onUnlock(){
ngUnlock(this.onClick);
}
}
```## Support
This is an open-source project. Star this [repository](https://github.com/nigrosimone/ng-lock), if you like it, or even [donate](https://www.paypal.com/paypalme/snwp). Thank you so much!
## My other libraries
I have published some other Angular libraries, take a look:
- [NgSimpleState: Simple state management in Angular with only Services and RxJS](https://www.npmjs.com/package/ng-simple-state)
- [NgHttpCaching: Cache for HTTP requests in Angular application](https://www.npmjs.com/package/ng-http-caching)
- [NgGenericPipe: Generic pipe for Angular application for use a component method into component template.](https://www.npmjs.com/package/ng-generic-pipe)
- [NgLet: Structural directive for sharing data as local variable into html component template](https://www.npmjs.com/package/ng-let)
- [NgForTrackByProperty: Angular global trackBy property directive with strict type checking](https://www.npmjs.com/package/ng-for-track-by-property)