We have an app shell architecture, meaning a host app that loads several other apps on our page. This lets us maintain and deploy each app separately. The app shell routes to /app1 and App 1 will route to everything after /app1/.... Some of the loaded apps are using Navigation.program. When the path changes from /app1 to /app2, App 1 still listens to popstate events even though it should be idle.
There are a couple of ways to circumvent this from being a problem.
Removing all subscriptions. => Won't work. There is no way to tell the Navigation.program to stop listening to this event since the subscription is untouchable and always there https://github.com/elm-lang/navigation/blob/master/src/Navigation.elm#L73.
"Turning off" the App1 when routing away (with something like this update = (\model msg -> if model.isMounted then update model msg else (model, Cmd.none))) => would work but bloats the model and update function a little bit.
Making sure each app only parses routes that are within it's namespace (only parse stuff starting with /app1)
All these methods will just elimate the reaction to the event, rather than removing the actual event listener.
Thanks for the issue! Make sure it satisfies this checklist. My human colleagues will appreciate it!
Here is what to expect next, and if anyone wants to comment, keep these things in mind.
I actually found out that leaking stuff is not necessarily specific to multiple elm apps on the page, but more to reinitializing elm modules(calling Elm.<Module>.embed/fullscreen()). This can happen for example when using react-elm-components and mounting the component multiple times (i.e. by navigating to a part of the page where the component isn't shown and then back to where it is) if subscriptions are used without reloading the page.
I created an example here: https://ellie-app.com/3LHDqnYqD4za1/10
@ChristophP Almost a year later, but I just came up with a workaround for this issue; using web components, you can fire a custom event when the elm app is detached from the DOM. You could then create a custom program function that automatically disconnects subscriptions when the app is unmounted. I have not tested this extensively yet, but I can verify it fixes the ellie example you posted above. Here's the ellie:
Adding workaround for the memory leak where smaller Elm apps embedded in other things - https://github.com/Pilatch/angularjs-ng-elm#technical-stuff
@joefiorini I haven't thought of using web components callbacks yet but nice idea. I have been using something similar with ports and callbacks to clean up lingering subscriptions. But it would we cool if there was a function for elm that clobbers everything down on command. However now with 0.19 out, ther only seems to be a strong use case for this with Browser.element. The other ones will pretty much always live as long as the page does.
+1 Elm applications are a great candidate for stable drop-in components, and having no way to cleanup lingering listeners is very detrimental.
Is this still a thing in 0.19?
@FranzSkuffka Yup
Here's another use case:
I'm using Elm in a browser extension content script, so I inject a little Elm app on certain pages. This is happening on someone else's single-page-app, so when the user navigates away from the current view, I need to clean up my Elm stuff.
Up! I have a very similar situation as @ChristophP have.
I second the need/want for a way to completely stop, unsubscribe, ..., cleanup the Elm app on demand. My usecase is similar - SPA composed of various sub-apps (Elm, React, etc.).
I would suggest this being called terminate() instead of kill(). This mimics the Worker API and sounds less violent.
Most helpful comment
+1 Elm applications are a great candidate for stable drop-in components, and having no way to cleanup lingering listeners is very detrimental.