I am trying to implement show/hide functionality using Rxjs in Angular.
I have multiple elements on which I can hover (arrows). After hovering over an arrow I am displaying div.
I need hover with some delay but this can be done with debounceTime and this is not a problem
The problem I have is a global timer which I need.
- When the user hovers over the
arrowI should showdivelement after 500ms for example - When the user moves the mouse outside the
arrow, the timer should start and count 5s and after 5s I should hide thedivelement - If the user moves the mouse on
divwhich was shown during that 5s, the timer should stop and reset - When the user moves the mouse outside
divtimer should start again.
I tried:
countdownSeconds = 5;
interval$ = interval(1000).pipe(mapTo(-1));
arrowHover$: Observable<any>;
arrowUnhover$: Observable<any>;
onMouseEnter() {
this.arrowHover$ = from(EMPTY).pipe(mapTo(false));
}
onMouseLeave() {
this.arrowUnhover$ = from(EMPTY).pipe(mapTo(true));
this.setTimer();
}
setTimer() {
this.timer$ = merge(this.arrowHover$, this.arrowUnhover$)
.pipe(
startWith(this.interval$),
switchMap(val => (val ? this.interval$ : empty())),
scan((acc, curr) => (curr ? curr acc : acc), this.countdownSeconds),
takeWhile(v => v >= 0)
)
.subscribe(val => console.log(val));
}
And when I move the mouse outside arrow timer is starting but how to stop it and reset its value? I have no idea what to do next or how to change it to work as expected.
CodePudding user response:
If I understand correctly this is what you are looking for:
countDownMs = 5000; // in milliseconds
arrowHover$ = new Subject();
isDivShowing = false;
isOnDiv = false;
ngOnInit(){
arrowHover$.pipe(
debounceTime(this.countDownMs),
filter(() => !this.isDivShowing || !this.isOnDiv),
).subscribe((hover) => {
console.log(`${hover ? 'show div' : 'hide div'}`);
this.isDivShowing = hover;
});
}
onEnterDiv(){
this.isOnDiv = true;
};
onLeaveDiv(){
this.isOnDiv = false;
this.arrowHover$.next(false);
};
onMouseEnter(){
this.arrowHover$.next(true);
};
onMouseLeft(){
this.arrowHover$.next(false);
};
Example on stackblitz
CodePudding user response:
Whenever there are things set in motion but possibly reset or overridden by future events, switchMap and takeUntil are operators to keep in mind.
Below is untested because I am responding via mobile phone, but here's the general idea:
const myDiv = document.getElementById('myDiv');
const enter$ = fromEvent(myDiv, 'onMouseEnter').pipe(
mapTo(true)
);
const exit$ = fromEvent(myDiv, 'onMouseExit').pipe(
mapTo(false)
);
const destroy$ = NEVER; // Replace this with something that emits once when it should all be recycled, then completes. ...or get rid of this.
let isDisplayed = false;
merge(enter$, exit$).pipe(
switchMap(isEntering => isEntering === isDisplayed ? EMPTY // Don't emit anything, but cancel whatever might have been brewing from the previous switch map evaluation
: of(isEntering).pipe(
delay(isEntering ? 500 : 5000)
)
),
takeUntil(destroy$)
).subscribe({
next: isEntering => {
isDisplayed = isEntering;
myDiv.style.display = isEntering ? 'block' : 'none';
}
});
