Learn how to use Angular Directives to build a hold-to-delete style button that removes a document from Firestore after holding down for 1 second.  https://angularfirebase.com/lessons/hold-to-delete-button-angular-directive-with-firestore

- Angular Directives https://angular.io/guide/attribute-directives
2024-02-04T07:14:21.715Z
nova-2
2-general-nova
2024-01-09.29447
30089e05-99d1-4376-b32e-c263170674af
e21fb2a3-d72f-4f85-a26f-00c674aba3ef
557480794a742e7186a791e8a7ff5c9f02d1fb6d27a840f0eb50563d7aebef02
67875a7f-c9c4-48a0-aa55-5bdb8a91c34a
deprecated
One design flaw that I see in a lot of apps is having a delete button with no option to undo or to confirm the deletion first.
You could implement an undo button, but then you have some complex state management issues to worry about or you could trigger an alert message, but that's kind of annoying for your users.
What I want to show you today is how to build a hold to delete button, which I think is a refreshing can take on how to prevent accidental deletion, and it's a great exercise for learning how to work with RxJS and Angular directives.
If you're new to the channel, like and subscribe, and you can find the full source code on angularfirebase.com.
The inspiration for this video comes from Josh on our Slack team who asked for a more elegant way to handle deletions than just showing a standard Bootstrap Confirm modal.
I'm definitely not the first person to come up with this idea.
There's a number of apps that have started using this functionality instead of alert messages or undo buttons.
And the idea is pretty simple, we have a regular delete button, but the user needs to hold it for a certain amount of time before the actual deletion is executed.
The way this works is we have an Angular directive that can attach itself to any DOM element or other component, then track the time difference between the mouse down event and the mouse up event.
Then we can emit a custom event every 10th of a second so we can update our progress bar while the user is holding the button down.
I'm just in a regular Angular app and I have AngularFire installed just so we have something meaningful to delete in an actual database, but this lesson's primarily focused on RxJS and Angular.
The first thing we'll do is generate our directive by running ng generate directive holdable from the command line.
We'll be able to attach this directive to a button and then keep track of its internal state and then emit events up to the parent component.
To do that, we'll import host listener, event emitter, and output from Angular Core.
Will also bring in a few things from RxJS that we'll see here in a minute.
The first thing we'll do is set up a custom event and this is the data that we want to emit up to the parent component.
This event will emit the number of milliseconds that a user has held the button down.
So that allows the parent component to decide how long the user can hold the button before it fires off the delete operation to Firestore.
In Angular, you can create public custom events by using the output decorator and making the value an EventEmitter.
In in our case, we'll just be emitting the number of milliseconds.
And then if we look at the HTML, it would be used like this.
We insert the holdable directive in a button and then we wrap hold time in parentheses because it's an event, and later in the video, we'll define a function that handles that event.
Let's go back to our directive and the next thing we'll define is the internal state of this component.
We can define it as a subject, which itself is just an observable that we can push values to.
And there are 2 possible states that this directive can be in, either the user's holding the button down or they're not.
The next thing I'll do is define an observable called cancel, which will be based off of the state source subject.
Eventually will set this observable up to only emit values when the user stops holding the button.
But before we can do that, we need to first listen who mouse events on this element and then update the internal state.
So there are 2 ways that a user can cancel holding the button.
They can either stop clicking where they can move the mouse outside of the button area.
We use host listener to listen to events like mouse up that are built into the DOM.
And and if we want to listen to multiple events, we can just stack them on top of each other like this.
Then we can write a single method to run the same code when either event occurs.
When the user stops holding the button, we just want to update the internal state on this directive to be cancel.
This is important because we'll actually react to the state change when we keep track of the amount of time that the user has been holding down.
The next step is to add a host listener for the mouse down event.
And the first thing I'm going to do here is add a console log that will console log that the hold is started in bright green.
And just a quick side tip, you can console log with custom CSS by doing percent c on the first argument and make the second argument the actual styles you want applied there.
The next thing I'll do is update the internal state to start, so we know the user has started a new hold.
Then I'll define a number that defines the interval period.
So this will be every 10th of a second or 100 milliseconds.
You can adjust that as needed and then I'll set up an RxJS Interval with that number.
When we subscribe to this interval, it will emit a number that increases by 1 every 10th of a second.
0, 1, 2, 3, etcetera.
Now, the tricky part is that we want this observable to stop emitting values when the user has stopped holding it.
And for that, we can actually use the TakeAndTell operator from RxJS then pass in that Cancel Observable.
So when Cancel emits, it will cause this ObservableInterval to complete.
But while the interval is running, we want to use the Tap operator to emit the custom event which is hold time emit.
And then we'll emit the number of milliseconds I'm multiplying it again by that original interval number.
So that will emit the number of milliseconds when the user clicks down, but it will keep running forever because right now we don't have any observable that will cancel it.
We can create that observable by taking our source state subject and then piping in the filter operator and filter it so that only the cancel events are emitted.
And then we'll also throw in a tap operator here and console log that the hold is stopped in bright red.
And then we'll have our custom event emit the number 0 just so we can restart our progress bar when we get to the component.
That takes care of the directive code.
We can now go into the HTML and just drop that into a button and we'll get all of that custom functionality.
If we click the button and hold for a second, you'll see that we first get our green console log, then we get view a 100 milliseconds and then it stops and you emit 0.
It'll be the responsibility of the parent component to listen to these events.
And then we'll set our demo up to delete the document from Firestore once it's been held for at least one second.
We can go ahead and start in the HTML.
We'll add the holdable div and then listen to our custom hold time event, and then we'll define a function here called Hold Handler that takes the event as its argument.
If you want to see how many milliseconds the button has been held, you can just add a console log to the Hold Handler function.
Then to make this non trivial, we'll bring in Firestore and load a collection of documents.
Now I'm just going to copy and paste a pretty big chunk of code here.
But basically what I'm doing is I'm grabbing a collection is Firestore.
And then I'm mapping the document IDs to that collection's data payload so we can use that ID to later delete a document.
I cover this code in detail in the full Ionic course as well as multiple other videos in the past.
It's not critical for this video, so we'll just brush past it.
The next important thing that we'll do is generate a component called customer which will hold the data for each document in Firestore.
When we retrieve this collection from Firestore, we're going to loop over it with n g 4 and then we'll pass each unwrapped object to the child component.
The App component is the parent component so we'll set up our loop there.
We declare the App Customer component, run the ngFor loop.
AngularFire gives us that as an observable, so we unwrap it with the async pipe.
Then we'll pass each document object down to the child component by wrapping customer in square brackets.
And we'll set up that custom input property in the component in the next step.
Now before we write the child component, there's one last tip that I want to show you and that's if you're using a progress bar, you'll want to add an animation property in the CSS.
Otherwise, it will look really janky as it animates from step to step.
Now the child component is going to be pretty simple.
We first bring in AngularFirestore as well as input from Angular Core.
Then we'll decorate the Customer property with input, which allows it to be passed in through the parent component.
And we'll also set up a Progress property that starts at 0.
Now the most important aspect of this component is the delete customer method that handles the hold time event that we defined from our directive.
This method will take the event itself as well as the customer object as an argument and it will update the progress on this component so we can change the value of the progress bar.
When the button's being held down, it's going to emit this event every 10th of a second.
We're dividing it by 10 to treat it more like a 0 to 100 percentage.
And if that value is greater than 100 or one second in this case, we'll use AngularFirestore to delete that customer's document from the database.
And then the last formality for this demo is just the HTML markup in the child component.
So we'll just interpolate some values from the database.
Then we'll add our button down here at the bottom, which of course uses the holdable directive, then uses the delete customer method as the event handler.
And then we also have that progress property available on this component, which we can use to show a progress bar.
The only trick we have to do here is add value in square brackets, so it uses the progress value that we define in the component.
And now if we go back to our app, we should have a result that looks something like this.
I have some dummy records in my database.
If I click the delete button, it will start the progress bar, but if I let go then it stops.
If I keep holding it for at least one second, then you'll see it actually deletes the record from the Firestore database.
Hopefully, that gives you some ideas on how to handle deletes gracefully in your UI.
I'm I'm gonna go ahead and wrap things up there.
If you have any questions, send me a message on Slack.
And if you're ready to build and ship a real life product, consider becoming a pro member at angularfirebase.com.
You'll get access to all kinds of exclusive content like my full courses and book as well as 1 on 1 project support.
Thanks for watching, and I'll talk to you soon.

