原来问题是文档事件侦听器太多。
我有一个onClickOutside指令,它注册了一个文档点击监听器,表中的每个条目都有一个下拉菜单,使用这个指令。因此,当我显示100个条目时,每次单击任意位置都会触发100个事件侦听器。
我优化了这一点,在click服务中只有一个文档click侦听器,并且指令只(取消)向服务注册元素。
服务内容如下:
/**
* A event emitter that should be notified about some kind of click related to the element
*/
export interface ClickListener {
element : ElementRef;
emitter : EventEmitter<Event>;
}
/**
* A central service for managing click handlers
* This is mainly an optimization to reduce the amount of global click event handler
*/
@Injectable()
export class ClickService {
private clickOutsideListeners : ClickListener[] = [];
constructor() {
this.registerClickHandlers();
}
private registerClickHandlers() : void {
document.addEventListener('click', (event : Event) => {
this.handleClickOutside(event);
});
}
/**
* Emits for every registered callback if the click is outside of the element
*/
private handleClickOutside(event : Event) : void {
this.clickOutsideListeners
.filter(entry => !entry.element.nativeElement.contains(event.target))
.forEach(entry => {
entry.emitter.emit(event);
});
}
/**
* Registers a listener to be notified whenever there is a click outside of the element
*/
public addOutsideClickListener(element : ClickListener) : void {
if (this.clickOutsideListeners.indexOf(element) !== -1) return;
this.clickOutsideListeners.push(element);
}
/**
* Unregisters the listener
*/
public removeOutsideClickListener(element : ClickListener) : void {
const index = this.clickOutsideListeners.findIndex(entry => entry === element);
if (index === -1) return;
this.clickOutsideListeners.splice(index, 1);
}
}
指令:
/**
* Emits a event when there has been a click outside of the host element
*/
@Directive({
selector: '[mdeOnClickOutside]',
})
export class OnClickOutsideDirective implements OnInit, OnDestroy {
@Output() mdeOnClickOutside : EventEmitter<Event> = new EventEmitter();
/**
* (Un-)registers a click listener with the global click service
*/
@Input() set mdeOnClickOutsideActive(active : boolean) {
this.inputReceived = true;
const clickCallback = {
element: this.element,
emitter: this.mdeOnClickOutside,
};
if (active) {
this.clickService.addOutsideClickListener(clickCallback);
} else {
this.clickService.removeOutsideClickListener(clickCallback);
}
}
private inputReceived : boolean;
constructor(private element : ElementRef, private clickService : ClickService) {
}
/**
* Activates click listener if it has not explicitly been set
*/
ngOnInit() : void {
if (this.inputReceived) return;
this.mdeOnClickOutsideActive = true;
}
ngOnDestroy() : void {
this.mdeOnClickOutsideActive = false;
}
}
然后在其他组件的模板中使用它:
<div (mdeOnClickOutside)="clickOutside()">Something</div>
具有组件中的功能:
clickOutside() {
console.log('Somebody clicked somwhere outside');
}