Leveraging RXJS for real-time response to keyboard and mouse click combinations in web

I am new to RXJS and looking for a way to drag an HtmlElement when the user presses the space key and then drags the element with the mouse.

The dragging should be initiated by either pressing the SPACE key followed by a left click, or vice versa. The dragging will end when the SPACE key is released (keyup event) or when the mouse button is clicked (mouseup event), whichever happens first.

To implement this, I have created four observables for the specific events:

spaceDown$ = fromEvent<KeyboardEvent>(document, "keydown").pipe( filter(key => key.code === 'Space'))

spaceUp$ = fromEvent<KeyboardEvent>(document, "keyup").pipe( filter( key => key.code === 'Space'))

mouseDown$ = fromEvent<MouseEvent>(document, "mousedown").pipe( filter( mouse => mouse.button == 0))

mouseUp$ = fromEvent<MouseEvent>(document, "mouseup").pipe( filter( mouse => mouse.button == 0))

I have tried using operators like 'combineLatest', 'withLatestFrom', 'merge' on these streams but have not been able to achieve the desired behavior yet. Any suggestions on how to accomplish this?

Answer №1

To handle drag events, I suggest mapping the source observables to a boolean value, where true represents pressed. Create combined observables for both keyboard and mouse inputs, then merge those two streams. When both values are true, it indicates a drag start event; otherwise, it's a drag end event.

const spaceDown$ = fromEvent<KeyboardEvent>(document, 'keydown').pipe(
  filter((key) => key.code === 'Space'),
  map(() => true)
);

const spaceUp$ = fromEvent<KeyboardEvent>(document, 'keyup').pipe(
  filter((key) => key.code === 'Space'),
  map(() => false)
);

const mouseDown$ = fromEvent<MouseEvent>(document, 'mousedown').pipe(
  filter((mouse) => mouse.button == 0),
  map(() => true)
);

const mouseUp$ = fromEvent<MouseEvent>(document, 'mouseup').pipe(
  filter((mouse) => mouse.button == 0),
  map(() => false)
);

const key$ = merge(spaceDown$, spaceUp$).pipe(startWith(false));
const mouse$ = merge(mouseDown$, mouseUp$).pipe(startWith(false));

const combined$ = combineLatest([
  key$,
  mouse$,
]).pipe(
  map(([key, mouse]) => key && mouse),
  distinctUntilChanged(),
);

combined$.subscribe((res) => console.log('combined$', res));

// Separate streams for dragstart and dragend:

const [dragStart$, dragEnd$] = partition(combined$, (val) => val);

dragStart$.subscribe(() => console.log('dragStart$'));
dragEnd$.subscribe(() => console.log('dragEnd$'));

Answer №2

There are multiple approaches to achieving this, and here are two options:

Option 1: The "dummy" approach

In this solution, behavior subjects are used to control movement by filtering when to listen.

If you are not familiar with operators, this approach is easy to understand and works effectively.

import {
  BehaviorSubject,
  combineLatest,
  filter,
  fromEvent,
  map,
  Observable,
  switchMap,
  throttleTime,
} from 'rxjs';

// Code implementation omitted for brevity

moving$.pipe(throttleTime(100)).subscribe(() => console.log('dragging'));

Option 2: The "advanced" approach

This option may be more challenging to comprehend but aligns with your requirements.

The process involves creating a "starting" listener using `combineLatest` and a "stopping" condition using `race` operator.

Unlike combineLatest, race emits when any of its members emit, and the behavior repeats accordingly.

Finally, the behavior switches to the desired observable (mouse moving) until the stopping condition is met, repeating the process as needed.

import {
  combineLatest,
  filter,
  first,
  fromEvent,
  Observable,
  race,
  repeat,
  switchMap,
  takeUntil,
  throttleTime,
} from 'rxjs';

// Code implementation omitted for brevity

moving$.pipe(throttleTime(100)).subscribe(() => console.log('dragging'));

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The issue with ngModel in Angular is that it fails to update the value within the component

My ngModel doesn't seem to be functioning properly when I use it with a textbox in my application. This is the code snippet from app.component.html: <input type="text" [value]="name" [ngModel]="name"> Name is: {{na ...

Struggling to utilize a custom react-three-fiber component despite specifying the custom type within the react-three-fiber module

