How can one page emit a message to navigate to another page in Elm?

371 Views Asked by At

Here's how my Elm app is currently structured:

Types.elm:

import Pages.Login.Types as Login
import Pages.Dashboard.Types as Dashboard

type Page = LoginPage
          | DashboardPage

type Msg = LoginMsg Login.Msg
         | DashboardMsg Dashboard.Msg
         | NavigationStart Page
         | NavigationEnd Page

type Model = LoginModel Login.Model
           | DashboardModel Dashboard.Model

Login.elm:

import Pages.Login.Types as PageTypes
import Types

view : (PageTypes.Msg -> msg) -> PageTypes.Model -> Html msg
view = -- some code 

I'm stuck with the following, seemingly, competing requirements:

  • Trying to keep the pages fairly independent of each other, where their Msg and Model types can be reasoned about independently
  • Making pages aware of each other's existence (at the type-level) so that their view/update functions can emit the NavigationStart page msg to navigate between each other.

What is the best way to achieve this in Elm?

2

There are 2 best solutions below

0
On

If you want to issue top level navigation messages from a sub-page's view, there's nothing wrong with concretely returning the top level message type, such as:

 view : Login.Model -> Html Types.Msg

If you insist on abstracting the message type as indicated, you can pass an extra argument for the navigation messages:

view : (Types.Page -> msg) -> (Login.Msg -> msg) -> Login.Model -> Html msg
view navigateTo wrapPageMsg model = ...

and have the top level view function pass NavigationStart as the first argument.

Finally, if you need the sub-page's update to be able to trigger top-level navigation, you can put that information in the return value:

-- Login.update, updates the model and
-- optionally returns a navigation destination
update : Login.Msg -> Login.Model -> (Login.Model, Maybe Types.Page)

-- top level update
update : Msg -> Model -> Model
update msg model =
    let
        -- navigation helper
        navigateTo page model = ...
    in
    case (msg, model) of
        (NavigationStart p, _) ->
            navigateTo p model
        (LoginMsg lmsg, LoginModel lmodel) ->
            let
                (newlmodel, navigate) = Login.update lmsg lmodel
            in
            case navigate of
                Just p ->
                    navigateTo p model
                Nothing ->
                    LoginModel newlmodel
        ...

In general: It's fine to adapt the types of the view and update functions for you sub-pages to fit your specific requirements!

0
On

Check out Richard Feldman's single page example repo here. Essentially you have a top level app, that manages each model\view\update per page.