From 60ccb3d9cf1898c3a4efa13e606b9b1ee3338f5c Mon Sep 17 00:00:00 2001 From: Michal Grzegorczyk Date: Tue, 8 Apr 2025 00:24:02 +0200 Subject: [PATCH] feat(49): finished --- .../src/app/app.component.ts | 36 ++++++++-- .../src/app/holdable.ts | 67 +++++++++++++++++++ 2 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 apps/rxjs/49-hold-to-save-button/src/app/holdable.ts 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..1c2fb9285 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,25 +1,51 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; +import { HoldableDirective } from './holdable'; @Component({ - imports: [], + imports: [HoldableDirective], selector: 'app-root', template: `
- + + + @if (finished()) { +

Finished!

+ }
`, changeDetection: ChangeDetectionStrategy.OnPush, }) export class AppComponent { - onSend() { - console.log('Save it!'); + protected readonly currentValue = signal(0); + protected readonly finished = signal(false); + + protected readonly maxValue = 10; + + onHold(val: number): void { + this.currentValue.set(val); + } + + onHoldFinished(): void { + this.finished.set(true); + this.currentValue.set(this.maxValue); + } + + onHoldCancelled() { + this.currentValue.set(0); + this.finished.set(false); } } diff --git a/apps/rxjs/49-hold-to-save-button/src/app/holdable.ts b/apps/rxjs/49-hold-to-save-button/src/app/holdable.ts new file mode 100644 index 000000000..139b382ac --- /dev/null +++ b/apps/rxjs/49-hold-to-save-button/src/app/holdable.ts @@ -0,0 +1,67 @@ +import { + DestroyRef, + Directive, + ElementRef, + inject, + input, + output, +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { fromEvent, interval, merge, takeUntil } from 'rxjs'; + +@Directive({ + selector: '[appHoldable]', + standalone: true, +}) +export class HoldableDirective { + holdInterval = input(500); + startValue = input(0); + endValue = input(100); + + readonly hold = output(); + readonly finished = output(); + readonly cancelled = output(); + + private readonly elRef = inject(ElementRef); + private readonly destroyRef = inject(DestroyRef); + + constructor() { + const el = this.elRef.nativeElement; + + const mousedown$ = fromEvent(el, 'mousedown', { passive: true }); + const touchstart$ = fromEvent(el, 'touchstart', { passive: true }); + const start$ = merge(mousedown$, touchstart$); + + const mouseup$ = fromEvent(el, 'mouseup', { passive: true }); + const mouseleave$ = fromEvent(el, 'mouseleave', { passive: true }); + const touchend$ = fromEvent(el, 'touchend', { passive: true }); + const stop$ = merge(mouseup$, mouseleave$, touchend$); + + start$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => { + if (this.startValue() >= this.endValue()) return; + + let value = this.startValue(); + let completed = false; + + const sub = interval(this.holdInterval()) + .pipe(takeUntil(stop$), takeUntilDestroyed(this.destroyRef)) + .subscribe({ + next: () => { + if (value >= this.endValue()) { + completed = true; + this.finished.emit(); + sub.unsubscribe(); + } else { + value++; + this.hold.emit(value); + } + }, + complete: () => { + if (!completed) { + this.cancelled.emit(); + } + }, + }); + }); + } +}