# "Route reuse" + looking for general advice

**URL:** <https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826>\
**Category:** Learn\
**Created:** [October 16, 2021, 9:33am UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826 "2021-10-16T09:33:56Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![benjamin-thomas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/benjamin-thomas/32/3869_2.png) [@benjamin-thomas](https://discourse.elm-lang.org/u/benjamin-thomas)\
**Post date:** [October 16, 2021, 9:33am UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/1 "2021-10-16T09:33:56Z")

</div>

Hello elm coders,

I’ve been digging my teeth into the Angular framework lately and would like to explore the Elm way of doing things for the next few days.

The functionality I’d like to reproduce can be seen here, [in this stackblitz](https://angular-ivy-vrpb4j.stackblitz.io).

This demo represents the main features I’m looking for in a front end technology:

- when hitting the root route, you are redirected to `/form`
- visiting the page simulates triggering a long and expensive HTTP call without blocking the UI
- when filling the form inputs, their values are reflected in the URL
  - this allows one to share links and recover the form state from the URL later

- when clicking on “GO TO LIST” you go to another page
  - the previous form page stays in memory for later restoration

- when clicking on “GO TO FORM”:
  - the form state is optionally restored if “Keep form data” was checked
  - the expensive HTTP is not triggered anymore since we already have the data
  - the whole page is cached, **not** the HTTP call.

The reason I want to cache the whole page is that restoring the UI from cached HTTP calls does not make for a good user experience IMO (at least in Angular). The problem being that there is a noticeable lag and jitter (maybe between 10 ms to 50ms) when rebuilding a complex page. Instead, I prefer to cache the whole page for **instant** feedback upon revisiting a page, while at the same time refreshing the data from new HTTP calls. That way, the user interface feels very snappy and almost desktop app like.

In Angular, I can obtain this behavior by making use of a tricky technique: implementing a custom “route reuse strategy”. With this technique, a previously built page object can be restored from a global cache. But using such a technique can open one up to all sort of bugs and hard to manage life cycle state (that can lead to unintended memory leaks, etc.).

So that’s why I’d like to explore Elm again. I went through 75% of the guide a few months ago and I’m looking for practical advice and what to learn to be able to achieve this demo. And maybe if I should use any libraries such as this one: [elm-router 2.0.0](https://package.elm-lang.org/packages/danhandrea/elm-router/latest/)

Thanks for any feedback

---

<div class="post-metadata">

**Author:** ![NicolasGuilloux](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/nicolasguilloux/32/3063_2.png) [@NicolasGuilloux](https://discourse.elm-lang.org/u/NicolasGuilloux)\
**Post date:** [October 17, 2021, 7:53am UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/2 "2021-10-17T07:53:42Z")

</div>

As far as I can understand, it looks like you want an SPA application where Elm has control over the whole UI.

About reusing the data and every thing, I thing it is as simple as storing the models of both view in separate entries, and just display the appropriate view based on the current route.

I’m on my phone, so it’s hard to type some code and may introduce some typos, but I see something like

```auto
type alias Model =
   { formModel : FormModel
   , listModel : ListModel
   , currentRoute : Route
  }

type Route
    = FormRoute
    | ListRoute

```

Use the Browser.Navigation lib to control the Url, and in your view, just use the appropriate view based on the value of `currentRoute`.

---

<div class="post-metadata">

**Author:** ![benjamin-thomas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/benjamin-thomas/32/3869_2.png) [@benjamin-thomas](https://discourse.elm-lang.org/u/benjamin-thomas)\
**Post date:** [October 20, 2021, 9:18pm UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/3 "2021-10-20T21:18:18Z")

</div>

Thanks for the feedback @NicolasGuilloux

So yes indeed, I’m looking for a SPA. I now understand I need to use `Browser.application` for my needs.

I went through the guides once more as I had to refresh my memory on how one works with Elm.

Here is what I’ve got so far:

(ellie-app doesn’t seem to handle `Browser.application` unfortunately so no demo)

```elm
module Main exposing (main)

import Browser exposing (Document, UrlRequest(..))
import Browser.Navigation as Nav
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import Url exposing (Url)
import Url.Parser as UP

-- ROUTING

type Route
    = FormPage
    | ListPage

routeParser : UP.Parser (Route -> a) a
routeParser =
    UP.oneOf
        [ UP.map FormPage (UP.s "form")
        , UP.map ListPage (UP.s "list")
        ]

-- MAIN

main : Program () Model Msg
main =
    Browser.application
        { init = init
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.none
        , onUrlRequest = ClickLink
        , onUrlChange = ChangeUrl
        }

-- MODEL

type alias FormModel =
    { firstName : String
    , lastName : String
    }

type alias Model =
    { key : Nav.Key
    , route : Maybe Route
    , formModel : FormModel
    }

init : () -> Url -> Nav.Key -> ( Model, Cmd Msg )
init _ url key =
    ( { key = key, route = UP.parse routeParser url, formModel = FormModel "" "" }, Cmd.none )

-- UPDATE

type Msg
    = ChangeUrl Url
    | ClickLink UrlRequest
    | FirstNameChanged String
    | LastNameChanged String

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ChangeUrl url ->
            ( { model | route = UP.parse routeParser url }, Cmd.none )

        ClickLink urlRequest ->
            case urlRequest of
                Internal url ->
                    ( model, Nav.pushUrl model.key (Url.toString url) )

                External url ->
                    ( model, Nav.load url )

        FirstNameChanged val ->
            ( { model | formModel = FormModel val model.formModel.lastName }, Cmd.none )

        LastNameChanged val ->
            ( { model | formModel = FormModel model.formModel.firstName val }, Cmd.none )

-- VIEW

view : Model -> Document Msg
view model =
    let
        navBar =
            let
                padding =
                    style "margin" "20px"
            in
            div []
                [a [ padding, href "/form"] [text "Form"]
                , a [padding, href "/list"] [text "List"]
                ]

        content =
            case model.route of
                Just a ->
                    case a of
                        FormPage ->
                            viewForm model.formModel

                        ListPage ->
                            viewList

                Nothing ->
                    p [] [text "Oops"]
    in
    { title = "URL handling example"
    , body =
        [ navBar
        , content
        ]
    }

viewForm : FormModel -> Html Msg
viewForm fm =
    div []
        [h1 [] [text "A form"]
        , Html.form []
            [input [ type_ "text", value fm.firstName, onInput FirstNameChanged] []
            , input [type_ "text", value fm.lastName, onInput LastNameChanged] []
            ]
        ]

viewList : Html msg
viewList =
    div []
        [h1 [] [text "A list"]
        , div []
            [ul []
                [li [] [text "A"]
                , li [] [text "B"]
                , li [] [text "C"]
                ]
            ]
        ]

```

I’ve got the form values sticking in memory alright.

I’m now looking at syncing the URL with the form state. Any idea on how to go about this in Elm?

I also would like to “simulate” an HTTP request. Is this possible in Elm?

In Angular, one can do this:

```auto
// All those functions come from the RXJS library.
// This lib is integrated into Angular.
// `slowRequest$` is an "Observable"
// `tap` is for side effects
slowRequest$ = of('A SERVER MESSAGE').pipe(
    delay(5000),
    tap(_ => this.showSpinner = false),
  );

```

Then in my template I would subscribe to this observable via an “async pipe”.  
Basically the framework handles the resource cleanup under the hood (subscribing/unsubscribing).

```auto
    <h4>Slow request:
      {{ slowRequest$ | async }}
      <app-spinner *ngIf="showSpinner"></app-spinner>
    </h4>

```

Once I’m ready to work on the backend code, I can then swap `slowRequest$` with a real HTTP observable.

It’s a very handy feature, it’d be great if Elm had a similar workflow.

---

<div class="post-metadata">

**Author:** ![wolfadex](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/wolfadex/32/2461_2.png) [@wolfadex](https://discourse.elm-lang.org/u/wolfadex)\
**Post date:** [October 20, 2021, 11:59pm UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/4 "2021-10-20T23:59:04Z")

</div>

I know I’ve seen packages or such for mocking http in Elm, but my searching is coming up short. Another option is to use something like [https://miragejs.com/](https://miragejs.com/) for mocking endpoints. You can simulate sending/receiving values from the back end, timeouts, whatever you want.

---

<div class="post-metadata">

**Author:** ![jerith](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/jerith/32/114_2.png) [@jerith](https://discourse.elm-lang.org/u/jerith)\
**Post date:** [October 21, 2021, 1:28am UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/5 "2021-10-21T01:28:18Z")

</div>

I’ve found the `onUrlRequest` / `onUrlChange` bifurcation in [`Browser.application`](https://package.elm-lang.org/packages/elm/browser/latest/Browser#application) to be really tedious to work with. It’s better than it was in 0.18, but still doesn’t feel natural to me. See more discussion in this thread:

> [@Understanding the "double update" behavior of Browser.application](https://discourse.elm-lang.org/t/understanding-the-double-update-behavior-of-browser-application/2963/1):
>
> Hi all, I’m working upgrading to Elm 0.19. Our main was using Navigation.programWithFlags and I’ve shifted to Browser.application. In my efforts to understand what was needed, I was reviewing experience reports like [@jerith 's update experience report.](https://discourse.elm-lang.org/t/a-0-19-update-experience-report/2593). I wanted to understand a line in here: elm-route-url does seem to still be necessary: Browser.application still has a “double-update problem”. I have realized that I don’t fully understand the impact of switching directly to Browser.applica…

Since that thread, I’ve completed [updating `elm-route-url` to 0.19](https://github.com/rgrempel/elm-route-url/pull/38) (but it hasn’t been merged yet).

I’ve also built upon that to create a feature I call “anchor management”. I’ve been using it in my app for a while now and really like it.

The basic idea is that your `view` function gets a `msg -> ... -> Html msg` helper function that it can use to make `<a/>` elements that correspond to a `msg` in your app. These special `<a/>` elements that it creates interact with your `update` function much more cleanly, their target URLs are automatically generated using the other functions you’ve already provided to `RouteUrl`, etc. No more choosing the lesser evil between `a href` and `div onClick`. 🙂

Unfortunately I haven’t had time to write documentation for it, so you’ll have to see what more you can gather from the [code changes](https://github.com/jerith666/elm-route-url/commit/01f22f1164c1ef71a07ed7ab48c6f051f354d7cd) and my [sandbox test code](https://github.com/jerith666/elbum/blob/dev/src/client/src/Sandbox/AnchorTest.elm).

---

<div class="post-metadata">

**Author:** ![benjamin-thomas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/benjamin-thomas/32/3869_2.png) [@benjamin-thomas](https://discourse.elm-lang.org/u/benjamin-thomas)\
**Post date:** [October 21, 2021, 7:52pm UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/6 "2021-10-21T19:52:55Z")

</div>

Thanks. I was more looking for something at the language level, something quick and simple for rapid prototyping.

The testing story demonstrated on their site in the “Write UI tests” section looks pretty sweet though 🙂

---

<div class="post-metadata">

**Author:** ![benjamin-thomas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/benjamin-thomas/32/3869_2.png) [@benjamin-thomas](https://discourse.elm-lang.org/u/benjamin-thomas)\
**Post date:** [October 21, 2021, 8:21pm UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/7 "2021-10-21T20:21:29Z")

</div>

I don’t think I fully understand that double update problem. And couldn’t understand nor compile your code unfortunately ☹

I’ve managed to initialize my form values from the URL but it’s buggy: if I change one form input, the other input reverts to its init state, from before the first `ChangeUrl` message was called.

So it seems both `init` and `ChangeUrl` compete for data access in a problematic way for me here.

In fact, using `Browser.application` seems to lead me to a dead end here as `Browser.application` requires me to initialize my `formModel` via `init`, but I want to initialize its state from the URL via `ChangeUrl` which comes after `init`.

Is this what you mean by `onUrlRequest` / `onUrlChange` being tedious to work with?

On a side note, since posting I stumbled upon this cool project: [https://www.elm-spa.dev/](https://www.elm-spa.dev/)

So hopefully I’ll find some intersting ideas there.

```elm
module Main exposing (main)

import Browser exposing (Document, UrlRequest(..))
import Browser.Navigation as Nav
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import Url exposing (Url)
import Url.Parser as UP exposing ((<?>))
import Url.Parser.Query as Q

-- Next, I need to do this:
-- history.pushState(null, '', '/#form?a=b')
--
--
-- ROUTING

type alias FirstName =
    String

type alias LastName =
    String

type Route
    = FormPage (Maybe FirstName) (Maybe LastName)
    | ListPage

routeParser : UP.Parser (Route -> b) b
routeParser =
    UP.oneOf
        [ UP.map FormPage <| UP.s "form" <?> Q.string "fName" <?> Q.string "lName"
        , UP.map ListPage (UP.s "list")
        ]

-- MAIN

main : Program () Model Msg
main =
    Browser.application
        { init = init
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.none
        , onUrlRequest = ClickLink
        , onUrlChange = ChangeUrl
        }

-- MODEL

type alias FormModel =
    { firstName : String
    , lastName : String
    , dirty : Bool
    }

type alias Model =
    { key : Nav.Key
    , route : Maybe Route
    , formModel : FormModel
    }

init : () -> Url -> Nav.Key -> ( Model, Cmd Msg )
init _ url key =
    ( { key = key
      , route = UP.parse routeParser url
      , formModel = FormModel "A" "B" False
      }
    , Cmd.none
    )

-- UPDATE

type Msg
    = ChangeUrl Url
    | ClickLink UrlRequest
    | ChangeFirstName String
    | ChangeLastName String

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ChangeUrl url ->
            ( { model | route = UP.parse routeParser url }
            , Cmd.none
            )

        ClickLink urlRequest ->
            case urlRequest of
                Internal url ->
                    ( model
                    , Nav.pushUrl model.key (Url.toString url)
                    )

                External url ->
                    ( model, Nav.load url )

        ChangeFirstName val ->
            ( { model | formModel = FormModel val model.formModel.lastName True }
            , Cmd.none
            )

        ChangeLastName val ->
            ( { model | formModel = FormModel model.formModel.firstName val True }
            , Cmd.none
            )

-- VIEW

view : Model -> Document Msg
view model =
    let
        navBar =
            let
                padding =
                    style "margin" "20px"
            in
            div []
                [a [ padding, href "#/form"] [text "Form"]
                , a [padding, href "#/list"] [text "List"]
                ]

        content =
            case model.route of
                Just a ->
                    case a of
                        FormPage fn ln ->
                            viewForm model.formModel fn ln

                        ListPage ->
                            viewList

                Nothing ->
                    p [] [text "Kinda 404"]
    in
    { title = "Just a SPA"
    , body =
        [ navBar
        , content
        ]
    }

selectFormValue : Bool -> String -> Maybe String -> String
selectFormValue dirty dirtyVal initVal =
    if dirty then
        dirtyVal

    else
        case initVal of
            Just val ->
                val

            Nothing ->
                ""

viewForm : FormModel -> Maybe FirstName -> Maybe LastName -> Html Msg
viewForm fm initFn initLn =
    let
        fnVal =
            selectFormValue fm.dirty fm.firstName initFn

        lnVal =
            selectFormValue fm.dirty fm.lastName initLn
    in
    div []
        [h1 [] [text "A form"]
        , Html.form []
            [input [ type_ "text", value fnVal, onInput ChangeFirstName] []
            , input [type_ "text", value lnVal, onInput ChangeLastName] []
            ]
        ]

viewList : Html msg
viewList =
    div []
        [h1 [] [text "A list"]
        , ul []
            [li [] [text "A"]
            , li [] [text "B"]
            , li [] [text "C"]
            ]
        ]

```

---

<div class="post-metadata">

**Author:** ![Enkidatron](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/enkidatron/32/1976_2.png) [@Enkidatron](https://discourse.elm-lang.org/u/Enkidatron)\
**Post date:** [October 21, 2021, 8:51pm UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/8 "2021-10-21T20:51:08Z")

</div>

If you already had the `Msg` that is going to be used with the HTTP request, you could use `Process.sleep`, `Task.andThen`, and `Task.succeed`, like such, no library required:

```elm
type Msg
    = ... 
    | RxHttpResponse (Result Http.Error String)
    -- ^ could be used with Http.expectString. 
    -- Change as appropriate if you plan to use Http.expectJson or Http.expectBytes

fakeHttpRequest : Cmd Msg
fakeHttpRequest = 
    Process.sleep 5000 
        |> Task.andThen (\_ -> Task.succeed "Mocked String body of response: A SERVER MESSAGE")
        |> Task.attempt RxHttpResponse

```

If you know the parameters that you will be passing into your Http request when it’s ready, you can even change the type signature of the `fakeHttpRequest` function to match that:

```elm
fakeHttpRequest2 : Key -> Cmd Msg
fakeHttpRequest2 key = 
    Process.sleep 5000 
        |> Task.andThen (\_ -> Task.succeed ("Mocked String body of response for key: " ++ Key.toString key))
        |> Task.attempt RxHttpResponse

```

If you’d like to simulate the call failing after five seconds, you can replace `Task.succeed` with `Task.fail`.

---

<div class="post-metadata">

**Author:** ![benjamin-thomas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/benjamin-thomas/32/3869_2.png) [@benjamin-thomas](https://discourse.elm-lang.org/u/benjamin-thomas)\
**Post date:** [October 23, 2021, 7:37am UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/9 "2021-10-23T07:37:39Z")

</div>

Thanks @Enkidatron, that’s exactly what I was looking for!

I don’t understand the `Key` concept yet but I’ll look into it.

I had to fiddle about to make the failure case compile. There’s probably a more general concept on top of `Json.Encode/Decode` I could use:

```elm
fakeHttpRequestFail : Cmd Msg
fakeHttpRequestFail =
    Process.sleep 5000
        |> Task.andThen (\_ -> Task.fail (Json.Decode.Failure "Oops" (Json.Encode.object [])))
        |> Task.attempt RxHttpResponse

```

`elm-spa` looks promising but I’ll have to keep digging…

---

<div class="post-metadata">

**Author:** ![benjamin-thomas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/benjamin-thomas/32/3869_2.png) [@benjamin-thomas](https://discourse.elm-lang.org/u/benjamin-thomas)\
**Post date:** [October 28, 2021, 5:54pm UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/10 "2021-10-28T17:54:31Z")

</div>

Ok, I’ve had a bit of success with `elm-spa` which simplified quite a lot of stuff for me.

I’d appreciate a little bit of feedback regarding [this demo](https://github.com/benjamin-thomas/elm-spa-caching-demo/tree/dd3c35a1da39ac4fb1fc3cf9f1d5ebc3c9006f77). If somebody knows how to easily share a runnable example, please let me know.

Here, I’m only caching data at `/form`. That’s ok but I still would like to not re-render `/form` each time I re-visit it. I’m not sure what I would need though, maybe a special router?

I also want to synchronize the form state in the URL. I haven’t found much on the subject so I suppose I’ll have to implement that feature through ports. Any other ideas?

Thanks 🙂

---

<div class="post-metadata">

**Author:** ![benjamin-thomas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/benjamin-thomas/32/3869_2.png) [@benjamin-thomas](https://discourse.elm-lang.org/u/benjamin-thomas)\
**Post date:** [October 30, 2021, 7:03pm UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/11 "2021-10-30T19:03:26Z")

</div>

Ok for the record, I managed to implement my little demo [here](https://github.com/benjamin-thomas/elm-spa-caching-demo/commits/master) (based on elm-spa)

To sync the URL with the form state I used a port and that worked very well.

I also managed to store the computed view (which is a feature I would want for performance reason), albeit it feels a bit clunky as-is.

So far so good with Elm, gotta keep digging now 🙂

---

<div class="post-metadata">

**Author:** ![Enkidatron](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/enkidatron/32/1976_2.png) [@Enkidatron](https://discourse.elm-lang.org/u/Enkidatron)\
**Post date:** [November 2, 2021, 6:21pm UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/12 "2021-11-02T18:21:15Z")

</div>

Sorry to unnecessarily complicate things with the `Key` data type. I just imagined it as a type-safe wrapper around a `String`, like such:

```elm
module Key exposing (Key, toString, ...)

type Key = Key String

toString : Key -> String
toString (Key innerKey) = innerKey

```

A type-safe wrapper like this would prevent you from mixing up this string with other strings. In retrospect, `Key` was not the best name for it.

If you had, for example, both “Post IDs” and “User IDs”, then you could make each of them be their own wrapped type, and then the compiler would prevent you from every mixing them up.

---

<div class="post-metadata">

**Author:** ![benjamin-thomas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/benjamin-thomas/32/3869_2.png) [@benjamin-thomas](https://discourse.elm-lang.org/u/benjamin-thomas)\
**Post date:** [November 2, 2021, 8:42pm UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/13 "2021-11-02T20:42:19Z")

</div>

Oh that’s much clearer now.

I thought you were referring to an internal concept at the time 😉

Thanks for your help

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [November 12, 2021, 8:42pm UTC](https://discourse.elm-lang.org/t/route-reuse-looking-for-general-advice/7826/14 "2021-11-12T20:42:22Z")

</div>

Closed on November 12, 2021, 8:42pm UTC

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.
