Двойной щелчок по угловой директиве - PullRequest
0 голосов
/ 23 января 2019

У меня есть следующий шаблон в Angular 7:

<ul class="posts">
  <li *ngFor="let post of posts">
    <h2>{{post.title}}</h2>
    <a (click)="delete(post)">Delete Post</a>
  </li>
</ul>

Я бы хотел создать директиву для подтверждения:

<a (click)="delete(post)" confirm="Confirm delete" class="delete">Delete Post</a>

Если щелкнуть момент (достаточно), он изменится на:

<a (click)="delete(post)" confirm="Confirm delete" class="delete confirm">Confirm delete</a>

Итак, что происходит:
- текст якоря изменится с "Удалить сообщение" на тот, который находится внутри подтверждения, например «Подтвердить удаление»;
- добавлен класс «подтверждения» для привязки классов CSS;
- метод Delete (post) вызывается только после нажатия кнопки привязки в «режиме подтверждения»;
- После нажатия кнопки «Подтвердить режим» ИЛИ 5 секунд без нажатия кнопки он возвращается в исходное состояние:

<a (click)="delete(post)" confirm="Confirm delete" class="delete">Delete Post</a>

Можно ли это сделать с помощью директивы?

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

@Directive({
  selector: '[confirm]'
})

export class ConfirmDirective {

  constructor(el: ElementRef) {
     el.nativeElement ...
  }

}

Я начал создавать директиву, но я действительно не уверен, как это сделать.

Ответы [ 3 ]

0 голосов
/ 23 января 2019

Вы можете использовать rxjs takeUntil,

posts: any[] = [
  { id: 1, title: 'post 1', deleteText: 'Delete Post' },
  { id: 2, title: 'post 2', deleteText: 'Delete Post' }
];

delete(post) {
  post.deleteText = 'Click to Confirm';
  post.css = 'custom';
  let confirm$ = fromEvent(document.getElementById('post-' + post.id), 'click');
  let timer$ = timer(5000)

  confirm$
    .pipe(
      takeUntil(timer$)
    )
    .subscribe(() => {
      console.log('ready to delete');
      this.posts = this.posts.filter(p => p.id !== post.id);
    });



  timer$
    .subscribe(() => {
      if (this.posts.find(p => p.id === post.id)) {
        console.log('timer is up, abort delete');
        post.deleteText = 'Delete Post';
        post.css = '';
      }
    });
  }

HtML:

<ul class="posts">
   <li *ngFor="let post of posts">
      <h2>{{post.title}}</h2>
      <a (click)="delete(post)" [ngClass]="post.css" [id]="'post-'+post.id">          {{post.deleteText}}</a>
  </li>
</ul>

демо: https://stackblitz.com/edit/angular-7-master-xg8phb

(также необходимо управлять подписками)

0 голосов
/ 23 января 2019

Обновление:

Если вы действительно хотите, вы можете сделать это с помощью директивы. Попробуйте это:

import { Directive, ElementRef, Input, Renderer2, OnInit } from '@angular/core';
import { Observable, Subject, BehaviorSubject, timer, fromEvent } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Directive({
  selector: '[confirm]'
})
export class ConfirmDirective implements OnInit {
  @Input('confirm') delete: Function;
  private confirm$ = fromEvent(this.el.nativeElement, 'click');
  private confirmTimeout: number = 5000;
  private timer$: Observable<number>;
  private isConfirming = new BehaviorSubject<boolean>(false);
  private isConfirming$ = this.isConfirming.asObservable();

  constructor(private el: ElementRef, private renderer: Renderer2) {}

  ngOnInit() {
    this.isConfirming$.subscribe((isConfirming) => this.setLabel(isConfirming));
    this.confirm$.subscribe((event: any) => this.doConfirm());
  }

