Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Hold-to-Delete Angular Directive With RxJS and Firestore

Episode 139 written by Jeff Delaney
full courses and content on fireship.io

Health Check: This lesson was last reviewed on and tested with these packages:

  • Angular v6
  • RxJS v6.2

Find an issue? Let's fix it

Source code for Hold-to-Delete Angular Directive With RxJS and Firestore on Github

Have you ever clicked a button by accident, only to have it that delete something valuable with on option to undo? It’s pretty annoying when this happens and there are many different UI elements we can use to prevent it. In the following lesson, you will learn how to use an Angular Attribute Directive to build a hold-to-delete button that requires the user to hold down on the mouse before triggering a delete operation.

Example of a hold to delete directive in Angular

Holdable Directive

The holdable directive’s role is to listen to events on its host element, then calculate the time difference between mousedown and mouseup. It will emit a custom event to the parent component every 100ms, making it easy to animate a progress bar.

We use an RxJS Subject to manage the state of the button, which has two possible state (1) the user is holding down or (2) they are not. The takeUntil operator allows us to cancel an interval when the state changes to cancel.

import { Directive, HostListener, EventEmitter, Output } from '@angular/core';
import { Observable, Subject, interval } from 'rxjs';
import { takeUntil, tap, filter } from 'rxjs/operators';

@Directive({
selector: '[holdable]'
})
export class HoldableDirective {

@Output() holdTime: EventEmitter<number> = new EventEmitter();

state: Subject<string> = new Subject();

cancel: Observable<string>;

constructor() {

this.cancel = this.state.pipe(
filter(v => v === 'cancel'),
tap(v => {
console.log('%c stopped hold', 'color: #ec6969; font-weight: bold;')
this.holdTime.emit(0)
})
);
}

@HostListener('mouseup', ['$event'])
@HostListener('mouseleave', ['$event'])
onExit() {
this.state.next('cancel')
}

@HostListener('mousedown', ['$event'])
onHold() {
console.log('%c started hold', 'color: #5fba7d; font-weight: bold;')

this.state.next('start')

const n = 100;

interval(n).pipe(
takeUntil(this.cancel),
tap(v => {
this.holdTime.emit(v * n)
}),
)
.subscribe();

}

}

Using the Directive for Firestore Deletion

Now that we have a way to track the “hold time” on an element, we can write an event handler that will listen to it and delete a record from the database after 1000ms. The first step is to attach the directive to a host element, such as a button. I’m also adding a progress bar to show the user how close they are to deletion.


<button holdable (holdTime)="deleteSomething($event)">Delete</button>


<progress [value]="progress" max="100" class="danger" ></progress>

Inside a component, you should have a delete event handler that will wait for the value to exceed a certain point, then trigger the backend operation.

@Component(...)
export class MyComponent {

progress: number;

delete(e) {
this.progress = e / 10;
if (this.progress === 100) {
this.firestore.doc(`customers/bob`).delete()
}
}
}