admin管理员组

文章数量:1327667

I have a problem with RxJs.

I need to log a message in console.log when I click once, and different message when I click twice on the button. The problems are:

  • at the beginning if I click first time - nothing happens - wrong (I should see first message 'one click')

  • then if I click on the button and then (after one second) I click again, for the second click I can see two messages - wrong (I should see only one message per action)

  • when I click on the button, wait a second, click again, wait a second and click again, then for the last click I will see three messages in console.log - wrong (I should see only one message per action)
  • after that if I click twice (doubleclick) I will see five messages for double click, and one for one click - wrong (I should see only one message 'double click')

all I want is:

  • if I click once, I need to see only one message ('one click')
  • if I click twice (doubleclick), still I need to see only one message ('double click')
  • if I click more than twice - nothing happens

any help?

check hear for examples

I have a problem with RxJs.

I need to log a message in console.log when I click once, and different message when I click twice on the button. The problems are:

  • at the beginning if I click first time - nothing happens - wrong (I should see first message 'one click')

  • then if I click on the button and then (after one second) I click again, for the second click I can see two messages - wrong (I should see only one message per action)

  • when I click on the button, wait a second, click again, wait a second and click again, then for the last click I will see three messages in console.log - wrong (I should see only one message per action)
  • after that if I click twice (doubleclick) I will see five messages for double click, and one for one click - wrong (I should see only one message 'double click')

all I want is:

  • if I click once, I need to see only one message ('one click')
  • if I click twice (doubleclick), still I need to see only one message ('double click')
  • if I click more than twice - nothing happens

any help?

check hear for examples

Share Improve this question edited Sep 6, 2018 at 3:59 Dulanga Heshan 1,4171 gold badge22 silver badges38 bronze badges asked Sep 5, 2018 at 20:45 PrzemoPrzemo 5381 gold badge8 silver badges22 bronze badges 5
  • 2 please put your code in your question as well – bryan60 Commented Sep 5, 2018 at 20:47
  • yeah I forget, thx – Przemo Commented Sep 5, 2018 at 20:48
  • have you tried angulars (dblclick) binding instead of a plex observable? – bryan60 Commented Sep 5, 2018 at 20:50
  • of course, but I need another solution for ios where dblclick is not working properly – Przemo Commented Sep 5, 2018 at 20:52
  • 1 FYI, the reason you're getting multiples of each message is because you are resubscribing to the observable every time the test() function is called. You should unsubscribe after the observable subscription callback fires. Which begs the question, why are you using observables in the first place? Seems like a regular click handler should do, yeah? – mhodges Commented Sep 5, 2018 at 21:24
Add a ment  | 

3 Answers 3

Reset to default 4

A very simple answer (not using any observables) is to use a setTimeout() and check on each click if the timeout is already set, if so, you know it's a second click within a given time window (double click) and if not, it's the first click. If the timeout expires, you know it was just a single click, like so:

Updated StackBlitz

// have a timer that will persist between function calls
private clickTimeout = null;
public test(event): void {
  // if timeout exists, we know it's a second click within the timeout duration
  // AKA double click
  if (this.clickTimeout) {
    // first, clear the timeout to stop it from pleting
    clearTimeout(this.clickTimeout);
    // set to null to reset
    this.clickTimeout = null;
    // do whatever you want on double click
    console.log("double!");
  } else {
  // if timeout doesn't exist, we know it's first click
    this.clickTimeout = setTimeout(() => {
      // if timeout expires, we know second click was not handled within time window
      // so we can treat it as a single click
      // first, reset the timeout
      this.clickTimeout = null;
      // do whatever you want on single click
      console.log("one click");
    }, 400);
  }
}

EDIT

I missed the part about ignoring any more than 2 clicks. It's not that much more work, but I broke it out a bit more to be able to reuse code, so it looks like a lot more. Anyway, to ignore 3+ clicks, it would look like the following:

// count the clicks
private clicks = 0;
private clickTimeout = null;
public test(event): void {
  this.clicks++;
  if (this.clickTimeout) {
    // if is double click, set the timeout to handle double click
    if (this.clicks <= 2) {
      this.setClickTimeout(this.handleDoubleClick);
    } else {
    // otherwise, we are at 3+ clicks, use an empty callback to essentially do a "no op" when pleted
      this.setClickTimeout(() => {});
    }
  } else {
    // if timeout doesn't exist, we know it's first click - treat as single click until further notice
    this.setClickTimeout(this.handleSingleClick);
  }
}
// sets the click timeout and takes a callback for what operations you want to plete when the
// click timeout pletes
public setClickTimeout(cb) {
  // clear any existing timeout
  clearTimeout(this.clickTimeout);
  this.clickTimeout = setTimeout(() => {
    this.clickTimeout = null;
    this.clicks = 0;
    cb();
  }, 400);
}
public handleSingleClick() {
  console.log("one click");
}
public handleDoubleClick() {
  console.log("double!");
}

@Siddharth Ajmeras answer shows how to do this with events. I was not aware a dblclick event existed. The more you know. If you are still interested on how to do this with rxjs, here is an example.

// How fast does the user has to click
// so that it counts as double click
const doubleClickDuration = 100;

// Create a stream out of the mouse click event.
const leftClick$ = fromEvent(window, 'click')
// We are only interested in left clicks, so we filter the result down
  .pipe(filter((event: any) => event.button === 0));

// We have two things to consider in order to detect single or
// or double clicks.

// 1. We debounce the event. The event will only be forwared 
// once enough time has passed to be sure we only have a single click
const debounce$ = leftClick$
  .pipe(debounceTime(doubleClickDuration));

// 2. We also want to abort once two clicks have e in.
const clickLimit$ = leftClick$
  .pipe(
    bufferCount(2),
  );


// Now we bine those two. The gate will emit once we have 
// either waited enough to be sure its a single click or
// two clicks have passed throug
const bufferGate$ = race(debounce$, clickLimit$)
  .pipe(
    // We are only interested in the first event. After that
    // we want to restart.
    first(),
    repeat(),
  );

// Now we can buffer the original click stream until our
// buffer gate triggers.
leftClick$
  .pipe(
    buffer(bufferGate$),
    // Here we map the buffered events into the length of the buffer
    // If the user clicked once, the buffer is 1. If he clicked twice it is 2
    map(clicks => clicks.length),
  ).subscribe(clicks => console.log('CLicks', clicks));

This is either what you want or very close to it:

import { Component, ViewChild, ElementRef } from '@angular/core';
import { fromEvent } from 'rxjs';

@Component({
  selector: 'my-app',
  templateUrl: './app.ponent.html',
  styleUrls: [ './app.ponent.css' ]
})

export class AppComponent  {

  @ViewChild('mybutton') button: ElementRef;

  ngAfterViewInit() {
    fromEvent(this.button.nativeElement, 'dblclick')
    .subscribe(e => console.log('double click'));

    fromEvent(this.button.nativeElement, 'click')
    .subscribe((e: MouseEvent) => {
      if (e.detail === 1) console.log('one click') // could use a filter inside a pipe instead of using an if statement
      // if (e.detail === 2) console.log('double click') // an alternative for handling double clicks
    });
  }
}

and the HTML:

<button #mybutton>Test</button>

This solution uses event.detail and is loosely based on this useful post - Prevent click event from firing when dblclick event fires - which not only discusses event.detail but also looks at the timing issues involved.

One of the big probs with your code is that you are subscribing to the event inside a function that is called multiple times, which means each time the button is clicked you create another subscription. Using ngAfterViewInit (which is only called once as part of the lifecycle) prevents this issue (and ensures the DOM is loaded).

Here's a stackblitz for you:

https://stackblitz./edit/angular-t6cvjj

Forgive me for my sloppy type declarations!

This satisfies your requirements as follows:

  • If I click once, I need to see only one message ('one click') - PASS
  • If I click twice (doubleclick), still I need to see only one message ('double click') - FAIL, you see 'one click' on the 1st click and 'double click' on the 2nd click
  • If I click more than twice - nothing happens - PASS

Due to the timing issues discussed in the SO post given, how you solve this is a matter of preference and thus I decided not to tackle it. And plus, you are not paying me ;) This answer however should set you well on your way to solving it fully.

PS There is a ment on the SO post above that suggests event.detail may not work on IE11

本文标签: javascriptSeparate action for single click and double click with RxJsStack Overflow