【问题标题】:Double click Angular directive双击 Angular 指令
【发布时间】:2023-03-05 04:39:02
【问题描述】:

我在 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 ...
  }

}

我开始创建指令,但我真的不知道该怎么做。

【问题讨论】:

    标签: angular typescript angular6 angular-directive angular7


    【解决方案1】:

    更新:

    如果你真的想要,你实际上可以使用指令来做到这一点。试试这个:

    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

    【讨论】:

    • 感谢您的代码。我想要一个指令的原因是我可以在按钮、锚点等中使用它。也可以像往常一样使用本机 HTML 标签。令人惊讶的是,在 JQuery 中易于实现的任务在 Angular 中是多么复杂。
    • 我知道确认对话框可能更容易,但在这个项目中我需要这样设置...
    • @MiguelMoura 我想出了一种在指令中执行此操作的方法。请参阅我的更新答案。
    【解决方案2】:

    你可以使用 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

    (也需要管理订阅)

    【讨论】:

    • 但这不能用指令来完成吗?我寻找指令的原因是将此逻辑保留在 HTML 和组件代码之外。并且还要重用它......除了你发布的代码的行为正是我想要的。
    • 您可以将逻辑提取到指令中。
    【解决方案3】:

    您可以通过带有@HostBinding@HostListener 的指令以及绑定到指令本身的任意@Inputs 来添加宿主元素行为绑定和侦听器:

    @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 / 标记,则此行为最好用组件包装。组件用于视图;指令是针对行为的。一个想法是将确认对话框(?)包装到指令可以调用的服务中。

    【讨论】:

    • 也许我没有正确解释自己......我按问题更新。我不想使用对话框。我想要的只是使用锚点......当它被点击时(一次就足够了)它进入“确认模式”等待第二次点击。然后调用方法 delete(post)。这有意义吗?
    猜你喜欢
    • 1970-01-01
    • 2015-09-27
    • 1970-01-01
    • 2015-02-01
    • 2016-04-04
    • 1970-01-01
    • 1970-01-01
    • 2019-07-08
    • 1970-01-01
    相关资源
    最近更新 更多