angularopenlayersangular-componentsopenlayers-6angular-renderer

Angular rerenders when user hovers over Openlayers map


I am using Openlayers 6 in my project alongside Angular 8. And up until now i noticed that whenever i hover over Openlayers map, Angular component, where the map is located, gets re-rendered.

My question is how do i make parent component stop re-rendering on that hover all the time. Because it slows down the app. I have a much bigger component in my project and making everything re-render because of this, will make the app itself slower.

For this purpose i created a repo to demonstrate this: https://github.com/petrovichm/angular-openlayers-hover-problem. In this example i added a method in html that will log when it is run, thus giving a overview how many times is angular re rendering component.

I wanted to create online runnable with plunker or codesanbox but when i make this example window freezes because there is endless loop in re-render, which makes them unusable, that doesn't happen really when i run this project locally, it only happens on hover

Thanks.


Solution

  • After cloning your repo to look at things, there are two things.

    First, the use case is very simple so you won't see all of the benefits from using the on push change detection strategy that mitigates most of this issue since it won't cause non-root components to have a change detection cycle (if all components use the on push strategy that is).

    Second, since the underlying map library (OpenLayers) is attaching events to DOM nodes itself, that will cause Angular's change detection to fire due to zone.js intercepting the event handling. To prevent this, you will have to set up the map outside of the Angular zone:

    import { ..., NgZone } from '@angular/core';
    
    ...
    export class AppComponent implements OnInit {
      ...
    
      construtor(private zone: NgZone) {
      }
    
      ...
    
      ngOnInit() {
        this.zone.runOutsideAngular(() => {
          this.map = new Map({ ... });
        });
      }
    }
    

    You have to be careful when running things outside of the Angular zone, but it makes sense here to do so to prevent your issues. You will have to wrap any logic in event handling of your map back in the Angular zone so that Angular will know about the updates.

    export class AppComponent implements OnInit {
      currentValue = 0;
    
      ...
    
      ngOnInit() {
        this.zone.runOutsideAngular(() => {
          this.map = new Map({ ... });
        });
    
        this.map.on('click', (e) => {
          this.currentValue++; // Angular will not pick up this change because it will run outside of the Angular zone
        });
    
        this.map.on('click', (e) => {
          this.zone.run(() => {
            this.currentValue++; // Angular will pick up this change because it will run inside the Angular zone
          });
        });
      }
    }
    

    Here is a good blog post that I think can further help understanding this: https://netbasal.com/optimizing-angular-change-detection-triggered-by-dom-events-d2a3b2e11d87.