In a ‘single page application’ that is managed by Elm, we might still have multiple routes (So the name ‘single page app’ is maybe somewhat of a misnomer), each pointing to a different page. However, on each of these pages, we might actually want to show different information.
Now, I am wondering what common patterns are to manage the change in state between these pages. I can think of some, which I will describe below, but I’d really like to know what patterns you came up with, and what their advantages and drawbacks are.
Simple: Throw away current page data whenever switching pages. (re-)request information every time you visit a given page. Very simple to implement, but might take a long time to load pages.
Lazy Tree: Give every page its own section of the model (For static pages, a type with multiple constructors. For dynamic pages (like when you are on a page showing a single instance of a data structure), store them in a Dict. All data, once loaded, is cached. We depend on changes to this data to be pushed to us once they happen (using e.g. Websockets) Might lead to high memory usage, but is very fast once the information is loaded.
Cache of Recent Pages: Give every page an entry in a list (or other bounded data strucure) of a maximum size. When we change pages, the new page’s data is added to the front, and the oldes element in the list is dropped. recently-visited pages are therefore still in this cache, but we do not ever-increase memory usage.
Can you clarify what you mean by this? As far as I can see, the information downloaded from the servers is in the order of MB while the memory available is in the order of GB. You could easily store in the memory ALL the relevant database data for most use-case scenarios I can think of.
Please tell us about your approaches!
Another approach is a hybrid between 1 and 2. You keep a session record of all the data you frequently need on your pages and you throw away all the accidental data for the pages. When you switch to a page you issue a request for new data with the timestamp of the last request and you update the session upon receiving the new data. This allows you to present your users with old data that might still be relevant and inform them that the data is being refreshed using a spinner (or something similar).
elm-taco is one implementation of something that is very similar to the above approach.
Is option 1 in combination with service workers an option? Then you can keep your code simple and let the service worker worry about the caching of data.
Speaking from experience, there are performance limitations to this. We made a dynamic form system with a front end written in Elm. Individual form submissions were rather large, heavily nested records. Once we got to around 5,000 submissions, paginating through the table that displayed them in pages of 20 slowed down significantly. At 30,000 submissions, the entire Elm UI would be very sluggish if it at all performed manipulations on the Dict that stored the submissions.
Eventually we solved the problem by converting the form submission display to be more server-heavy, where only one page of submissions is downloaded at a time, and a maximum of 25 pages (500 submissions) stays “cached” in the front end before pages start getting dropped.
+1 on the Elm taco approach. Ive used it a bunch of times (like here: https://github.com/Chadtech/CtPaint-Desktop/blob/master/source/Model.elm#L15). Basically the main model is always split into a Page and a Taco, where the Page contains page specific information, and the Taco contains everything thats globally relevant. Then I also pass in the Taco to every page level update and view function.
You mention keeping each page as a separate and concurrent field in your model. That can work, but it depends on what you are doing. Do you really want to persist all page data all the time? Some times yes, such as if you have a form with fields broken down into categories or steps. But generally its the assumed “no” in the context of an SPA.
I have not worked with ServiceWorkers before. How would this look; could you maybe create some Elm example code?
I really like the Taco approach, and @pdamoc is completely right: A hybrid version where some part of the state is ‘global’ while another is page-specific is probably the way to go.