Speaker 0: One design flaw that I see in a lot of apps is having a delete button with no option to undo or to confirm the deletion first. You could implement an undo button, but then you have some complex state management issues to worry about or you could trigger an alert message, but that's kind of annoying for your users. What I want to show you today is how to build a hold to delete button, which I think is a refreshing can take on how to prevent accidental deletion, and it's a great exercise for learning how to work with RxJS and Angular directives. If you're new to the channel, like and subscribe, and you can find the full source code on angularfirebase.com. The inspiration for this video comes from Josh on our Slack team who asked for a more elegant way to handle deletions than just showing a standard Bootstrap Confirm modal.

I'm definitely not the first person to come up with this idea. There's a number of apps that have started using this functionality instead of alert messages or undo buttons. And the idea is pretty simple, we have a regular delete button, but the user needs to hold it for a certain amount of time before the actual deletion is executed. The way this works is we have an Angular directive that can attach itself to any DOM element or other component, then track the time difference between the mouse down event and the mouse up event. Then we can emit a custom event every 10th of a second so we can update our progress bar while the user is holding the button down.

I'm just in a regular Angular app and I have AngularFire installed just so we have something meaningful to delete in an actual database, but this lesson's primarily focused on RxJS and Angular. The first thing we'll do is generate our directive by running ng generate directive holdable from the command line. We'll be able to attach this directive to a button and then keep track of its internal state and then emit events up to the parent component. To do that, we'll import host listener, event emitter, and output from Angular Core. Will also bring in a few things from RxJS that we'll see here in a minute.

The first thing we'll do is set up a custom event and this is the data that we want to emit up to the parent component. This event will emit the number of milliseconds that a user has held the button down. So that allows the parent component to decide how long the user can hold the button before it fires off the delete operation to Firestore. In Angular, you can create public custom events by using the output decorator and making the value an EventEmitter. In in our case, we'll just be emitting the number of milliseconds.

And then if we look at the HTML, it would be used like this. We insert the holdable directive in a button and then we wrap hold time in parentheses because it's an event, and later in the video, we'll define a function that handles that event. Let's go back to our directive and the next thing we'll define is the internal state of this component. We can define it as a subject, which itself is just an observable that we can push values to. And there are 2 possible states that this directive can be in, either the user's holding the button down or they're not.

The next thing I'll do is define an observable called cancel, which will be based off of the state source subject. Eventually will set this observable up to only emit values when the user stops holding the button. But before we can do that, we need to first listen who mouse events on this element and then update the internal state. So there are 2 ways that a user can cancel holding the button. They can either stop clicking where they can move the mouse outside of the button area.

We use host listener to listen to events like mouse up that are built into the DOM. And and if we want to listen to multiple events, we can just stack them on top of each other like this. Then we can write a single method to run the same code when either event occurs. When the user stops holding the button, we just want to update the internal state on this directive to be cancel. This is important because we'll actually react to the state change when we keep track of the amount of time that the user has been holding down.

The next step is to add a host listener for the mouse down event. And the first thing I'm going to do here is add a console log that will console log that the hold is started in bright green. And just a quick side tip, you can console log with custom CSS by doing percent c on the first argument and make the second argument the actual styles you want applied there. The next thing I'll do is update the internal state to start, so we know the user has started a new hold. Then I'll define a number that defines the interval period.

So this will be every 10th of a second or 100 milliseconds. You can adjust that as needed and then I'll set up an RxJS Interval with that number. When we subscribe to this interval, it will emit a number that increases by 1 every 10th of a second. 0, 1, 2, 3, etcetera. Now, the tricky part is that we want this observable to stop emitting values when the user has stopped holding it.

And for that, we can actually use the TakeAndTell operator from RxJS then pass in that Cancel Observable. So when Cancel emits, it will cause this ObservableInterval to complete. But while the interval is running, we want to use the Tap operator to emit the custom event which is hold time emit. And then we'll emit the number of milliseconds I'm multiplying it again by that original interval number. So that will emit the number of milliseconds when the user clicks down, but it will keep running forever because right now we don't have any observable that will cancel it.

We can create that observable by taking our source state subject and then piping in the filter operator and filter it so that only the cancel events are emitted. And then we'll also throw in a tap operator here and console log that the hold is stopped in bright red. And then we'll have our custom event emit the number 0 just so we can restart our progress bar when we get to the component. That takes care of the directive code. We can now go into the HTML and just drop that into a button and we'll get all of that custom functionality.

If we click the button and hold for a second, you'll see that we first get our green console log, then we get view a 100 milliseconds and then it stops and you emit 0. It'll be the responsibility of the parent component to listen to these events. And then we'll set our demo up to delete the document from Firestore once it's been held for at least one second. We can go ahead and start in the HTML. We'll add the holdable div and then listen to our custom hold time event, and then we'll define a function here called Hold Handler that takes the event as its argument.

If you want to see how many milliseconds the button has been held, you can just add a console log to the Hold Handler function. Then to make this non trivial, we'll bring in Firestore and load a collection of documents. Now I'm just going to copy and paste a pretty big chunk of code here. But basically what I'm doing is I'm grabbing a collection is Firestore. And then I'm mapping the document IDs to that collection's data payload so we can use that ID to later delete a document.

I cover this code in detail in the full Ionic course as well as multiple other videos in the past. It's not critical for this video, so we'll just brush past it. The next important thing that we'll do is generate a component called customer which will hold the data for each document in Firestore. When we retrieve this collection from Firestore, we're going to loop over it with n g 4 and then we'll pass each unwrapped object to the child component. The App component is the parent component so we'll set up our loop there.

We declare the App Customer component, run the ngFor loop. AngularFire gives us that as an observable, so we unwrap it with the async pipe. Then we'll pass each document object down to the child component by wrapping customer in square brackets. And we'll set up that custom input property in the component in the next step. Now before we write the child component, there's one last tip that I want to show you and that's if you're using a progress bar, you'll want to add an animation property in the CSS.

Otherwise, it will look really janky as it animates from step to step. Now the child component is going to be pretty simple. We first bring in AngularFirestore as well as input from Angular Core. Then we'll decorate the Customer property with input, which allows it to be passed in through the parent component. And we'll also set up a Progress property that starts at 0.

Now the most important aspect of this component is the delete customer method that handles the hold time event that we defined from our directive. This method will take the event itself as well as the customer object as an argument and it will update the progress on this component so we can change the value of the progress bar. When the button's being held down, it's going to emit this event every 10th of a second. We're dividing it by 10 to treat it more like a 0 to 100 percentage. And if that value is greater than 100 or one second in this case, we'll use AngularFirestore to delete that customer's document from the database.

And then the last formality for this demo is just the HTML markup in the child component. So we'll just interpolate some values from the database. Then we'll add our button down here at the bottom, which of course uses the holdable directive, then uses the delete customer method as the event handler. And then we also have that progress property available on this component, which we can use to show a progress bar. The only trick we have to do here is add value in square brackets, so it uses the progress value that we define in the component.

And now if we go back to our app, we should have a result that looks something like this. I have some dummy records in my database. If I click the delete button, it will start the progress bar, but if I let go then it stops. If I keep holding it for at least one second, then you'll see it actually deletes the record from the Firestore database. Hopefully, that gives you some ideas on how to handle deletes gracefully in your UI.

