typescript - Clic în afara componentului nu funcționează | Angular


Am făcut un Stackblitz pentru codul meu (link: https://stackblitz.com/edit/angular-iah7up). Acesta este componentul meu personalizat care comută între două ng-content în funcție de click-ul în interior și click-urile în afara lui.

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';
    }

}

Șablon :

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

Utilizare :

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

    </div>
    <div edit>

              <input placeholder="Click to edit"   >

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

Dar când dau clic pe vizualizare, clickOutside$se declanșează instantaneu (nu știu de ce). De asemenea, linia this.element.contains(target) === false nu funcționează pentru că pot vedea în consolă că gazda conține elementul pe care am dat clic, deși spune mereu că am făcut clic în exterior (nu știu de ce nici asta).

Răspuns #1

În browser, multe evenimente fac bubbling (și evenimentul click este unul dintre ele). Aceasta înseamnă că evenimentul merge de la elementul țintă direct în sus. Și dacă ai înregistrat un eveniment cu același nume undeva mai sus, atunci acesta va fi declanșat indiferent ce s-a întâmplat în handlerul țintei.

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

Așadar, așa cum ați ghicit deja din acest motiv, clickOutside$ este declanșat instantaneu.

Există diferite moduri pe care le poți folosi pentru a-l repara:

1) Cea mai simplă metodă este să folosești event.stopPropagation() astfel încât evenimentul să nu fie propagat mai departe.

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

2) Deoarece evenimentul de bule este același în acei handleri, puteți seta un flag pentru a împiedica tratarea lui în handlerul de nivel superior.

 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) Prinde evenimentul click în faza de captare:

fromEvent(this.element, 'click', { capture: true }).pipe(

... 

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

Răspunsul #2

Este pentru că ți-ai legat evenimentul de clic de întregul document.

datorită asta contorul tău de clicuri în afara se incrementează.

Tot ce trebuie să faci este să verifici evenimentul de control click și apoi să previi propagarea.

vă rugăm să revizuiți acest articol pentru a înțelege mai multe despre evenimente.

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