Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is an RxJS Subject faster than multiple event listeners?

I recently discovered that the performance of a page was greatly hindered by an angular directive which was used multiple times on its template. The cause of the slower performance was found in the following bit of code:

@HostListener('window:keydown', ['$event'])
private keydown(e: KeyboardEvent) {
     this.doSomething(e);
}

I suspected the problem might have been caused by the registration of multiple event listeners on the window keydown event, because a new one was registered each time that directive was repeated on the page. To test that theory, I created a service with an RxJS Subject to handle that keyboard event:

@Injectable()
export class KeyboardService {
    constructor() {
        window.addEventListener('keydown', event => {
            this.keydownSubject.next(event);
        });
    }
}

private keydownSubject: Subject<KeyboardEvent> = new Subject<KeyboardEvent>();

get keydown(): Observable<KeyboardEvent> {
    return this.keydownSubject.asObservable();
}

I then removed the @HostListener in the directive, and subscribed to this service's subject in ngOnInit:

export class KeydownEventDirective implements OnInit, OnDestroy {
    constructor(private keyboardService: KeyboardService) {}

    private keydown(e: KeyboardEvent) {
        this.doSomething(e);
    }

    private keydownSubscription: Subscription;
    ngOnInit() {
        this.keydownSubscription =
            this.keyboardService.keydown.subscribe(e => {
                this.keydown(e);
            });
    }

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

    ...
}

The solution sped up the page, and I have had difficulty discovering why this would be the case. Why would @HostListener or adding multiple event listeners to the window's keydown event be more detrimental to the page's performance than multiple subscriptions to an RxJS Subject? Could it be that angular HostListeners are not passive listeners by default?

like image 326
Braden Van Wagenen Avatar asked Aug 11 '17 20:08

Braden Van Wagenen


1 Answers

The answer lies in Angular's use of Zone.js. Angular uses Zone.js for its change detection. For information on how Zone.js works, I recommend the thoughtram.io articles, Understanding Zones and Zones in Angular.

The initial problem was found in the performance of the page on each keystroke. Why would the events not be able to handle it efficiently? The problem was Angular's change detection. Zone.js monkey patches the DOM event listeners registration with its own functions. Angular takes advantage of this, making every DOM event with a listener also trigger change detection.

When multiple instances of a repeated component each had their own @HostListener on the window, they each independently triggered Angular's change detection. This resulted in Angular trying to check the entire application for changes at every key stroke for each component listening to the keyboard event. It's no wonder with that in mind that there was a performance issue.

The KeyboardService solved the problem because it only fires the change detection once. There is only one listener, and the RxJS Subject synchronously passes the event to each of the components' subscriptions within a singular Zone.js execution.

like image 63
Braden Van Wagenen Avatar answered Oct 02 '22 22:10

Braden Van Wagenen