  setLabel(isConfirming: boolean): void {
    // set the correct element text and styles
    let text: any;
    let textEl = this.renderer.createElement('span');

    if (this.el.nativeElement.firstChild) {
      this.renderer.removeChild(this.el.nativeElement, this.el.nativeElement.firstChild);
    }
    if (this.isConfirming.value) { // we are confirming right now
      text = this.renderer.createText('Please confirm delete');
      this.renderer.addClass(this.el.nativeElement, 'delete');
    } else {
      text = this.renderer.createText('Delete');
      this.renderer.removeClass(this.el.nativeElement, 'delete');
    }

    this.renderer.appendChild(this.el.nativeElement, text);
  }

  doConfirm(): void {
    if (this.isConfirming.value === false) { // start confirming
      this.timer$ = timer(this.confirmTimeout);
      this.isConfirming.next(true);

      // start the timer
      this.timer$
          .pipe(
            takeUntil(this.confirm$) // stop timer when confirm$ emits (this happens when the button is clicked again)
          )
            .subscribe(() => {
            this.isConfirming.next(false); // timeout done - confirm cancelled
        });
    } else { // delete confirmation
      this.isConfirming.next(false);
      this.delete(); // this is the delete action that was passed to the directive
    }
  }
}

Вы бы применили его к такому элементу, передав в качестве параметра фактический метод delete.

<button type="button" [confirm]="delete"></button>

Рабочий пример: https://stackblitz.com/edit/angular-wdfcux

Старый ответ:

Не уверен, что директива будет лучшим способом. Возможно, это можно сделать, но вам нужно как-то перехватить обработчик щелчка и / или передать ему метод удаления. Это было бы, вероятно, грязно.

Я бы, вероятно, создал компонент для кнопки удаления и обработал бы его там (ну, на самом деле, это ложь, если бы это был я, я бы использовал собственный диалог confirm и покончил бы с этим, но вы этого не сделаете хочу).

Примерно так:

import { Component } from '@angular/core';
import { Observable, Subject, timer } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'delete-button',
  template: `<button type="button" (click)="delete()" [ngClass]="{ delete: isConfirming }">{{ label }}</button>`,
  styles: ['.delete { background-color: teal; color: white; } ']
})
export class DeleteButtonComponent {
  private confirmTimeout: number = 5000;
  private timer$: Observable<number>;
  private cancelTimer = new Subject();
  public isConfirming: boolean = false;

  constructor() {}

  get label(): string {
    return this.isConfirming
      ? 'Please confirm delete'
      : 'Delete'
  }

  delete() {
    if (!this.isConfirming) {
      this.timer$ = timer(this.confirmTimeout);
      this.isConfirming = true;

      this.timer$
          .pipe(
            takeUntil(this.cancelTimer)
          ).subscribe(() => {
            this.isConfirming = false;
        }, null, () => this.isConfirming = false);
    } else {
      this.cancelTimer.next();
      // really delete
    }
  }
}

Рабочий пример: https://stackblitz.com/edit/angular-z6fek4

0 голосов
/ 23 января 2019

Вы можете добавить привязки поведения элемента хоста и слушателей через директиву с @HostBinding и @HostListener, а также произвольные @Input s, привязанные к самой директиве:

@Directive({
  selector: '[confirm]'
})

export class ConfirmDirective {

  @HostListener('dblclick') onDoubleClick(event) {
    // .. do double click logic, just like binding (dbclick) to an element
  }

  @HostBinding('class.confirm') confirmStyle: boolean; // toggles class on host, just like with template binding

  @Input('confirm') confirm: boolean; // State from outside the directive that can be bound to the directive attribute directly, i.e. 'click to confirm' box

  constructor(el: ElementRef) {
     el.nativeElement ...
  }

}

Если вы 'Если вы хотите напрямую добавить HTML / разметку с директивой для кнопки подтверждения, то вместо этого лучше использовать компонент.Компоненты предназначены для просмотра;Директивы для поведения.Одна идея состоит в том, чтобы заключить диалоговое окно подтверждения (?) В сервис, который может вызвать директива.

...