I'm I'm gonna go ahead and wrap things up there. If you have any questions, send me a message on Slack. And if you're ready to build and ship a real life product, consider becoming a pro member at angularfirebase.com. You'll get access to all kinds of exclusive content like my full courses and book as well as 1 on 1 project support. Thanks for watching, and I'll talk to you soon.
One design flaw that I see in a lot of apps is having a delete button with no option to undo or to confirm the deletion first. You could implement an undo button, but then you have some complex state management issues to worry about or you could trigger an alert message, but that's kind of annoying for your users. What I want to show you today is how to build a hold to delete button, which I think is a refreshing can take on how to prevent accidental deletion, and it's a great exercise for learning how to work with RxJS and Angular directives.
If you're new to the channel, like and subscribe, and you can find the full source code on angularfirebase.com. The inspiration for this video comes from Josh on our Slack team who asked for a more elegant way to handle deletions than just showing a standard Bootstrap Confirm modal. I'm definitely not the first person to come up with this idea.
youtube
There's a number of apps that have started using this functionality instead of alert messages or undo buttons. And the idea is pretty simple, we have a regular delete button, but the user needs to hold it for a certain amount of time before the actual deletion is executed. The way this works is we have an Angular directive that can attach itself to any DOM element or other component, then track the time difference between the mouse down event and the mouse up event. Then we can emit a custom event every 10th of a second so we can update our progress bar while the user is holding the button down. I'm just in a regular Angular app and I have AngularFire installed just so we have something meaningful to delete in an actual database, but this lesson's primarily focused on RxJS and Angular. The first thing we'll do is generate our directive by running ng generate directive holdable from the command line. We'll be able to attach this directive to a button and then keep track of its internal state and then emit events up to the parent component. To do that, we'll import host listener, event emitter, and output from Angular Core. Will also bring in a few things from RxJS that we'll see here in a minute. The first thing we'll do is set up a custom event and this is the data that we want to emit up to the parent component. This event will emit the number of milliseconds that a user has held the button down. So that allows the parent component to decide how long the user can hold the button before it fires off the delete operation to Firestore. In Angular, you can create public custom events by using the output decorator and making the value an EventEmitter. In in our case, we'll just be emitting the number of milliseconds. And then if we look at the HTML, it would be used like this. We insert the holdable directive in a button and then we wrap hold time in parentheses because it's an event, and later in the video, we'll define a function that handles that event.
Let's go back to our directive and the next thing we'll define is the internal state of this component. We can define it as a subject, which itself is just an observable that we can push values to. And there are 2 possible states that this directive can be in, either the user's holding the button down or they're not.
policy
systems
The next thing I'll do is define an observable called cancel, which will be based off of the state source subject. Eventually will set this observable up to only emit values when the user stops holding the button. But before we can do that, we need to first listen who mouse events on this element and then update the internal state. So there are 2 ways that a user can cancel holding the button. They can either stop clicking where they can move the mouse outside of the button area. We use host listener to listen to events like mouse up that are built into the DOM. And and if we want to listen to multiple events, we can just stack them on top of each other like this. Then we can write a single method to run the same code when either event occurs. When the user stops holding the button, we just want to update the internal state on this directive to be cancel. This is important because we'll actually react to the state change when we keep track of the amount of time that the user has been holding down. The next step is to add a host listener for the mouse down event. And the first thing I'm going to do here is add a console log that will console log that the hold is started in bright green. And just a quick side tip, you can console log with custom CSS by doing percent c on the first argument and make the second argument the actual styles you want applied there. The next thing I'll do is update the internal state to start, so we know the user has started a new hold. Then I'll define a number that defines the interval period. So this will be every 10th of a second or 100 milliseconds. You can adjust that as needed and then I'll set up an RxJS Interval with that number. When we subscribe to this interval, it will emit a number that increases by 1 every 10th of a second. 0, 1, 2, 3, etcetera. Now, the tricky part is that we want this observable to stop emitting values when the user has stopped holding it. And for that, we can actually use the TakeAndTell operator from RxJS then pass in that Cancel Observable. So when Cancel emits, it will cause this ObservableInterval to complete. But while the interval is running, we want to use the Tap operator to emit the custom event which is hold time emit. And then we'll emit the number of milliseconds I'm multiplying it again by that original interval number. So that will emit the number of milliseconds when the user clicks down, but it will keep running forever because right now we don't have any observable that will cancel it. We can create that observable by taking our source state subject and then piping in the filter operator and filter it so that only the cancel events are emitted. And then we'll also throw in a tap operator here and console log that the hold is stopped in bright red. And then we'll have our custom event emit the number 0 just so we can restart our progress bar when we get to the component. That takes care of the directive code. We can now go into the HTML and just drop that into a button and we'll get all of that custom functionality. If we click the button and hold for a second, you'll see that we first get our green console log, then we get view a 100 milliseconds and then it stops and you emit 0. It'll be the responsibility of the parent component to listen to these events. And then we'll set our demo up to delete the document from Firestore once it's been held for at least one second. We can go ahead and start in the HTML. We'll add the holdable div and then listen to our custom hold time event, and then we'll define a function here called Hold Handler that takes the event as its argument. If you want to see how many milliseconds the button has been held, you can just add a console log to the Hold Handler function.
Then to make this non trivial, we'll bring in Firestore and load a collection of documents. Now I'm just going to copy and paste a pretty big chunk of code here. But basically what I'm doing is I'm grabbing a collection is Firestore. And then I'm mapping the document IDs to that collection's data payload so we can use that ID to later delete a document. I cover this code in detail in the full Ionic course as well as multiple other videos in the past. It's not critical for this video, so we'll just brush past it. The next important thing that we'll do is generate a component called customer which will hold the data for each document in Firestore.
streaming
theft
When we retrieve this collection from Firestore, we're going to loop over it with n g 4 and then we'll pass each unwrapped object to the child component. The App component is the parent component so we'll set up our loop there. We declare the App Customer component, run the ngFor loop. AngularFire gives us that as an observable, so we unwrap it with the async pipe.
Then we'll pass each document object down to the child component by wrapping customer in square brackets. And we'll set up that custom input property in the component in the next step. Now before we write the child component, there's one last tip that I want to show you and that's if you're using a progress bar, you'll want to add an animation property in the CSS. Otherwise, it will look really janky as it animates from step to step. Now the child component is going to be pretty simple. We first bring in AngularFirestore as well as input from Angular Core. Then we'll decorate the Customer property with input, which allows it to be passed in through the parent component.
children
animation
And we'll also set up a Progress property that starts at 0. Now the most important aspect of this component is the delete customer method that handles the hold time event that we defined from our directive. This method will take the event itself as well as the customer object as an argument and it will update the progress on this component so we can change the value of the progress bar. When the button's being held down, it's going to emit this event every 10th of a second. We're dividing it by 10 to treat it more like a 0 to 100 percentage.
And if that value is greater than 100 or one second in this case, we'll use AngularFirestore to delete that customer's document from the database. And then the last formality for this demo is just the HTML markup in the child component. So we'll just interpolate some values from the database.
databases
Then we'll add our button down here at the bottom, which of course uses the holdable directive, then uses the delete customer method as the event handler. And then we also have that progress property available on this component, which we can use to show a progress bar. The only trick we have to do here is add value in square brackets, so it uses the progress value that we define in the component. And now if we go back to our app, we should have a result that looks something like this. I have some dummy records in my database. If I click the delete button, it will start the progress bar, but if I let go then it stops. If I keep holding it for at least one second, then you'll see it actually deletes the record from the Firestore database. Hopefully, that gives you some ideas on how to handle deletes gracefully in your UI. I'm I'm gonna go ahead and wrap things up there. If you have any questions, send me a message on Slack. And if you're ready to build and ship a real life product, consider becoming a pro member at angularfirebase.com.
You'll get access to all kinds of exclusive content like my full courses and book as well as 1 on 1 project support. Thanks for watching, and I'll talk to you soon.
websites
One design flaw that I see in a lot of apps is having a delete button with no option to undo or to confirm the deletion first. You could implement an undo button, but then you have some complex state management issues to worry about or you could trigger an alert message, but that's kind of annoying for your users. What I want to show you today is how to build a hold to delete button, which I think is a refreshing can take on how to prevent accidental deletion, and it's a great exercise for learning how to work with RxJS and Angular directives. If you're new to the channel, like and subscribe, and you can find the full source code on angularfirebase.com. The inspiration for this video comes from Josh on our Slack team who asked for a more elegant way to handle deletions than just showing a standard Bootstrap Confirm modal. I'm definitely not the first person to come up with this idea. There's a number of apps that have started using this functionality instead of alert messages or undo buttons. And the idea is pretty simple, we have a regular delete button, but the user needs to hold it for a certain amount of time before the actual deletion is executed. The way this works is we have an Angular directive that can attach itself to any DOM element or other component, then track the time difference between the mouse down event and the mouse up event. Then we can emit a custom event every 10th of a second so we can update our progress bar while the user is holding the button down. I'm just in a regular Angular app and I have AngularFire installed just so we have something meaningful to delete in an actual database, but this lesson's primarily focused on RxJS and Angular. The first thing we'll do is generate our directive by running ng generate directive holdable from the command line. We'll be able to attach this directive to a button and then keep track of its internal state and then emit events up to the parent component. To do that, we'll import host listener, event emitter, and output from Angular Core. Will also bring in a few things from RxJS that we'll see here in a minute. The first thing we'll do is set up a custom event and this is the data that we want to emit up to the parent component. This event will emit the number of milliseconds that a user has held the button down. So that allows the parent component to decide how long the user can hold the button before it fires off the delete operation to Firestore. In Angular, you can create public custom events by using the output decorator and making the value an EventEmitter. In in our case, we'll just be emitting the number of milliseconds. And then if we look at the HTML, it would be used like this. We insert the holdable directive in a button and then we wrap hold time in parentheses because it's an event, and later in the video, we'll define a function that handles that event. Let's go back to our directive and the next thing we'll define is the internal state of this component. We can define it as a subject, which itself is just an observable that we can push values to. And there are 2 possible states that this directive can be in, either the user's holding the button down or they're not. The next thing I'll do is define an observable called cancel, which will be based off of the state source subject. Eventually will set this observable up to only emit values when the user stops holding the button. But before we can do that, we need to first listen who mouse events on this element and then update the internal state. So there are 2 ways that a user can cancel holding the button. They can either stop clicking where they can move the mouse outside of the button area. We use host listener to listen to events like mouse up that are built into the DOM. And and if we want to listen to multiple events, we can just stack them on top of each other like this. Then we can write a single method to run the same code when either event occurs. When the user stops holding the button, we just want to update the internal state on this directive to be cancel. This is important because we'll actually react to the state change when we keep track of the amount of time that the user has been holding down. The next step is to add a host listener for the mouse down event. And the first thing I'm going to do here is add a console log that will console log that the hold is started in bright green. And just a quick side tip, you can console log with custom CSS by doing percent c on the first argument and make the second argument the actual styles you want applied there. The next thing I'll do is update the internal state to start, so we know the user has started a new hold. Then I'll define a number that defines the interval period. So this will be every 10th of a second or 100 milliseconds. You can adjust that as needed and then I'll set up an RxJS Interval with that number. When we subscribe to this interval, it will emit a number that increases by 1 every 10th of a second. 0, 1, 2, 3, etcetera. Now, the tricky part is that we want this observable to stop emitting values when the user has stopped holding it. And for that, we can actually use the TakeAndTell operator from RxJS then pass in that Cancel Observable. So when Cancel emits, it will cause this ObservableInterval to complete. But while the interval is running, we want to use the Tap operator to emit the custom event which is hold time emit. And then we'll emit the number of milliseconds I'm multiplying it again by that original interval number. So that will emit the number of milliseconds when the user clicks down, but it will keep running forever because right now we don't have any observable that will cancel it. We can create that observable by taking our source state subject and then piping in the filter operator and filter it so that only the cancel events are emitted. And then we'll also throw in a tap operator here and console log that the hold is stopped in bright red. And then we'll have our custom event emit the number 0 just so we can restart our progress bar when we get to the component. That takes care of the directive code. We can now go into the HTML and just drop that into a button and we'll get all of that custom functionality. If we click the button and hold for a second, you'll see that we first get our green console log, then we get view a 100 milliseconds and then it stops and you emit 0. It'll be the responsibility of the parent component to listen to these events. And then we'll set our demo up to delete the document from Firestore once it's been held for at least one second. We can go ahead and start in the HTML. We'll add the holdable div and then listen to our custom hold time event, and then we'll define a function here called Hold Handler that takes the event as its argument. If you want to see how many milliseconds the button has been held, you can just add a console log to the Hold Handler function. Then to make this non trivial, we'll bring in Firestore and load a collection of documents. Now I'm just going to copy and paste a pretty big chunk of code here. But basically what I'm doing is I'm grabbing a collection is Firestore. And then I'm mapping the document IDs to that collection's data payload so we can use that ID to later delete a document. I cover this code in detail in the full Ionic course as well as multiple other videos in the past. It's not critical for this video, so we'll just brush past it. The next important thing that we'll do is generate a component called customer which will hold the data for each document in Firestore. When we retrieve this collection from Firestore, we're going to loop over it with n g 4 and then we'll pass each unwrapped object to the child component. The App component is the parent component so we'll set up our loop there. We declare the App Customer component, run the ngFor loop. AngularFire gives us that as an observable, so we unwrap it with the async pipe. Then we'll pass each document object down to the child component by wrapping customer in square brackets. And we'll set up that custom input property in the component in the next step. Now before we write the child component, there's one last tip that I want to show you and that's if you're using a progress bar, you'll want to add an animation property in the CSS. Otherwise, it will look really janky as it animates from step to step. Now the child component is going to be pretty simple. We first bring in AngularFirestore as well as input from Angular Core. Then we'll decorate the Customer property with input, which allows it to be passed in through the parent component. And we'll also set up a Progress property that starts at 0. Now the most important aspect of this component is the delete customer method that handles the hold time event that we defined from our directive. This method will take the event itself as well as the customer object as an argument and it will update the progress on this component so we can change the value of the progress bar. When the button's being held down, it's going to emit this event every 10th of a second. We're dividing it by 10 to treat it more like a 0 to 100 percentage. And if that value is greater than 100 or one second in this case, we'll use AngularFirestore to delete that customer's document from the database. And then the last formality for this demo is just the HTML markup in the child component. So we'll just interpolate some values from the database. Then we'll add our button down here at the bottom, which of course uses the holdable directive, then uses the delete customer method as the event handler. And then we also have that progress property available on this component, which we can use to show a progress bar. The only trick we have to do here is add value in square brackets, so it uses the progress value that we define in the component. And now if we go back to our app, we should have a result that looks something like this. I have some dummy records in my database. If I click the delete button, it will start the progress bar, but if I let go then it stops. If I keep holding it for at least one second, then you'll see it actually deletes the record from the Firestore database. Hopefully, that gives you some ideas on how to handle deletes gracefully in your UI. I'm I'm gonna go ahead and wrap things up there. If you have any questions, send me a message on Slack. And if you're ready to build and ship a real life product, consider becoming a pro member at angularfirebase.com. You'll get access to all kinds of exclusive content like my full courses and book as well as 1 on 1 project support. Thanks for watching, and I'll talk to you soon.
One
one
design
design
flaw
flaw
that
that
I
i
see
see
in
in
a
a
lot
lot
of
of
apps
apps
is
is
having
having
a
a
delete
delete
button
button
with
with
no
no
option
option
to
to
undo
undo
or
or
to
to
confirm
confirm
the
the
deletion
deletion
first.
first
You
you
could
could
implement
implement
an
an
undo
undo
button,
button
but
but
then
then
you
you
have
have
some
some
complex
complex
state
state
management
management
issues
issues
to
to
worry
worry
about
about
or
or
you
you
could
could
trigger
trigger
an
an
alert
alert
message,
message
but
but
that's
that's
kind
kind
of
of
annoying
annoying
for
for
your
your
users.
users
What
what
I
i
want
want
to
to
show
show
you
you
today
today
is
is
how
how
to
to
build
build
a
a
hold
hold
to
to
delete
delete
button,
button
which
which
I
i
think
think
is
is
a
a
refreshing
refreshing
can
can
take
take
on
on
how
how
to
to
prevent
prevent
accidental
accidental
deletion,
deletion
and
and
it's
it's
a
a
great
great
exercise
exercise
for
for
learning
learning
how
how
to
to
work
work
with
with
RxJS
rxjs
and
and
Angular
angular
directives.
directives
If
if
you're
you're
new
new
to
to
the
the
channel,
channel
like
like
and
and
subscribe,
subscribe
and
and
you
you
can
can
find
find
the
the
full
full
source
source
code
code
on
on
angularfirebase.com.
angularfirebase.com
The
the
inspiration
inspiration
for
for
this
this
video
video
comes
comes
from
from
Josh
josh
on
on
our
our
Slack
slack
team
team
who
who
asked
asked
for
for
a
a
more
more
elegant
elegant
way
way
to
to
handle
handle
deletions
deletions
than
than
just
just
showing
showing
a
a
standard
standard
Bootstrap
bootstrap
Confirm
confirm
modal.
modal
I'm
i'm
definitely
definitely
not
not
the
the
first
first
person
person
to
to
come
come
up
up
with
with
this
this
idea.
idea
There's
there's
a
a
number
number
of
of
apps
apps
that
that
have
have
started
started
using
using
this
this
functionality
functionality
instead
instead
of
of
alert
alert
messages
messages
or
or
undo
undo
buttons.
buttons
And
and
the
the
idea
idea
is
is
pretty
pretty
simple,
simple
we
we
have
have
a
a
regular
regular
delete
delete
button,
button
but
but
the
the
user
user
needs
needs
to
to
hold
hold
it
it
for
for
a
a
certain
certain
amount
amount
of
of
time
time
before
before
the
the
actual
actual
deletion
deletion
is
is
executed.
executed
The
the
way
way
this
this
works
works
is
is
we
we
have
have
an
an
Angular
angular
directive
directive
that
that
can
can
attach
attach
itself
itself
to
to
any
any
DOM
dom
element
element
or
or
other
other
component,
component
then
then
track
track
the
the
time
time
difference
difference
between
between
the
the
mouse
mouse
down
down
event
event
and
and
the
the
mouse
mouse
up
up
event.
event
Then
then
we
we
can
can
emit
emit
a
a
custom
custom
event
event
every
every
10th
10th
of
of
a
a
second
second
so
so
we
we
can
can
update
update
our
our
progress
progress
bar
bar
while
while
the
the
user
user
is
is
holding
holding
the
the
button
button
down.
down
I'm
i'm
just
just
in
in
a
a
regular
regular
Angular
angular
app
app
and
and
I
i
have
have
AngularFire
angularfire
installed
installed
just
just
so
so
we
we
have
have
something
something
meaningful
meaningful
to
to
delete
delete
in
in
an
an
actual
actual
database,
database
but
but
this
this
lesson's
lesson's
primarily
primarily
focused
focused
on
on
RxJS
rxjs
and
and
Angular.
angular
The
the
first
first
thing
thing
we'll
we'll
do
do
is
is
generate
generate
our
our
directive
directive
by
by
running
running
ng
ng
generate
generate
directive
directive
holdable
holdable
from
from
the
the
command
command
line.
line
We'll
we'll
be
be
able
able
to
to
attach
attach
this
this
directive
directive
to
to
a
a
button
button
and
and
then
then
keep
keep
track
track
of
of
its
its
internal
internal
state
state
and
and
then
then
emit
emit
events
events
up
up
to
to
the
the
parent
parent
component.
component
To
to
do
do
that,
that
we'll
we'll
import
import
host
host
listener,
listener
event
event
emitter,
emitter
and
and
output
output
from
from
Angular
angular
Core.
core
Will
will
also
also
bring
bring
in
in
a
a
few
few
things
things
from
from
RxJS
rxjs
that
that
we'll
we'll
see
see
here
here
in
in
a
a
minute.
minute
The
the
first
first
thing
thing
we'll
we'll
do
do
is
is
set
set
up
up
a
a
custom
custom
event
event
and
and
this
this
is
is
the
the
data
data
that
that
we
we
want
want
to
to
emit
emit
up
up
to
to
the
the
parent
parent
component.
component
This
this
event
event
will
will
emit
emit
the
the
number
number
of
of
milliseconds
milliseconds
that
that
a
a
user
user
has
has
held
held
the
the
button
button
down.
down
So
so
that
that
allows
allows
the
the
parent
parent
component
component
to
to
decide
decide
how
how
long
long
the
the
user
user
can
can
hold
hold
the
the
button
button
before
before
it
it
fires
fires
off
off
the
the
delete
delete
operation
operation
to
to
Firestore.
firestore
In
in
Angular,
angular
you
you
can
can
create
create
public
public
custom
custom
events
events
by
by
using
using
the
the
output
output
decorator
decorator
and
and
making
making
the
the
value
value
an
an
EventEmitter.
eventemitter
In
in
in
in
our
our
case,
case
we'll
we'll
just
just
be
be
emitting
emitting
the
the
number
number
of
of
milliseconds.
milliseconds
And
and
then
then
if
if
we
we
look
look
at
at
the
the
HTML,
html
it
it
would
would
be
be
used
used
like
like
this.
this
We
we
insert
insert
the
the
holdable
holdable
directive
directive
in
in
a
a
button
button
and
and
then
then
we
we
wrap
wrap
hold
hold
time
time
in
in
parentheses
parentheses
because
because
it's
it's
an
an
event,
event
and
and
later
later
in
in
the
the
video,
video
we'll
we'll
define
define
a
a
function
function
that
that
handles
handles
that
that
event.
event
Let's
let's
go
go
back
back
to
to
our
our
directive
directive
and
and
the
the
next
next
thing
thing
we'll
we'll
define
define
is
is
the
the
internal
internal
state
state
of
of
this
this
component.
component
We
we
can
can
define
define
it
it
as
as
a
a
subject,
subject
which
which
itself
itself
is
is
just
just
an
an
observable
observable
that
that
we
we
can
can
push
push
values
values
to.
to
And
and
there
there
are
are
2
2
possible
possible
states
states
that
that
this
this
directive
directive
can
can
be
be
in,
in
either
either
the
the
user's
user's
holding
holding
the
the
button
button
down
down
or
or
they're
they're
not.
not
The
the
next
next
thing
thing
I'll
i'll
do
do
is
is
define
define
an
an
observable
observable
called
called
cancel,
cancel
which
which
will
will
be
be
based
based
off
off
of
of
the
the
state
state
source
source
subject.
subject
Eventually
eventually
will
will
set
set
this
this
observable
observable
up
up
to
to
only
only
emit
emit
values
values
when
when
the
the
user
user
stops
stops
holding
holding
the
the
button.
button
But
but
before
before
we
we
can
can
do
do
that,
that
we
we
need
need
to
to
first
first
listen
listen
who
who
mouse
mouse
events
events
on
on
this
this
element
element
and
and
then
then
update
update
the
the
internal
internal
state.
state
So
so
there
there
are
are
2
2
ways
ways
that
that
a
a
user
user
can
can
cancel
cancel
holding
holding
the
the
button.
button
They
they
can
can
either
either
stop
stop
clicking
clicking
where
where
they
they
can
can
move
move
the
the
mouse
mouse
outside
outside
of
of
the
the
button
button
area.
area
We
we
use
use
host
host
listener
listener
to
to
listen
listen
to
to
events
events
like
like
mouse
mouse
up
up
that
that
are
are
built
built
into
into
the
the
DOM.
dom
And
and
and
and
if
if
we
we
want
want
to
to
listen
listen
to
to
multiple
multiple
events,
events
we
we
can
can
just
just
stack
stack
them
them
on
on
top
top
of
of
each
each
other
other
like
like
this.
this
Then
then
we
we
can
can
write
write
a
a
single
single
method
method
to
to
run
run
the
the
same
same
code
code
when
when
either
either
event
event
occurs.
occurs
When
when
the
the
user
user
stops
stops
holding
holding
the
the
button,
button
we
we
just
just
want
want
to
to
update
update
the
the
internal
internal
state
state
on
on
this
this
directive
directive
to
to
be
be
cancel.
cancel
This
this
is
is
important
important
because
because
we'll
we'll
actually
actually
react
react
to
to
the
the
state
state
change
change
when
when
we
we
keep
keep
track
track
of
of
the
the
amount
amount
of
of
time
time
that
that
the
the
user
user
has
has
been
been
holding
holding
down.
down
The
the
next
next
step
step
is
is
to
to
add
add
a
a
host
host
listener
listener
for
for
the
the
mouse
mouse
down
down
event.
event
And
and
the
the
first
first
thing
thing
I'm
i'm
going
going
to
to
do
do
here
here
is
is
add
add
a
a
console
console
log
log
that
that
will
will
console
console
log
log
that
that
the
the
hold
hold
is
is
started
started
in
in
bright
bright
green.
green
And
and
just
just
a
a
quick
quick
side
side
tip,
tip
you
you
can
can
console
console
log
log
with
with
custom
custom
CSS
css
by
by
doing
doing
percent
percent
c
c
on
on
the
the
first
first
argument
argument
and
and
make
make
the
the
second
second
argument
argument
the
the
actual
actual
styles
styles
you
you
want
want
applied
applied
there.
there
The
the
next
next
thing
thing
I'll
i'll
do
do
is
is
update
update
the
the
internal
internal
state
state
to
to
start,
start
so
so
we
we
know
know
the
the
user
user
has
has
started
started
a
a
new
new
hold.
hold
Then
then
I'll
i'll
define
define
a
a
number
number
that
that
defines
defines
the
the
interval
interval
period.
period
So
so
this
this
will
will
be
be
every
every
10th
10th
of
of
a
a
second
second
or
or
100
100
milliseconds.
milliseconds
You
you
can
can
adjust
adjust
that
that
as
as
needed
needed
and
and
then
then
I'll
i'll
set
set
up
up
an
an
RxJS
rxjs
Interval
interval
with
with
that
that
number.
number
When
when
we
we
subscribe
subscribe
to
to
this
this
interval,
interval
it
it
will
will
emit
emit
a
a
number
number
that
that
increases
increases
by
by
1
1
every
every
10th
10th
of
of
a
a
second.
second
0,
0
1,
1
2,
2
3,
3
etcetera.
etcetera
Now,
now
the
the
tricky
tricky
part
part
is
is
that
that
we
we
want
want
this
this
observable
observable
to
to
stop
stop
emitting
emitting
values
values
when
when
the
the
user
user
has
has
stopped
stopped
holding
holding
it.
it
And
and
for
for
that,
that
we
we
can
can
actually
actually
use
use
the
the
TakeAndTell
takeandtell
operator
operator
from
from
RxJS
rxjs
then
then
pass
pass
in
in
that
that
Cancel
cancel
Observable.
observable
So
so
when
when
Cancel
cancel
emits,
emits
it
it
will
will
cause
cause
this
this
ObservableInterval
observableinterval
to
to
complete.
complete
But
but
while
while
the
the
interval
interval
is
is
running,
running
we
we
want
want
to
to
use
use
the
the
Tap
tap
operator
operator
to
to
emit
emit
the
the
custom
custom
event
event
which
which
is
is
hold
hold
time
time
emit.
emit
And
and
then
then
we'll
we'll
emit
emit
the
the
number
number
of
of
milliseconds
milliseconds
I'm
i'm
multiplying
multiplying
it
it
again
again
by
by
that
that
original
original
interval
interval
number.
number
So
so
that
that
will
will
emit
emit
the
the
number
number
of
of
milliseconds
milliseconds
when
when
the
the
user
user
clicks
clicks
down,
down
but
but
it
it
will
will
keep
keep
running
running
forever
forever
because
because
right
right
now
now
we
we
don't
don't
have
have
any
any
observable
observable
that
that
will
will
cancel
cancel
it.
it
We
we
can
can
create
create
that
that
observable
observable
by
by
taking
taking
our
our
source
source
state
state
subject
subject
and
and
then
then
piping
piping
in
in
the
the
filter
filter
operator
operator
and
and
filter
filter
it
it
so
so
that
that
only
only
the
the
cancel
cancel
events
events
are
are
emitted.
emitted
And
and
then
then
we'll
we'll
also
also
throw
throw
in
in
a
a
tap
tap
operator
operator
here
here
and
and
console
console
log
log
that
that
the
the
hold
hold
is
is
stopped
stopped
in
in
bright
bright
red.
red
And
and
then
then
we'll
we'll
have
have
our
our
custom
custom
event
event
emit
emit
the
the
number
number
0
0
just
just
so
so
we
we
can
can
restart
restart
our
our
progress
progress
bar
bar
when
when
we
we
get
get
to
to
the
the
component.
component
That
that
takes
takes
care
care
of
of
the
the
directive
directive
code.
code
We
we
can
can
now
now
go
go
into
into
the
the
HTML
html
and
and
just
just
drop
drop
that
that
into
into
a
a
button
button
and
and
we'll
we'll
get
get
all
all
of
of
that
that
custom
custom
functionality.
functionality
If
if
we
we
click
click
the
the
button
button
and
and
hold
hold
for
for
a
a
second,
second
you'll
you'll
see
see
that
that
we
we
first
first
get
get
our
our
green
green
console
console
log,
log
then
then
we
we
get
get
view
view
a
a
100
100
milliseconds
milliseconds
and
and
then
then
it
it
stops
stops
and
and
you
you
emit
emit
0.
0
It'll
it'll
be
be
the
the
responsibility
responsibility
of
of
the
the
parent
parent
component
component
to
to
listen
listen
to
to
these
these
events.
events
And
and
then
then
we'll
we'll
set
set
our
our
demo
demo
up
up
to
to
delete
delete
the
the
document
document
from
from
Firestore
firestore
once
once
it's
it's
been
been
held
held
for
for
at
at
least
least
one
one
second.
second
We
we
can
can
go
go
ahead
ahead
and
and
start
start
in
in
the
the
HTML.
html
We'll
we'll
add
add
the
the
holdable
holdable
div
div
and
and
then
then
listen
listen
to
to
our
our
custom
custom
hold
hold
time
time
event,
event
and
and
then
then
we'll
we'll
define
define
a
a
function
function
here
here
called
called
Hold
hold
Handler
handler
that
that
takes
takes
the
the
event
event
as
as
its
its
argument.
argument
If
if
you
you
want
want
to
to
see
see
how
how
many
many
milliseconds
milliseconds
the
the
button
button
has
has
been
been
held,
held
you
you
can
can
just
just
add
add
a
a
console
console
log
log
to
to
the
the
Hold
hold
Handler
handler
function.
function
Then
then
to
to
make
make
this
this
non
non
trivial,
trivial
we'll
we'll
bring
bring
in
in
Firestore
firestore
and
and
load
load
a
a
collection
collection
of
of
documents.
documents
Now
now
I'm
i'm
just
just
going
going
to
to
copy
copy
and
and
paste
paste
a
a
pretty
pretty
big
big
chunk
chunk
of
of
code
code
here.
here
But
but
basically
basically
what
what
I'm
i'm
doing
doing
is
is
I'm
i'm
grabbing
grabbing
a
a
collection
collection
is
is
Firestore.
firestore
And
and
then
then
I'm
i'm
mapping
mapping
the
the
document
document
IDs
ids
to
to
that
that
collection's
collection's
data
data
payload
payload
so
so
we
we
can
can
use
use
that
that
ID
id
to
to
later
later
delete
delete
a
a
document.
document
I
i
cover
cover
this
this
code
code
in
in
detail
detail
in
in
the
the
full
full
Ionic
ionic
course
course
as
as
well
well
as
as
multiple
multiple
other
other
videos
videos
in
in
the
the
past.
past
It's
it's
not
not
critical
critical
for
for
this
this
video,
video
so
so
we'll
we'll
just
just
brush
brush
past
past
it.
it
The
the
next
next
important
important
thing
thing
that
that
we'll
we'll
do
do
is
is
generate
generate
a
a
component
component
called
called
customer
customer
which
which
will
will
hold
hold
the
the
data
data
for
for
each
each
document
document
in
in
Firestore.
firestore
When
when
we
we
retrieve
retrieve
this
this
collection
collection
from
from
Firestore,
firestore
we're
we're
going
going
to
to
loop
loop
over
over
it
it
with
with
n
n
g
g
4
4
and
and
then
then
we'll
we'll
pass
pass
each
each
unwrapped
unwrapped
object
object
to
to
the
the
child
child
component.
component
The
the
App
app
component
component
is
is
the
the
parent
parent
component
component
so
so
we'll
we'll
set
set
up
up
our
our
loop
loop
there.
there
We
we
declare
declare
the
the
App
app
Customer
customer
component,
component
run
run
the
the
ngFor
ngfor
loop.
loop
AngularFire
angularfire
gives
gives
us
us
that
that
as
as
an
an
observable,
observable
so
so
we
we
unwrap
unwrap
it
it
with
with
the
the
async
async
pipe.
pipe
Then
then
we'll
we'll
pass
pass
each
each
document
document
object
object
down
down
to
to
the
the
child
child
component
component
by
by
wrapping
wrapping
customer
customer
in
in
square
square
brackets.
brackets
And
and
we'll
we'll
set
set
up
up
that
that
custom
custom
input
input
property
property
in
in
the
the
component
component
in
in
the
the
next
next
step.
step
Now
now
before
before
we
we
write
write
the
the
child
child
component,
component
there's
there's
one
one
last
last
tip
tip
that
that
I
i
want
want
to
to
show
show
you
you
and
and
that's
that's
if
if
you're
you're
using
using
a
a
progress
progress
bar,
bar
you'll
you'll
want
want
to
to
add
add
an
an
animation
animation
property
property
in
in
the
the
CSS.
css
Otherwise,
otherwise
it
it
will
will
look
look
really
really
janky
janky
as
as
it
it
animates
animates
from
from
step
step
to
to
step.
step
Now
now
the
the
child
child
component
component
is
is
going
going
to
to
be
be
pretty
pretty
simple.
simple
We
we
first
first
bring
bring
in
in
AngularFirestore
angularfirestore
as
as
well
well
as
as
input
input
from
from
Angular
angular
Core.
core
Then
then
we'll
we'll
decorate
decorate
the
the
Customer
customer
property
property
with
with
input,
input
which
which
allows
allows
it
it
to
to
be
be
passed
passed
in
in
through
through
the
the
parent
parent
component.
component
And
and
we'll
we'll
also
also
set
set
up
up
a
a
Progress
progress
property
property
that
that
starts
starts
at
at
0.
0
Now
now
the
the
most
most
important
important
aspect
aspect
of
of
this
this
component
component
is
is
the
the
delete
delete
customer
customer
method
method
that
that
handles
handles
the
the
hold
hold
time
time
event
event
that
that
we
we
defined
defined
from
from
our
our
directive.
directive
This
this
method
method
will
will
take
take
the
the
event
event
itself
itself
as
as
well
well
as
as
the
the
customer
customer
object
object
as
as
an
an
argument
argument
and
and
it
it
will
will
update
update
the
the
progress
progress
on
on
this
this
component
component
so
so
we
we
can
can
change
change
the
the
value
value
of
of
the
the
progress
progress
bar.
bar
When
when
the
the
button's
button's
being
being
held
held
down,
down
it's
it's
going
going
to
to
emit
emit
this
this
event
event
every
every
10th
10th
of
of
a
a
second.
second
We're
we're
dividing
dividing
it
it
by
by
10
10
to
to
treat
treat
it
it
more
more
like
like
a
a
0
0
to
to
100
100
percentage.
percentage
And
and
if
if
that
that
value
value
is
is
greater
greater
than
than
100
100
or
or
one
one
second
second
in
in
this
this
case,
case
we'll
we'll
use
use
AngularFirestore
angularfirestore
to
to
delete
delete
that
that
customer's
customer's
document
document
from
from
the
the
database.
database
And
and
then
then
the
the
last
last
formality
formality
for
for
this
this
demo
demo
is
is
just
just
the
the
HTML
html
markup
markup
in
in
the
the
child
child
component.
component
So
so
we'll
we'll
just
just
interpolate
interpolate
some
some
values
values
from
from
the
the
database.
database
Then
then
we'll
we'll
add
add
our
our
button
button
down
down
here
here
at
at
the
the
bottom,
bottom
which
which
of
of
course
course
uses
uses
the
the
holdable
holdable
directive,
directive
then
then
uses
uses
the
the
delete
delete
customer
customer
method
method
as
as
the
the
event
event
handler.
handler
And
and
then
then
we
we
also
also
have
have
that
that
progress
progress
property
property
available
available
on
on
this
this
component,
component
which
which
we
we
can
can
use
use
to
to
show
show
a
a
progress
progress
bar.
bar
The
the
only
only
trick
trick
we
we
have
have
to
to
do
do
here
here
is
is
add
add
value
value
in
in
square
square
brackets,
brackets
so
so
it
it
uses
uses
the
the
progress
progress
value
value
that
that
we
we
define
define
in
in
the
the
component.
component
And
and
now
now
if
if
we
we
go
go
back
back
to
to
our
our
app,
app
we
we
should
should
have
have
a
a
result
result
that
that
looks
looks
something
something
like
like
this.
this
I
i
have
have
some
some
dummy
dummy
records
records
in
in
my
my
database.
database
If
if
I
i
click
click
the
the
delete
delete
button,
button
it
it
will
will
start
start
the
the
progress
progress
bar,
bar
but
but
if
if
I
i
let
let
go
go
then
then
it
it
stops.
stops
If
if
I
i
keep
keep
holding
holding
it
it
for
for
at
at
least
least
one
one
second,
second
then
then
you'll
you'll
see
see
it
it
actually
actually
deletes
deletes
the
the
record
record
from
from
the
the
Firestore
firestore
database.
database
Hopefully,
hopefully
that
that
gives
gives
you
you
some
some
ideas
ideas
on
on
how
how
to
to
handle
handle
deletes
deletes
gracefully
gracefully
in
in
your
your
UI.
ui
I'm
i'm
I'm
i'm
gonna
gonna
go
go
ahead
ahead
and
and
wrap
wrap
things
things
up
up
there.
there
If
if
you
you
have
have
any
any
questions,
questions
send
send
me
me
a
a
message
message
on
on
Slack.
slack
And
and
if
if
you're
you're
ready
ready
to
to
build
build
and
and
ship
ship
a
a
real
real
life
life
product,
product
consider
consider
becoming
becoming
a
a
pro
pro
member
member
at
at
angularfirebase.com.
angularfirebase.com
You'll
you'll
get
get
access
access
to
to
all
all
kinds
kinds
of
of
exclusive
exclusive
content
content
like
like
my
my
full
full
courses
courses
and
and
book
book
as
as
well
well
as
as
1
1
on
on
1
1
project
project
support.
support
Thanks
thanks
for
for
watching,
watching
and
and
I'll
i'll
talk
talk
to
to
you
you
soon.
soon
success
The speakers discuss the functionality of a delete button in apps and how to build a hold to delete button. They also explain how to update internal state in a component, set up a custom event, and generate a customer component. The process for deleting a customer and setting up a progress bar is discussed, along with information on how to handle requests for code changes and use the Tap operator to emit the number of milliseconds a user clicks on.
Angular Directives - Build a Hold-to-Delete Button
One design flaw that I see in a lot of apps is having a delete button with no option to undo or to confirm the deletion first. You could implement an undo button, but then you have some complex state management issues to worry about or you could trigger an alert message, but that's kind of annoying for your users. What I want to show you today is how to build a hold to delete button, which I think is a refreshing can take on how to prevent accidental deletion, and it's a great exercise for learning how to work with RxJS and Angular directives. If you're new to the channel, like and subscribe, and you can find the full source code on angularfirebase.com. The inspiration for this video comes from Josh on our Slack team who asked for a more elegant way to handle deletions than just showing a standard Bootstrap Confirm modal. I'm definitely not the first person to come up with this idea. There's a number of apps that have started using this functionality instead of alert messages or undo buttons. And the idea is pretty simple, we have a regular delete button, but the user needs to hold it for a certain amount of time before the actual deletion is executed. The way this works is we have an Angular directive that can attach itself to any DOM element or other component, then track the time difference between the mouse down event and the mouse up event. Then we can emit a custom event every 10th of a second so we can update our progress bar while the user is holding the button down. I'm just in a regular Angular app and I have AngularFire installed just so we have something meaningful to delete in an actual database, but this lesson's primarily focused on RxJS and Angular. The first thing we'll do is generate our directive by running ng generate directive holdable from the command line. We'll be able to attach this directive to a button and then keep track of its internal state and then emit events up to the parent component. To do that, we'll import host listener, event emitter, and output from Angular Core. Will also bring in a few things from RxJS that we'll see here in a minute. The first thing we'll do is set up a custom event and this is the data that we want to emit up to the parent component. This event will emit the number of milliseconds that a user has held the button down. So that allows the parent component to decide how long the user can hold the button before it fires off the delete operation to Firestore. In Angular, you can create public custom events by using the output decorator and making the value an EventEmitter. In in our case, we'll just be emitting the number of milliseconds. And then if we look at the HTML, it would be used like this. We insert the holdable directive in a button and then we wrap hold time in parentheses because it's an event, and later in the video, we'll define a function that handles that event. Let's go back to our directive and the next thing we'll define is the internal state of this component. We can define it as a subject, which itself is just an observable that we can push values to. And there are 2 possible states that this directive can be in, either the user's holding the button down or they're not. The next thing I'll do is define an observable called cancel, which will be based off of the state source subject. Eventually will set this observable up to only emit values when the user stops holding the button. But before we can do that, we need to first listen who mouse events on this element and then update the internal state. So there are 2 ways that a user can cancel holding the button. They can either stop clicking where they can move the mouse outside of the button area. We use host listener to listen to events like mouse up that are built into the DOM. And and if we want to listen to multiple events, we can just stack them on top of each other like this. Then we can write a single method to run the same code when either event occurs. When the user stops holding the button, we just want to update the internal state on this directive to be cancel. This is important because we'll actually react to the state change when we keep track of the amount of time that the user has been holding down. The next step is to add a host listener for the mouse down event. And the first thing I'm going to do here is add a console log that will console log that the hold is started in bright green. And just a quick side tip, you can console log with custom CSS by doing percent c on the first argument and make the second argument the actual styles you want applied there. The next thing I'll do is update the internal state to start, so we know the user has started a new hold. Then I'll define a number that defines the interval period. So this will be every 10th of a second or 100 milliseconds. You can adjust that as needed and then I'll set up an RxJS Interval with that number. When we subscribe to this interval, it will emit a number that increases by 1 every 10th of a second. 0, 1, 2, 3, etcetera. Now, the tricky part is that we want this observable to stop emitting values when the user has stopped holding it. And for that, we can actually use the TakeAndTell operator from RxJS then pass in that Cancel Observable. So when Cancel emits, it will cause this ObservableInterval to complete. But while the interval is running, we want to use the Tap operator to emit the custom event which is hold time emit. And then we'll emit the number of milliseconds I'm multiplying it again by that original interval number. So that will emit the number of milliseconds when the user clicks down, but it will keep running forever because right now we don't have any observable that will cancel it. We can create that observable by taking our source state subject and then piping in the filter operator and filter it so that only the cancel events are emitted. And then we'll also throw in a tap operator here and console log that the hold is stopped in bright red. And then we'll have our custom event emit the number 0 just so we can restart our progress bar when we get to the component. That takes care of the directive code. We can now go into the HTML and just drop that into a button and we'll get all of that custom functionality. If we click the button and hold for a second, you'll see that we first get our green console log, then we get view a 100 milliseconds and then it stops and you emit 0. It'll be the responsibility of the parent component to listen to these events. And then we'll set our demo up to delete the document from Firestore once it's been held for at least one second. We can go ahead and start in the HTML. We'll add the holdable div and then listen to our custom hold time event, and then we'll define a function here called Hold Handler that takes the event as its argument. If you want to see how many milliseconds the button has been held, you can just add a console log to the Hold Handler function. Then to make this non trivial, we'll bring in Firestore and load a collection of documents. Now I'm just going to copy and paste a pretty big chunk of code here. But basically what I'm doing is I'm grabbing a collection is Firestore. And then I'm mapping the document IDs to that collection's data payload so we can use that ID to later delete a document. I cover this code in detail in the full Ionic course as well as multiple other videos in the past. It's not critical for this video, so we'll just brush past it. The next important thing that we'll do is generate a component called customer which will hold the data for each document in Firestore. When we retrieve this collection from Firestore, we're going to loop over it with n g 4 and then we'll pass each unwrapped object to the child component. The App component is the parent component so we'll set up our loop there. We declare the App Customer component, run the ngFor loop. AngularFire gives us that as an observable, so we unwrap it with the async pipe. Then we'll pass each document object down to the child component by wrapping customer in square brackets. And we'll set up that custom input property in the component in the next step. Now before we write the child component, there's one last tip that I want to show you and that's if you're using a progress bar, you'll want to add an animation property in the CSS. Otherwise, it will look really janky as it animates from step to step. Now the child component is going to be pretty simple. We first bring in AngularFirestore as well as input from Angular Core. Then we'll decorate the Customer property with input, which allows it to be passed in through the parent component. And we'll also set up a Progress property that starts at 0. Now the most important aspect of this component is the delete customer method that handles the hold time event that we defined from our directive. This method will take the event itself as well as the customer object as an argument and it will update the progress on this component so we can change the value of the progress bar. When the button's being held down, it's going to emit this event every 10th of a second. We're dividing it by 10 to treat it more like a 0 to 100 percentage. And if that value is greater than 100 or one second in this case, we'll use AngularFirestore to delete that customer's document from the database. And then the last formality for this demo is just the HTML markup in the child component. So we'll just interpolate some values from the database. Then we'll add our button down here at the bottom, which of course uses the holdable directive, then uses the delete customer method as the event handler. And then we also have that progress property available on this component, which we can use to show a progress bar. The only trick we have to do here is add value in square brackets, so it uses the progress value that we define in the component. And now if we go back to our app, we should have a result that looks something like this. I have some dummy records in my database. If I click the delete button, it will start the progress bar, but if I let go then it stops. If I keep holding it for at least one second, then you'll see it actually deletes the record from the Firestore database. Hopefully, that gives you some ideas on how to handle deletes gracefully in your UI. I'm I'm gonna go ahead and wrap things up there. If you have any questions, send me a message on Slack. And if you're ready to build and ship a real life product, consider becoming a pro member at angularfirebase.com. You'll get access to all kinds of exclusive content like my full courses and book as well as 1 on 1 project support. Thanks for watching, and I'll talk to you soon.
The speakers discuss the functionality of a delete button in apps and how to build a hold to delete button. They also explain how to update internal state in a component, set up a custom event, and generate a customer component. The process for deleting a customer and setting up a progress bar is discussed, along with information on how to handle requests for code changes and use the Tap operator to emit the number of milliseconds a user clicks on.