简体   繁体   中英

HostListener on outside click Angular 6

This is my way to open a popover:

<div>
  <a (click)="trigger = true">
    <div>
      <i class="pe-7s-file"></i>
    </div>
  </a>
  <div popover clickOutside (clickOutside)="console.log('Outside')" *ngIf="trigger==true">
    <p>Hello Popover</p>
  </div>
</div>

When I click on first div it prints (outside) in my console, I want to open the popover at first time without any message on console, and when I click outside popover div the console should print.

This is my hostlistener:

@Directive({
    selector: '[clickOutside]'
})
export class ClickOutsideDirective {

    constructor(private _elementRef: ElementRef) { }

    @Output('clickOutside') clickOutside: EventEmitter<any> = new EventEmitter();

    @HostListener('document:click', ['$event.target'])
    onMouseEnter(targetElement) {
        const clickedInside = this._elementRef.nativeElement.contains(targetElement);
        if (!clickedInside) {
            this.clickOutside.emit(null);
        }
    }

}

i am not sure about what you try to achieve so far. So base on my interpretation i have modify your code as following :

html

<div>
    <a (click)="displayPopover()">
        <div>
            my content outside
        </div>
    </a>
    <ng-container *ngIf="isDisplay">
      <div clickOutside (clickOutside)="myHandler()" style="border: 1px solid black">
          <p>Click me, nothing will happen</p>
      </div>
    </ng-container>
</div>

component ts for sample only :

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  isDisplay: boolean = false;
  myHandler() {
    console.log('click outside');
    this.isDisplay = false;
  }

  displayPopover() {
    this.isDisplay = true;
  }
}

directive :

@Directive({
    selector: '[clickOutside]'
})
export class ClickOutsideDirective implements AfterViewInit, OnDestroy {
    @Output('clickOutside') clickOutside: EventEmitter<void> = new EventEmitter();
    squareMatrix: {
      x1: number,
      y1: number,
      x2: number,
      y2: number
    };
    sub: Subscription;
    constructor(private _elementRef: ElementRef) { }

    ngAfterViewInit() {
        /**
         * Populate current square x,y position.
         */
        this.populateMatrix();
        // Timeout is here to ignore first click. Not right way but do the job.
        setTimeout(() => {
        this.sub = fromEvent(window, 'click').subscribe((e: MouseEvent) => {
            if(!this.checkIfClickOnSquare(e.clientX, e.clientY)) {
              this.clickOutside.emit();
            }
        });
        },100); 
    }

    ngOnDestroy(){
      if(this.sub) {
        //Don't forget to unsubscribe.
        this.sub.unsubscribe();
      }
    }

    private populateMatrix() {
      const {x, y, width, height} = this._elementRef.nativeElement.getBoundingClientRect();

      this.squareMatrix = {
        x1 : x,
        y1: y,
        x2: x + width,
        y2: y + height
      };
    }

    private checkIfClickOnSquare(currentX, currentY): boolean {
      return (
          currentX > this.squareMatrix.x1 &&
          currentX < this.squareMatrix.x2 && 
          currentY > this.squareMatrix.y1 && 
          currentY < this.squareMatrix.y2
        );
    }
}

Explication : I have create square matrix to have point reference of the actual drawed Popover. base on this square, on each click trigger by document, i check if current x,y cursor is inside or outside of current DOMElement.

To prevent initial click i have added timeout before to bind the event. Is not right way to do it bit is doing the job.

you can look on the live sample if you prefer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM