Отдельное действие для одного клика и двойного клика с RxJ - PullRequest
0 голосов
/ 05 сентября 2018

У меня проблема с RxJs.

Мне нужно записать сообщение в console.log, когда я нажимаю один раз, и другое сообщение, когда я дважды нажимаю на кнопку. Проблемы:

  • в начале, если я нажму первый раз - ничего не происходит - неправильно (Я должен увидеть первое сообщение «в один клик»)

  • затем, если я нажимаю на кнопку, а затем (через одну секунду) я нажимаю снова, для второго щелчка я вижу два сообщения - неправильно (я должен видеть только одно сообщение за действие)

  • когда я нажимаю на кнопку, подожди секунду, нажми еще раз, подожди секунду и нажми снова, затем для последнего щелчка я увижу три сообщения в console.log - неправильно (я должен видеть только одно сообщение за действие)
  • после этого, если я нажму дважды (двойной щелчок), я увижу пять сообщений для двойного щелчка и одно для одного щелчка - неправильно (я должен видеть только одно сообщение «двойной щелчок»)

все, что я хочу, это:

  • если я нажму один раз, мне нужно увидеть только одно сообщение («один клик»)
  • если я нажму дважды (двойной щелчок), все равно мне нужно увидеть только одно сообщение («двойной щелчок»)
  • если я нажму более двух раз - ничего не произойдет

любая помощь?

проверить слух для примеров

Ответы [ 4 ]

0 голосов
/ 06 сентября 2018

@ Siddharth Ajmeras ответ показывает, как это сделать с событиями. Я не знал о существовании события dblclick. Чем больше ты знаешь. Если вам все еще интересно, как это сделать с помощью rxjs, вот пример.

// 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 come in.
const clickLimit$ = leftClick$
  .pipe(
    bufferCount(2),
  );


// Now we combine 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));
0 голосов
/ 06 сентября 2018

Вы можете создать директиву, которая прослушивает dblclick, а затем выполнять все необходимые действия в директиве.

import { Directive, HostListener } from '@angular/core';

@Directive({
  selector: '[appDoubleClick]'
})
export class DoubleClickDirective {
  constructor() { }

  @HostListener('dblclick') onDoubleClicked() {
    console.log('onDoubleClicked ran!');
  }

}

Шаблон:

<button appDoubleClick (click)="test($event)">Test</button>

ОБНОВЛЕННЫЙ STACKBLITZ

Не уверен, что это будет работать в iOS. Но, пожалуйста, попробуйте.

0 голосов
/ 06 сентября 2018

Это либо то, что вы хотите, либо очень близко к этому:

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

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.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
    });
  }
}

и HTML:

<button #mybutton>Test</button>

Это решение использует event.detail и свободно основано на этом полезном сообщении - Предотвращение срабатывания события click при возникновении события dblclick , которое не только обсуждает event.detail, но и учитывает связанные с этим проблемы синхронизации.

Одна из важных проблем в вашем коде заключается в том, что вы подписываетесь на событие внутри функции, которая вызывается несколько раз, что означает, что при каждом нажатии кнопки вы создаете новую подписку. Использование ngAfterViewInit (которое вызывается только один раз как часть жизненного цикла) предотвращает эту проблему (и обеспечивает загрузку DOM).

Вот для вас стек-блиц:

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

Простите за мои неряшливые объявления типов!

Это удовлетворяет вашим требованиям следующим образом:

  • Если я нажму один раз, мне нужно увидеть только одно сообщение («один клик») - PASS
  • Если я щелкаю дважды (двойной щелчок), все равно мне нужно видеть только одно сообщение («двойной щелчок») - FAIL, вы видите «один щелчок» при первом щелчке и «двойной щелчок» при втором щелчке
  • Если я нажму более двух раз - ничего не произойдет - ПРОЙДИТЕ

Из-за проблем со сроками, которые обсуждались в данной статье, вопрос о том, как вы решаете эту проблему, является вопросом предпочтения, и поэтому я решил не решать его. Кроме того, вы не платите мне;) Однако этот ответ должен помочь вам полностью решить его.

PS Есть комментарий к SO сообщению выше, который предполагает, что event.detail может не работать на IE11

0 голосов
/ 05 сентября 2018

Очень простой ответ (без использования каких-либо наблюдаемых) - использовать setTimeout() и проверять каждый щелчок, установлен ли тайм-аут, если это так, вы знаете, что это второй щелчок в пределах данного временного окна (двойной щелчок) а если нет, то это первый клик. Если время ожидания истекло, вы знаете, что это был всего один щелчок, например:

Обновлен 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 completing
    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

Я пропустил часть о игнорировании более двух кликов. Это не так уж много работы, но я немного разбил ее, чтобы можно было повторно использовать код, так что это выглядит намного больше. В любом случае, чтобы игнорировать 3+ клика, это выглядело бы следующим образом:

// 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 completed
      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 complete when the
// click timeout completes
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!");
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...