Angular 구성 요소 외부에서 클릭 감지


답변:


187
import { Component, ElementRef, HostListener, Input } from '@angular/core';

@Component({
  selector: 'selector',
  template: `
    <div>
      {{text}}
    </div>
  `
})
export class AnotherComponent {
  public text: String;

  @HostListener('document:click', ['$event'])
  clickout(event) {
    if(this.eRef.nativeElement.contains(event.target)) {
      this.text = "clicked inside";
    } else {
      this.text = "clicked outside";
    }
  }

  constructor(private eRef: ElementRef) {
    this.text = 'no clicks yet';
  }
}

실제 사례-여기를 클릭하십시오


13
DOM에서 요소를 제거하는 ngIf가 click 이벤트 이전에 발생하기 때문에 트리거 요소 내부에 ngIf에 의해 제어되는 요소가있는 경우 작동하지 않습니다. plnkr.co/edit/spctsLxkFCxNqLtfzE5q?p=preview
J. Frankenstein

다음을 통해 동적으로 생성 된 구성 요소에서 작동합니까? const factory = this.resolver.resolveComponentFactory (MyComponent); const elem = this.vcr.createComponent (공장);
Avi Moraly

1
이 주제에 대한 좋은 기사 : christianliebel.com/2016/05/...
미구엘 라라

47

AMagyar의 답변에 대한 대안. 이 버전은 ngIf로 DOM에서 제거 된 요소를 클릭 할 때 작동합니다.

http://plnkr.co/edit/4mrn4GjM95uvSbQtxrAS?p=preview

  private wasInside = false;
  
  @HostListener('click')
  clickInside() {
    this.text = "clicked inside";
    this.wasInside = true;
  }
  
  @HostListener('document:click')
  clickout() {
    if (!this.wasInside) {
      this.text = "clicked outside";
    }
    this.wasInside = false;
  }


이뿐만 아니라 ngif 또는 동적 업데이트와 함께 완벽하게 작동
카스 Kandari

이 굉장합니다
블라디미르 데미 레프

23

@Hostlistener를 통해 문서 클릭에 바인딩하는 것은 비용이 많이 듭니다. 과도하게 사용하는 경우 (예 : 사용자 지정 드롭 다운 구성 요소를 빌드하고 양식에 여러 인스턴스를 만들 때) 가시적 인 성능 영향을 미칠 수 있습니다.

기본 앱 구성 요소 내에서 한 번만 문서 클릭 이벤트에 @Hostlistener ()를 추가하는 것이 좋습니다. 이벤트는 글로벌 유틸리티 서비스에 저장된 공개 주제 내에서 클릭 된 대상 요소의 값을 푸시해야합니다.

@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {

  constructor(private utilitiesService: UtilitiesService) {}

  @HostListener('document:click', ['$event'])
  documentClick(event: any): void {
    this.utilitiesService.documentClickedTarget.next(event.target)
  }
}

@Injectable({ providedIn: 'root' })
export class UtilitiesService {
   documentClickedTarget: Subject<HTMLElement> = new Subject<HTMLElement>()
}

클릭 한 대상 요소에 관심이있는 사람은 누구나 유틸리티 서비스의 공개 주제를 구독하고 구성 요소가 파괴되면 구독을 취소해야합니다.

export class AnotherComponent implements OnInit {

  @ViewChild('somePopup', { read: ElementRef, static: false }) somePopup: ElementRef

  constructor(private utilitiesService: UtilitiesService) { }

  ngOnInit() {
      this.utilitiesService.documentClickedTarget
           .subscribe(target => this.documentClickListener(target))
  }