Currently developing a react application focused on visualizing mathematical concepts with the help of react-three-fiber. Utilizing TypeScript, I discovered that by extending custom ThreeElements (https://docs.pmnd.rs/react-three-fiber/tutorials/typescript ...

Angular requiring me to configure polyfills due to the updates in Webpack 5

While working on my code, I executed ng serve and encountered an error related to polyfills: BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default. This is no longer the case. Verify if you need this module and confi ...

When adding additional items in Angular's mat-select with multiselect, previously selected options will be reset

I am encountering an issue with a mat-select in my Angular application that has multiselect enabled. Within the mat-select, there is an input used for filtering available options. The problem arises when I select some options, filter using the search inp ...

Troubleshooting the malfunction of the Angular 2 Tour of Heroes project following the separation of the app

Recently, I encountered a challenge while following a tutorial on learning Angular 2. Everything was going smoothly until I reached the point where I had to divide appcomponent into heroescomponent & appcomponent. Is there anyone else who has faced th ...

How can I apply concatMap in Angular?

Can you please guide me on how to effectively utilize concatMap with getPrices() and getDetails()? export class HistoricalPricesComponent implements OnInit, OnDestroy { private unsubscribe$ = new Subject < void > (); infoTitle ...

How to Nest Interfaces in TypeScript

I'm just starting to learn about Angular and I am having trouble getting the interface class to work properly. export interface Header { parentTitles: string; childHeaders: ChildHeader[]; titleIcon: string; url: string; } export interf ...

Angular/NX - Element 'Component' is unrecognized, despite being imported within the module

Encountering the following issue: 'apocaloot-building' is showing as an unknown element: If 'apocaloot-building' is supposed to be an Angular component, please ensure that it is included in this module. The component in question is ...

Issue encountered when upgrading from Angular 4 to Angular 5: Module '@angular/router' is not found

I recently made the switch from angular 2 to angular 5. However, after the upgrade, I encountered some errors in my ts file. Should I remove @angular/core and @angular/router in angular 5? Here is a snippet of my package.json post-upgrade. Below are the de ...

Increase the ngClass attribute's value

Is there a way to automatically increment a numeric value in a class using the ngClass directive? For example, can we achieve something like this: <some-element [ngClass]="'class-*'">...</some-element>, where the asterisk (*) will in ...

io-ts: Defining mandatory and optional keys within an object using a literal union

I am currently in the process of defining a new codec using io-ts. Once completed, I want the structure to resemble the following: type General = unknown; type SupportedEnv = 'required' | 'optional' type Supported = { required: Gene ...

Implementing Observable lambdas in Angular templates for easy function calling

Within a component, I have the following code snippet: public hasFoo$: Observable<(name: string) => boolean> = ... Now, I would like to use this multiple times in my template with a stepper: <mat-vertical-stepper> <mat-step *ngIf="ha ...

How can I choose an option in a dropdown field labeled as ejs-dropdownlist with WebDriver in an Angular web application?

After spending some time grappling with this issue, I managed to find a solution. The ejs-dropdownlist tag is present on a web page that is currently in the development stage using Angular. Here is the complete XPath for the dropdown I am attempting to i ...

Having difficulty authenticating a JWT token in my Nextjs application

Help required with verifying a JWT token in Nextjs as I'm encountering the following error: TypeError: Right-hand side of 'instanceof' is not an object See below for the code I am currently using: useEffect(() => { let token = localS ...

Updating the Edit icon within the Mat table is not being properly refreshed when editing inline text

I am currently working on implementing inline editing for a mat table. I found a helpful guide at this link. When I try to edit the second row on page 1 (the edit symbol icon changes), and then move to the next page without saving the changes, the second r ...

Is it possible to load a script conditionally in Angular 2 using the Angular CLI

Is it possible to conditionally load scripts using Angular CLI? I am looking to dynamically load a script in this file based on the environment or a variable. For example, I want to load a specific script in production but not in development. How can I ac ...

Tips for converting text input in a textbox to a Date value

My knowledge of Angular is limited, and we are currently using Angular 10. In our application, there is a textbox where users need to input a date in the format 10202020. This value should then be reformatted as 10/20/2020 and displayed back in the same ...

What is the best way to line up changing column values with changing row values in an HTML table?

In its current state, the data appears as follows without alignment: The data columns are housed within a single variable due to the presence of multiple excel tabs with varying columns. Within the tr tag, rows are checked to match specific columns. The ...

Issues with exporting function and interface have been identified

When exporting a function and type from the library in the convertToUpper.ts file, I have the following code: export function Sample() { console.log('sample') } export type IProp = { name: string age: number } The index.ts file in my lib ...

Using Typescript: Utilizing only specific fields of an object while preserving the original object

I have a straightforward function that works with an array of objects. The function specifically targets the status field and disregards all other fields within the objects. export const filterActiveAccounts = ({ accounts, }: { accounts: Array<{ sta ...