MrMos
MrMos

Reputation: 21

RxJS6: reset stream after completion

I've been playing around a bit with RxJS 6. I am trying to implement dragstart, dragmove and dragend. This is the code I have so far.

import { fromEvent } from 'rxjs';
import { concatMap } from 'rxjs/internal/operators/concatMap';
import { map } from 'rxjs/internal/operators/map';
import { takeUntil } from 'rxjs/internal/operators/takeUntil';
import { repeat } from 'rxjs/internal/operators/repeat';
import { first } from 'rxjs/internal/operators/first';

const mousedown = fromEvent<MouseEvent>(window, 'mousedown');
const mousemove = fromEvent<MouseEvent>(window, 'mousemove');
const mouseup = fromEvent<MouseEvent>(window, 'mouseup');

const dragstart = mousedown.pipe(
  first()
);

const dragmove = mousedown.pipe(
  concatMap((dragStartEvent) => mousemove.pipe(
    takeUntil(mouseup))
  )
);

const dragend = mousedown.pipe(
  (dragEvent) => mouseup.pipe(first())
);

const log = (prefix: string) => (data: MouseEvent) => console.log(`${prefix}: x: ${data.clientX}, y:${data.clientY}`);

dragstart.subscribe(log('dragstart'));
dragmove.subscribe(log('dragmove'));
dragend.subscribe(log('dragend'));

The problem is that operators takeUntil and first mark the stream as completed. This means that dragstart and dragend will only fire once. Is there a way to somehow reset the stream after this event happens? For example by resetting the dragstart stream when the mouseup stream receives an event.


Additional explanation

In the current implementation dragstart and dragend will be logged exactly once to the console after loading the page (& dragging). After that the stream is completed and no further events will be sent through it. I want to reset the stream every drag operation so the dragstart & dragend will work every drag operation rather than just the very first after loading the page.

Current behavior ([] represent mouse action that happened, italic text is console output):

First drag operation

Second drag operation

Upvotes: 2

Views: 526

Answers (2)

LookForAngular
LookForAngular

Reputation: 1070

But you told to do so :)

If you remove the pipe and "first" operator from the dragstart stream

const dragstart = mousedown

the stream wont complete. The dragmove wont complete anyway because it is mapped from the mousedown, not the dragstart stream

If you tell to the resulting stream to pick only the first one, the stream will complete after emitting once.

This code should work:

const mousedown = fromEvent<MouseEvent>(window, 'mousedown');
const mousemove = fromEvent<MouseEvent>(window, 'mousemove');
const mouseup = fromEvent<MouseEvent>(window, 'mouseup');

// I dont think it's necessary to duplicate
const dragstart = mousedown

// No need to map to something else
const dragend = mouseup

const dragmove = dragstart.pipe(
  concatMapTo( 
    mousemove.pipe(
        takeUntil( dragend )
    )
  )
);



const log = (prefix: string) => (data: MouseEvent) => console.log(`${prefix}: x: ${data.clientX}, y:${data.clientY}`);

dragstart.subscribe(log('dragstart'));
dragmove.subscribe(log('dragmove'));
dragend.subscribe(log('dragend'));

Upvotes: 0

sylwester
sylwester

Reputation: 16498

Please see here for working example https://codepen.io/anon/pen/bMWjEVenter link description here

const { fromEvent } = Rx.Observable;
const target = document.querySelector('.box');

const mouseup = fromEvent(target, 'mouseup');
const mousemove = fromEvent(document, 'mousemove');
const mousedown = fromEvent(target, 'mousedown');
let log = (prefix: string, x:number, y:number) => console.log(`${prefix}: x: ${x}, y:${y}`);
const mousedrag = mousedown.selectMany((md) => {

  const startX = md.clientX + window.scrollX,
        startY = md.clientY + window.scrollY,
        startLeft = parseInt(md.target.style.left, 10) || 0,
        startTop = parseInt(md.target.style.top, 10) || 0;
  
  return mousemove.map((mm) => {
    mm.preventDefault();
   log('mousemove',mm.clientX, mm.clientY);
    return {
      left: startLeft + mm.clientX - startX,
      top: startTop + mm.clientY - startY
    };
  }).takeUntil(mouseup);
});

subscription = mousedrag.subscribe((pos) => {
  log('dragstart',pos.top, pos.left)
  target.style.top = pos.top + 'px';
  target.style.left = pos.left + 'px';
});
.box {
  position: relative;
  width: 100px;
  height: 100px;
  background: red;
  cursor: pointer;
  border:solid 10px green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.2/rx.all.js"></script>
<div class="box"></div>

Upvotes: 0

Related Questions