diff --git a/apps/rxjs/49-hold-to-save-button/src/app/app.component.ts b/apps/rxjs/49-hold-to-save-button/src/app/app.component.ts
index 8f0dbbc70..6bd8ae3fe 100644
--- a/apps/rxjs/49-hold-to-save-button/src/app/app.component.ts
+++ b/apps/rxjs/49-hold-to-save-button/src/app/app.component.ts
@@ -1,24 +1,31 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
+import { HoldableDirective } from './holdable.directive';
@Component({
- imports: [],
+ imports: [HoldableDirective],
selector: 'app-root',
template: `
-
+
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
+ progress = signal(0);
+
onSend() {
console.log('Save it!');
}
diff --git a/apps/rxjs/49-hold-to-save-button/src/app/holdable.directive.ts b/apps/rxjs/49-hold-to-save-button/src/app/holdable.directive.ts
new file mode 100644
index 000000000..7eccf3b4e
--- /dev/null
+++ b/apps/rxjs/49-hold-to-save-button/src/app/holdable.directive.ts
@@ -0,0 +1,63 @@
+import {
+ Directive,
+ ElementRef,
+ inject,
+ input,
+ OnDestroy,
+ OnInit,
+ output,
+} from '@angular/core';
+import {
+ filter,
+ fromEvent,
+ interval,
+ map,
+ merge,
+ Subscription,
+ switchMap,
+ take,
+ takeUntil,
+ tap,
+} from 'rxjs';
+
+@Directive({ selector: '[holdable]' })
+export class HoldableDirective implements OnInit, OnDestroy {
+ duration = input.required();
+ complete = output();
+ progressUpdated = output();
+
+ private elementRef = inject(ElementRef);
+ private sub: Subscription | null = null;
+
+ ngOnInit() {
+ const element = this.elementRef.nativeElement as HTMLElement;
+ const resetEvents$ = merge(
+ fromEvent(element, 'mouseup'),
+ fromEvent(element, 'mouseleave'),
+ );
+
+ this.sub = fromEvent(element, 'mousedown')
+ .pipe(
+ switchMap(() => {
+ return interval(10).pipe(
+ takeUntil(resetEvents$),
+ map((value) => (value + 1) * 10),
+ tap((value) => {
+ const progress = Math.round((value / this.duration()) * 100);
+ this.progressUpdated.emit(progress);
+ }),
+ filter((value) => value >= this.duration()),
+ take(1),
+ tap(() => {
+ this.complete.emit();
+ }),
+ );
+ }),
+ )
+ .subscribe();
+ }
+
+ ngOnDestroy() {
+ this.sub?.unsubscribe();
+ }
+}