maplibre / ngx-maplibre-gl

Angular binding of maplibre-gl
https://maplibre.org/ngx-maplibre-gl/
MIT License
76 stars 25 forks source link

Popup click changing routes results in NG0953 #193

Open marcjulian opened 1 month ago

marcjulian commented 1 month ago

I have an application where a Popup display data and uses RouterLink to change the route on click. When the route changes the popup is closed which emits the popupClose output. However, the component is already destroyed. This breaks the navigation resulting in a white screen.

CleanShot 2024-09-05 at 12 43 18 CleanShot 2024-09-05 at 12 45 24

I will try to add an example for this

magnolo commented 4 weeks ago

I stumbled across the same error when using @for around <mgl-layer /> and tried to get track by the layer ID. Our system then only updates the paint properties of the layer and didnt update because the paint object reference didnt change. I tried then to update the id of the layer when a paint property changes (not a good idea i know =). So a new layer gets created and the other the should be removed, but the removed layer (maybe) tries still to emit something.... sry for the confusing description.

Bildschirmfoto 2024-09-24 um 10 49 39 Bildschirmfoto 2024-09-24 um 10 49 59 Error: maplibre-ngx-maplibre-gl.mjs:245:47

Tried to create an example of all that: MapTest Stackblitz

EDIT: I played around a bit more and it seems also to happen, when i remove a layer and try to add a layer with an ID that existed before

HarelM commented 4 weeks ago

Looking at the MapService addLayer code, it looks like when removing a layer there might be a need to unregister all the registrations that were made when the latter was added. It's worth a shot if you want to try it out.

magnolo commented 4 weeks ago

hey there. Thanks for the fast response. I just digged into the code a bit and if i am not totally mistaken i need the listener function itself (reference) which gets registered on the mapInstance.on call, to unregister it later on to call mapInstance.off within removeLayer. So we have to store it somewhere. This should then also apply to all other mapInstance.on calls that are not global map related ?

i am not so familiar with this library code. so i will just get into it more deeper and hopefully can come up with a solution or maybe a good suggestion.

Wish i could remove all events from the mapInstance with just the eventTypeName and the layer ID, but as far is i know that is not possible.

HarelM commented 3 weeks ago

I tend to think you got it correctly. I would try and stove this only for a specific event and see if it actually solved the issue before doing it for all events.

magnolo commented 3 weeks ago

So i played around and testet this with only the layerMouseMove event and stored the function within a mapService variable. When i call the off function for the mapInstance within removeLayer with the function reference, no error gets thrown.

Now, i think, it would just be a matter of where to store the listener functions

magnolo commented 3 weeks ago

i got another confession to make. The base problem i stumbled across all this, is because i edit the layers paint and layout properties. Now i realized, that when i update layout or paint, the properties that are removed from previousValue to currentValue are ignored, because setAllLayerPaintProperty and setAllLayerLayoutProperty are only handling the current available keys.

i am on it =P

HarelM commented 2 weeks ago

I have the same issue now with one of my popups in my latest version. @magnolo @marcjulian did you find a solution to this?

HarelM commented 2 weeks ago

I see that ngOnDestroy was removed from the popup component class as part of

I'll create a PR to bring it back unless @n-elhk can explain why it was removed and how to solve the current issue at hand.

  ngOnDestroy() {
    if (this.popupInstance) {
      if (this.lngLat || this.feature) {
        this.mapService.removePopupFromMap(this.popupInstance);
      } else if (this.marker && this.marker.markerInstance) {
        this.mapService.removePopupFromMarker(this.marker.markerInstance);
      }
    }
    this.popupInstance = undefined;
  }
HarelM commented 2 weeks ago

My mistake, this method was just renamed and is used from destroyRef now...

HarelM commented 2 weeks ago

I think I was able to create a minimal reproduction of my scenario: The problem happens when there's a popup attached to a marker, and both are destoried. The marker gets destroyed first so when trying to remove the popup, the marker is already null, I think. I'll dig deeper and see what I find...

HarelM commented 2 weeks ago

Interestingly enough, calling the same method from ngOnDestory is working as it did before, because somehow the marker is removed only after ngDestory is being called and so the removal of the popup succeeds. I'm not sure what to do with this information. We can revert to using ngOnDestroy, but that's not a great solution. We can open a bug for angular, which I doubt will be solved in a timely manner. I can't think of a better solution. I'll sleep on it...

HarelM commented 2 weeks ago

I've checked the situation in a previous version (17.4.3). The behavior is basically the same, this is probably a new warning angular added in version 18 to let the developer know that this is happening. Still not sure how to properly solve this. Any input would be appreciated.

magnolo commented 2 weeks ago

I havent figured out yet, about the removing and readding layers with the same id problem (have not got time enought yet). But i just tried reset layout and paint properties if some of them got removed.

https://github.com/magnolo/ngx-maplibre-gl/commit/c36e9c2002afee487f08315017f09076add87dc1

will help to investigate more here. but i sadly dont know when to take the time.

n-elhk commented 2 weeks ago

Interestingly enough, calling the same method from ngOnDestory is working as it did before, because somehow the marker is removed only after ngDestory is being called and so the removal of the popup succeeds. I'm not sure what to do with this information. We can revert to using ngOnDestroy, but that's not a great solution. We can open a bug for angular, which I doubt will be solved in a timely manner. I can't think of a better solution. I'll sleep on it...

I investigated and found something surprising: destroyRef.ondestroy and ngOndestroy are not triggered at the same time.

It seems to me that the ngOndestroy life cycle is called when all its children have been destroyed.

whereas ondestroy is called as soon as the component is destroyed, regardless of the state of its children.

HarelM commented 2 weeks ago

I've observed this too, but I don't think this is the problem in this case. <mgl-marker> and <mgl-popup> are siblings in my case so there's no need to wait for a destroy of an inner child. I think this is a new warning by Angular, which we need to decide how to solve. The fact is that a popup is being destroyed and the close event is never unregistered... I think we have this problem for other registrations as well. A possible way to move this forward in a more infrastructure solution would be to allow receiving a subscription kind of return value from "on method" in maplibre-gl or to use rxjs' fromEvent more here... IDK, these are only some initial thoughts...

HarelM commented 1 week ago

@n-elhk do you remember why you changed the value of the second parameter below from false to true? https://github.com/maplibre/ngx-maplibre-gl/blob/04b99bb72d7510baba17bdf23d1f66de204fbe88/projects/ngx-maplibre-gl/src/lib/popup/popup.component.ts#L207

Was it the same warning in the console? I'm trying to figure out if and when a close event should be emitted if the popup is destroyed vs simply closed...