  documentClickListener(target: any): void {
     if (this.somePopup.nativeElement.contains(target))
        // Clicked inside  
     else
        // Clicked outside
  }

4
나는 이것이 많은 최적화를 허용하기 때문에 받아 들여진 대답이되어야한다고 생각합니다 : 이 예
에서처럼

이것은 내가 인터넷에서 얻은 가장 예쁜 솔루션입니다
Anup Bangale

1
@ 전등갓 맞습니다. 나는 이것에 대해 이야기했다. 답을 다시 읽으십시오. 구독 취소 구현은 귀하의 스타일 (takeUntil (), Subscription.add ())로 남겨 둡니다. 구독 취소하는 것을 잊지 마세요!
ginalx

@ginalx 솔루션을 구현했는데 예상대로 작동합니다. 내가 사용하는 방식으로 문제가 발생했지만. 여기 에 질문이 있습니다. 한 번보세요
Nilesh

6

위에서 언급 한 답변은 정확하지만 관련 구성 요소에서 초점을 잃은 후 무거운 프로세스를 수행하는 경우 어떻게해야합니까? 이를 위해 포커스 아웃 이벤트 프로세스가 관련 구성 요소에서만 포커스를 잃을 때만 발생하는 두 개의 플래그가있는 솔루션을 제공했습니다.

isFocusInsideComponent = false;
isComponentClicked = false;

@HostListener('click')
clickInside() {
    this.isFocusInsideComponent = true;
    this.isComponentClicked = true;
}

@HostListener('document:click')
clickout() {
    if (!this.isFocusInsideComponent && this.isComponentClicked) {
        // do the heavy process

        this.isComponentClicked = false;
    }
    this.isFocusInsideComponent = false;
}

이것이 당신을 도울 것입니다. 놓친 것이 있으면 정정하십시오.



2

ginalx의 답변 은 기본 imo로 설정되어야합니다.이 방법은 많은 최적화를 허용합니다.

문제

항목 목록이 있고 토글해야하는 메뉴를 포함하려는 모든 항목이 있다고 가정합니다. click자체 이벤트 를 수신하는 버튼에 토글을 포함 (click)="toggle()"하지만 사용자가 외부를 클릭 할 때마다 메뉴를 토글하려고합니다. 항목 목록이 커지고 @HostListener('document:click')모든 메뉴에를 첨부 하면 메뉴가 꺼져 있어도 항목 내에로드 된 모든 메뉴가 전체 문서에 대한 클릭을 수신하기 시작합니다. 명백한 성능 문제 외에도 이것은 불필요합니다.

예를 들어, 팝업이 클릭을 통해 토글 될 때마다 구독하고 "외부 클릭"만들을 수 있습니다.


isActive: boolean = false;

// to prevent memory leaks and improve efficiency, the menu
// gets loaded only when the toggle gets clicked
private _toggleMenuSubject$: BehaviorSubject<boolean>;
private _toggleMenu$: Observable<boolean>;

private _toggleMenuSub: Subscription;
private _clickSub: Subscription = null;


constructor(
 ...
 private _utilitiesService: UtilitiesService,
 private _elementRef: ElementRef,
){
 ...
 this._toggleMenuSubject$ = new BehaviorSubject(false);
 this._toggleMenu$ = this._toggleMenuSubject$.asObservable();

}

ngOnInit() {
 this._toggleMenuSub = this._toggleMenu$.pipe(
      tap(isActive => {
        logger.debug('Label Menu is active', isActive)
        this.isActive = isActive;

        // subscribe to the click event only if the menu is Active
        // otherwise unsubscribe and save memory
        if(isActive === true){
          this._clickSub = this._utilitiesService.documentClickedTarget
           .subscribe(target => this._documentClickListener(target));
        }else if(isActive === false && this._clickSub !== null){
          this._clickSub.unsubscribe();
        }

      }),
      // other observable logic
      ...
      ).subscribe();
}

toggle() {
    this._toggleMenuSubject$.next(!this.isActive);
}

private _documentClickListener(targetElement: HTMLElement): void {
    const clickedInside = this._elementRef.nativeElement.contains(targetElement);
    if (!clickedInside) {
      this._toggleMenuSubject$.next(false);
    }    
 }

ngOnDestroy(){
 this._toggleMenuSub.unsubscribe();
}

그리고 *.component.html:


<button (click)="toggle()">Toggle the menu</button>


나는 당신이 생각하는 방식에 동의하지만 tap연산자에 모든 논리를 채우지 않는 것이 좋습니다 . 대신 skipWhile(() => !this.isActive), switchMap(() => this._utilitiesService.documentClickedTarget), filter((target) => !this._elementRef.nativeElement.contains(target)), tap(() => this._toggleMenuSubject$.next(false)). 이렇게하면 RxJ를 더 많이 활용하고 일부 구독을 건너 뛸 수 있습니다.
Gizrah

0

@J 개선. 프랑켄슈타인 answear

  
  @HostListener('click')
  clickInside($event) {
    this.text = "clicked inside";
    $event.stopPropagation();
  }
  
  @HostListener('document:click')
  clickout() {
      this.text = "clicked outside";
  }


-1

u는 (focusout) 또는 (blur)와 같은 이벤트 함수를 호출 한 다음 코드를 넣을 수 있습니다.

<div tabindex=0 (blur)="outsideClick()">raw data </div>
 

 outsideClick() {
  alert('put your condition here');
   }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.