如果使用 *ngIf 删除子组件,则会触发 Angular 自定义 clickaway 侦听器

Posted

技术标签:

【中文标题】如果使用 *ngIf 删除子组件,则会触发 Angular 自定义 clickaway 侦听器【英文标题】:Angular custom clickaway listener is triggered if a child component is removed using *ngIf 【发布时间】:2020-10-15 05:16:12 【问题描述】:

我有一个 clickaway 监听器作为使用 @HostListener put on App.component.ts 的指令

@Component(
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.css"],
)
export class AppComponent 
  constructor(private clickaway: ClickawayService) 

  @HostListener("document:click", ["$event"]) documentClick(event: any): void 
    this.clickaway.target.next(event.target);
  


@Directive(
  selector: "[clickaway]",
)
export class ClickawayDirective implements OnInit, OnDestroy 
  @Input() clickaway = null;

  private subscription: Subscription = null;
  constructor(
    private clickawayService: ClickawayService,
    private eleRef: ElementRef
  ) 

  ngOnInit() 
    this.subscription = this.clickawayService.target.subscribe((target) => 
      if (!this.eleRef.nativeElement.contains(target)) 
        this.clickaway();
      
    );
  

  ngOnDestroy() 
    this.subscription.unsubscribe();
  


ClickAway 服务只是提供了一个主题来在AppComponent 上收听document:click

我在一个由 *ngIf 控制子元素的 div 上使用此指令,例如:

<div [clickaway]="doSomething">
  <span *ngIf="isVisible">
    <button (click)="closeSpan()">close</button> //closeSpan() sets isVisible to false.
  </span>
</div>

问题是每当我点击关闭时,它也会触发 clickaway 的 doSomething 功能。

我知道 *ngIf 会从 dom 中删除跨度,因此当指令运行时 !this.eleRef.nativeElement.contains(target) 评估为 false,因为元素不存在。

到目前为止我尝试过的是:

closeSpan() 
  setTimeout(() => this.isVisible = false, 100);

并使用position: absolute 和非常高的偏移量将跨度移出视野并移除*ngIf

这些解决方案有效,但我正在寻求一种更优雅的方式,最好是指令本身处理此类情况。

谢谢

【问题讨论】:

【参考方案1】:

实际上,setTimeout() 告诉浏览器在为任何当前未决事件运行完事件处理程序并完成更新文档的当前状态后调用该函数。 source

尝试将延迟设置为 0 以获得更优雅的解决方案。 以 0(零)毫秒的延迟调用 setTimeout 不会在给定时间间隔后执行回调函数。 执行取决于队列中等待任务的数量。 source

您的问题很有趣,需要进行一些调整。相信你已经有了答案。由于您想对 click 事件做两件事,因此您需要根据自己的喜好对它们进行计时。

其他可能的解决方案包括在您的服务中使用布尔变量 isHandled: boolean 来避免执行。或者像将当前点击的目标(不是文档点击的目标)推送到您的服务并进行额外检查。

【讨论】:

感谢您的调查。我试图找到一个更优雅的解决方案,但我想没有。尽管我将这个问题留给对其有更好理解的人。 很抱歉我想不出更好的东西。您的问题类似于***.com/a/46656671/7322763。您必须使用标志或 setTimeout。如果超时以 0 延迟工作,我会很高兴,但我也会接受标志解决方案。【参考方案2】:

我不熟悉点击离开,但这样的东西可以吗?

<div [clickaway]="doSomething()"> 
  <button *ngIf="isVisible" (click)="isVisible=false">close</button>
</div>

doSomething() 
  if(this.isVisible) 
  

【讨论】:

我在上面的例子中简化了我的问题,实际上我是在div内渲染一个组件链,而这个按钮类似于组件内的一个元素,所以我可能无法得到在 doSomething 中引用 isVisible 并为每个此类元素处理它似乎很复杂。 由于您使用的是 Angular,我想不出任何理由使用 document:click。为什么不直接使用 (click)="" ? document:click 用于 clickaway 指令,它监听应用程序上的任何点击并调用带有事件数据的subject.next clickaway 指令订阅此主题,当点击事件发生时指令检查如果点击的元素是宿主元素的子元素,它会告诉它点击是在内部还是外部。 这是我使用@ginalx 对其他问题的回答为 clickaway 实现的。如果您对此有其他方法,我可以尝试一下。【参考方案3】:

尝试更改 *ngIf:

  <span *ngIf="isVisible">
    <button (click)="closeSpan()">close</button> //closeSpan() sets isVisible to false.
  </span>

对于隐藏:

  <span [hidden]="!isVisible">
    <button (click)="closeSpan()">close</button> //closeSpan() sets isVisible to false.
  </span>

【讨论】:

以上是关于如果使用 *ngIf 删除子组件,则会触发 Angular 自定义 clickaway 侦听器的主要内容,如果未能解决你的问题,请参考以下文章

Angular 5. 检查子组件中的父 *ngIf

从Angular中的父组件访问子ngIf

如何检查ngIf是不是生效

Angular:使用 NgIf 删除组件、内部表单和元素?

p:dataTable:单击嵌入内容时触发rowSelect事件(图像)

子组件如何在子组件初始化时控制父组件中自身的可见性?