Pages
βIn Elmish Land, pages serve as the fundamental components of your application, each corresponding to a specific URL route. When a user navigates to a particular URL, Elmish Land determines the appropriate page to render based on the folder structure within your src/Pages
directory.
Already familiar with Elmish?
In a standard Elmish project, all URL requests often go to one big routing file. In Elmish Land, this file is generated for you automatically.
The big difference in Elmish Land is that all pages share data via SharedModel
and access type-safe URL information using the Route
type.
No need to write your URL parsers by hand!
Creating a New Pageβ
To add a new page to your Elmish Land project, follow these steps:
-
Generate the Page: Use the CLI command to create a new page. For example, to add an
About
page:dotnet elmish-land add page "/About"
This command creates a new file at
src/Pages/About/Page.fs
.π‘ Note: The page path should be in file system format
/MyProfile
and not URL format/my-profile
. -
Include the Page in Your Project: Manually add the new page to your project file (
MyProject.fsproj
) by inserting the following line within an<ItemGroup>
:<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="src/Shared.fs"/>
<Compile Include="src/Pages/NotFound.fs"/>
<Compile Include="src/Pages/Layout.fs"/>
<Compile Include="src/Pages/Page.fs"/>
<Compile Include="src/Pages/About/Page.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include=".elmish-land/Base/ElmishLand.MyProject.Base.fsproj" />
</ItemGroup>
</Project>F# projects require all sources to be listed in compilation order in an
.fsproj
file. This may look quite restrictive at first, but it does have some advantages.
The "add page" command generates src/Pages/About/Page.fs
with the following content:
module MyProject.Pages.About.Page
open Feliz
open ElmishLand
open MyProject.Shared
open MyProject.Pages
type Model = unit
type Msg =
| LayoutMsg of Layout.Msg
let init () =
(),
Command.none
let update (msg: Msg) (model: Model) =
match msg with
| LayoutMsg _ -> model, Command.none
let view (_model: Model) (_dispatch: Msg -> unit) =
Html.text "About Page"
let page (_shared: SharedModel) (_route: AboutRoute) =
Page.from init update view () LayoutMsg
Understanding the Page Structureβ
A typical page file (Page.fs
) includes the following components:
Model (the state of the page)β
The Model
contains the state of our page. Everytime a value changes on the page we need to update the Model
. An example
of this is typing text in a HTML input
element. For every change we need update the Model
with the new value from the
onChange
event of the element.
type Model = unit
Messages (events or actions on the page)β
The Msg
contains all the possible events of our page. Examples of events can be:
- The user clicks a button
- An API response from the server is received
- A timer event is triggered by the browser.
type Msg =
| LayoutMsg of Layout.Msg
You can read more about layouts on the Layouts page and more about LayoutMsg
on the Sending messages to pages section.
Initialization (the initial state of the page)β
This function is called anytime your page loads for the first time.
let init () =
(),
Command.none
You can read more about Command.none
on the Commands page.
Updates (state transitions in response to messages)β
This function is called whenever a message is sent. An example of this is a user clicking a button.
let update (msg: Msg) (model: Model) =
match msg with
| LayoutMsg _ -> model, Command.none
Rendering (the UI representation of the page)β
This function converts the current model to the HTML you want to show to the user.
let view (_model: Model) (_dispatch: Msg -> unit) =
Html.text "About Page"
Page (constructs the page for Elmish Land)β
The page
function is our starting point for the page. From this function we need to call the Page.from
function to setup our page.
let page (shared: SharedModel) (layout: MyProject.Pages.Layout.Model) (route: AboutRoute) =
Page.from init update view
Utilizing shared
and route
β
The page
function receives two parameters: shared
and route
.
-
shared: Provides access to the
SharedModel
, allowing data to be shared across all pages. -
route: Contains URL information, including parameters and query strings, enabling type-safe routing.
All of these values are available to any function within page
. That means init
, update
and view
all can get access to shared and route.
In the code example below, note how we pass the shared
value as the first argument of the view
function:
let page (shared: SharedModel) (layout: MyProject.Pages.Layout.Model) (route: AboutRoute) =
Page.from init update (view shared)
After we pass in the shared argument, we can update our view function to get access to shared in our view code:
let view (shared: SharedModel) (model: Model) (dispatch: Msg -> unit) =
Html.text "About Page"
The same concept applies to init
, update
, and subscriptions
.
For example, you might want your init
function to use a URL parameter to decide what API endpoint to call. In this case, we can pass route
into our init
function using the same process as before:
let page (shared: SharedModel) (route: AboutRoute) =
Page.from (fun () -> init route) update view
After we pass in the route
argument, we can update our init
function to get access to route
in our view code:
let init (route: AboutRoute) =
(),
Command.none