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 'fullDocument' property is not present in the 'ChangeStreamDropDocument' type

Upon cloning an express TypeScript project, I encountered a Typescript error within a Mongo related function as mentioned in the title. Property 'fullDocument' does not exist on type 'ChangeStreamDocument<IUser>'. Property &apos ...

Experiencing problems with the Locale setting when utilizing the formatNumber function in Angular's core functionalities

I am having trouble formatting a number in Angular using the formatNumber function from the Angular documentation. Here is my code snippet: import {formatNumber} from '@angular/common'; var testNumber = 123456.23; var x = formatNumber(Numb ...

The RxJS observable fails to initiate the subscribe function following the mergeMap operation

I am attempting to organize my dataset in my Angular application using the RxJS operators and split it into multiple streams. However, I am facing difficulties making this work properly. Inside my SignalRService, I have set up a SignalR trigger in the cons ...

Utilize Angular 5 to implement URL routing by clicking a button, while also preserving the querystring parameters within the URL

I have a link that looks like this http://localhost:4200/cr/hearings?UserID=61644&AppID=15&AppGroupID=118&SelectedCaseID=13585783&SelectedRoleID=0 The router module is set up to display content based on the above URL structure { path: & ...

deliver a promise with a function's value

I have created a function to convert a file to base64 for displaying the file. ConvertFileToAddress(event): string { let localAddress: any; const reader = new FileReader(); reader.readAsDataURL(event.target['files'][0]); reader ...

Can someone assist me with writing nested ngFor loops in Angular?

Struggling to implement nested ngFor loops in Angular where each question has 3 radio button answers. Need guidance on how to keep the selected answer for each question isolated. Current code snippet: <ng-container *ngFor="let daType of daTypes&qu ...

Error encountered during ng update process: The workspace root directory is unable to resolve the "@angular-devkit/schematics" package

ng update The error message stating that the "@angular-devkit/schematics" package cannot be found in the workspace root directory suggests a possible issue with the node modules structure. To address this, you can start by deleting bo ...

Exploring resources within a library in Angular

I need help accessing assets from a shared library within my nx workspace. Here is the structure: /apps -- my-app // ... /libs -- shared -- assets -- resources -- translation.json The shared lib has an alias defined as @my-company/s ...

retrieving information and parsing from an API using Ionic and Angular version 4

How can I extract data from the "data" field in my API using a function? getMenu() { return this.http.get('http://site.dev/api/menu/7'); } { "id": 26, "name": "Default", "title": "default", "pageelements": [ { "id": 15, ...

Having Trouble with Typescript Modules? Module Not Found Error Arising Due to Source Location Mismatch?

I have recently developed and released a Typescript package, serving as an SDK for my API. This was a new endeavor for me, and I heavily relied on third-party tools to assist in this process. However, upon installation from NPM, the package does not functi ...

I am integrating and connecting my angular2 library with my primary project by placing it in the node_modules folder within the library's build directory. This process is expected to

I have developed an Angular2 component as a library and I am connecting this library to my main project. After building my library, a build folder is created. However, when I run npm-link inside the build folder, it generates a node_modules folder with al ...

Generating PDF files from HTML using Angular 6

I am trying to export a PDF from an HTML in Angular 6 using the jspdf library. However, I am facing limitations when it comes to styling such as color and background color. Is there any other free library besides jspdf that I can use to achieve this? Feel ...

Creating a Custom FlatList Content Container with React Native

Is it possible to customize FlatList items with a custom component? I want to create a setup where my FlatList items are encapsulated within a custom component similar to the following: <ScrollView pt={8} px={16} pb={128} > <Card e ...

Struggling to display my array data retrieved from the database on my Angular 5 page

I hope everyone is doing well. I am currently facing a problem with retrieving data from Firebase. I have an array within an array and I want to display it in my view, but I am encountering difficulties. Let me share my code and explain what I am trying to ...

What is the TypeScript syntax for defining a component that does not require props to be passed when called?

Can you provide guidance on the correct type to specify for Component in order to compile this example without any type errors? import { memo } from "react"; import * as React from "react"; export function CustomComponent( props: ...

Does @angular/router have a similar function to ui-router's transition.from()?

I am currently in the process of updating an Angular application from ui-router to @angular/router (v6). There is a specific function that aims to retrieve the previous route. Below is the code snippet utilizing Transition from ui-router/core: const ...

The async pipe value seems to be constantly null when dealing with router events

I am facing a straightforward problem while attempting to access an asynchronous property in my template - the returned value is consistently null. This is the method I am using: someAsyncProperty():Observable<string> { return this._router.event ...

Nesting two ngFor loops in Angular using the async pipe leads to consistent reloading of data

In my Angular application, I am trying to retrieve a list of parent elements and then for each parent, fetch its corresponding children (1 parent to n children). Parent Child1 Child2 Parent Child1 Parent3 Child1 Child2 Child3 Initially, I succes ...

Creating an array object in TypeScript is a straightforward process

Working on an Angular 4 project, I am attempting to declare an attribute in a component class that is an object containing multiple arrays, structured like this: history: { Movies: Array<Media>, Images: Array<Media>, Music: Array<Medi ...

Sending a POST request in Angular 7 with custom headers and request body

I have been attempting to make a post request with body and header. Despite trying various methods, I keep encountering an error on the server indicating that the parameter 'key' was not passed in. When testing the API in Postman, it works witho ...