1

I am working on an Angular project where a directive needs to react to mouseenter and mouseleave events. it needs to update the CSS of the host element where it is used.

Consider the contrived example below:

import { Directive, ElementRef, HostListener } from '@angular/core';

@Directive({
  selector: '[appMouseEvents]',
})
export class MouseEventsDirective {
  constructor(private element: ElementRef) {

  }
  @HostListener('mouseEnter', ['$event'])
  onMouseEnter() {
    console.log('mouseenter');
  }

  @HostListener('mouseleave',['$event'])
  onMouseLeave() {
    console.log('mouseleave');
  }

}

app.component.html

<hello name="{{ name }}"></hello>
<p appMouseEvents>
  Hover Mouse over me
</p>

When I profile the application in Angular Dev tools, I see that these event cause Angular to trigger change detection which is not needed as only the CSS of host element is affected. enter image description here

Is there a way to react to mouse event in the directive and not trigger change detection?

Here's a demo application: https://stackblitz.com/edit/angular-ivy-r6ln5w?file=src%2Fapp%2Fapp.component.ts

1
  • 1
    Wait why don't you use :hover pseudo class to handle this situation? Commented Jul 29, 2022 at 18:32

2 Answers 2

1

Yes, event fired on DOM would be monkey patched by zone.js and it triggers a change detection.

I would suggest you to go here using native browser feature. Like use :hover pseudo css class on host element.

:host:hover {
    // CSS rules
}

Alternative way to fix it would be using this.zone.runOutsideAngular as other suggested answer.

Sign up to request clarification or add additional context in comments.

Comments

1

You may need to use NgZone to run code outside of Angular zone (scope) and thus prevent angular change detection https://angular.io/api/core/NgZone

Your directive will look something like this:

    import { Directive, ElementRef, HostListener, NgZone } from '@angular/core';

@Directive({
  selector: '[appMouseEvents]',
})
export class MouseEventsDirective {
  constructor(private element: ElementRef, private zone: NgZone) {
    this.zone.runOutsideAngular(() => {
      this.element.nativeElement.addEventListener('mouseenter', (e) => {
        // e.preventDefault();
        // e.stopPropagation();
        console.log('mouseenter');
      });
    });

    this.zone.runOutsideAngular(() => {
      this.element.nativeElement.addEventListener('mouseleave', (e) => {
        // e.preventDefault();
        // e.stopPropagation();
        console.log('mouseleave');
      });
    });
  }
}

Here is you stackblitz modified https://stackblitz.com/edit/angular-ivy-faavr3?file=src/app/mouse-events.directive.ts

Hope this helps.

Edit: Updated source code

https://stackblitz.com/edit/angular-ivy-qvyk2t?file=src%2Fapp%2Fmouse-events.directive.ts

4 Comments

I still see the change detection cycles being triggered.
Change detection won't be triggered for the code inside this.zone.runOutsideAngular but will be triggered for the rest of the code. And for your actual use case, a css native solution is maybe more appropriate.
The code at stackblitz.com/edit/angular-ivy-faavr3?file=src/app/… causes change detection to be triggered. See - imgur.com/a/XQF1hDf
I have the above answer with working code that does not trigger change detection. See - imgur.com/w7TWKPk

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.