【问题标题】:Click outside component not working | Angular单击外部组件不起作用 |角
【发布时间】:2019-11-13 11:36:57
【问题描述】:

我为我的代码创建了一个Stackblitz(链接:https://stackblitz.com/edit/angular-iah7up)。这是我的自定义组件,它根据点击和点击切换两个 ng-content。

import {Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output} from '@angular/core';
import {fromEvent, Subject} from "rxjs";
import {untilDestroyed} from "ngx-take-until-destroy";
import {filter, switchMapTo, take} from "rxjs/operators";

@Component({
    selector: 'app-editable-inplace',
    templateUrl: './editable-inplace.component.html',
    styleUrls: ['./editable-inplace.component.css']
})
export class EditableInplaceComponent implements OnInit, OnDestroy {

    @Output() update = new EventEmitter();
    mode: 'view' | 'edit' = 'view';
    editMode = new Subject();
    editMode$ = this.editMode.asObservable();


    ngOnInit(): void {
        this.viewModeHandler();
        this.editModeHandler();
    }

    constructor(private host: ElementRef) {
    }

    // This method must be present, even if empty.
    ngOnDestroy() {
        // To protect you, we'll throw an error if it doesn't exist.
    }

    private viewModeHandler() {
        fromEvent(this.element, 'click').pipe(
            untilDestroyed(this)
        ).subscribe(() => {
            console.log('clicked inside')
            this.editMode.next(true);
            this.mode = 'edit';

        });
    }

    private editModeHandler() {
        const clickOutside$ = fromEvent(document, 'click').pipe(
            filter(({target}) => {
                console.log(this.mode)
                console.log('parent', this.element, 'child', target)

                const ans = this.element.contains(target) === false
                console.log('clickoutside', ans)
                return ans
            }),
            take(1)
        )

        this.editMode$.pipe(
            switchMapTo(clickOutside$),
            untilDestroyed(this)
        ).subscribe(event => {

            this.update.next();
            this.mode = 'view';
        });
    }

    get element() {
        return this.host.nativeElement;
    }

    toViewMode() {
        this.update.next();
        this.mode = 'view';
    }

}

模板:

<div *ngIf="mode==='view'" >
    <ng-content select="[view]"></ng-content>
</div>
<div *ngIf="mode==='edit'" >
    <ng-content select="[edit]"></ng-content>
</div>

用法:

<app-editable-inplace >
    <div view>
       <h3>Click to edit [not working :-( ]</h3>

    </div>
    <div edit>

              <input placeholder="Click to edit"   >

    </div>
</app-editable-inplace>

但是当我点击视图时,clickOutside$ 被立即触发(我不知道为什么) 而且,this.element.contains(target) === false 行不起作用,因为我可以在控制台中看到主机包含点击的项目,尽管总是说我点击了外部(我也不知道为什么)

【问题讨论】:

    标签: angular typescript rxjs


    【解决方案1】:

    在浏览器中,许多事件都是bubbling(而click 事件就是其中之一)。这意味着事件直接从目标元素开始。如果你在上面某处注册了同名的事件,那么不管目标处理程序中发生了什么,它都会被触发。

     document  =====================================================>  outsideHandler
      body                                                          /\
        ....                                                        ||  
        <app-editable-inplace>                                   bubbling
          <div view>                                                /\
            <h3>Click to edit [not working :-( ]</h3> <===== target || viewClickHandler
          </div>
          ...
        </app-editable-inplace>
    

    所以,正如你已经猜到的那样,clickOutside$ 会立即触发。

    您可以使用不同的方法来修复它:

    1) 最简单的方法是使用event.stopPropagation(),这样事件就不会再冒泡了。

    private viewModeHandler() {
        fromEvent(this.element, 'click').pipe(
            untilDestroyed(this)
        ).subscribe((e: any) => {
            console.log('clicked inside');
            e.stopPropagation(); <==================== add this
    

    2) 由于冒泡事件在这些处理程序中是相同的,因此您可以设置一些标志来防止在顶级处理程序中处理它。

     private viewModeHandler() {
        fromEvent(this.element, 'click').pipe(
            untilDestroyed(this)
        ).subscribe((e: any) => {
            console.log('clicked inside');
            e.fromInside = true;
    
    
     ...
     const clickOutside$ = fromEvent(document, 'click').pipe(
      filter((e: any) => {
        return !e.fromInside && this.element.contains(e.target) === false
      }),
    

    3) 在捕获阶段捕获点击事件:

    fromEvent(this.element, 'click', { capture: true }).pipe(
    
    ... 
    
    const clickOutside$ = fromEvent(document, 'click', { capture: true })
    

    【讨论】:

      【解决方案2】:

      这是因为您已将点击事件绑定到整个文档。

      由于您的点击外部计数器增加。

      您所要做的就是检查点击控制事件,然后防止传播。

      请查看本文以了解有关活动的更多信息。

      https://medium.com/@vsvaibhav2016/event-bubbling-and-event-capturing-in-javascript-6ff38bec30e

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-04
        • 1970-01-01
        • 1970-01-01
        • 2018-01-06
        • 2018-11-11
        • 2021-12-21
        相关资源
        最近更新 更多