답변:
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';
}
}
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;
}
@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
}
위에서 언급 한 답변은 정확하지만 관련 구성 요소에서 초점을 잃은 후 무거운 프로세스를 수행하는 경우 어떻게해야합니까? 이를 위해 포커스 아웃 이벤트 프로세스가 관련 구성 요소에서만 포커스를 잃을 때만 발생하는 두 개의 플래그가있는 솔루션을 제공했습니다.
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;
}
이것이 당신을 도울 것입니다. 놓친 것이 있으면 정정하십시오.
https://www.npmjs.com/package/ng-click-outside 패키지 에서 clickOutside () 메서드를 사용할 수 있습니다.
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를 더 많이 활용하고 일부 구독을 건너 뛸 수 있습니다.