a suffix used to convey the sense of “having some characteristics of”
Elmish implements core abstractions that can be used to build F# applications following the “model view update” style of architecture, as made famous by Elm.
The goal of the architecture is to provide a solid UI-independent core to build the rest of the functionality around. Elm architecture operates using the following concepts, as they translate to Elmish:
This is a snapshot of your application's state, defined as an immutable data structure.
This an event representing a change (delta) in the state of your application, defined as a discriminated union.
This is a carrier of instructions, that when evaluated may produce one or more messages.
This is a pure function that produces the inital state of your application and, optionally, commands to process.
This is a pure function that produces a new state of your application given the previous state and, optionally, new commands to process.
This is a pure function that produces a new UI layout/content given the current state, defined as an F# function that uses a renderer (such as React) to declaratively build a UI.
This is an opaque data structure that combines all of the above plus a
setStatefunction to produce a view from the model. See the
Programmodule for more details.
dotnet add package Fable.Elmish
Once started, Program runs a dispatch loop, producing a new Model given the current state and an input Message.
See the basics example for details.
Parent-child composition and user interaction
Parent-child hierarchy is made explicit by wrapping model and message types of the child with those of the parent.
Following diagrams show interactions between components in case of a user interacting with an example web app. Note that Elmish doesn't depend on any specific UI such as HTML rendering (it actually does not require any UI at all), and HTML is used just for explanation purposes.
First the UI is initialised:
programrequests the initial model from the parent, top-level component (
- Parent component requests the initial model from its child subcomponent (
Widget.initialModelreturns its initial model to the parent
Main.initialModelwraps child's model and returns the top-level initial model to the program
- Program sends the model to the parent's
- Parent unwraps the child's component model from its model and sends it to child's
Widget.viewreturns a rendered
HTMLpage in its
- The resulting
HTMLpage is send to the user
Then the user interacts with the browser:
- User clicks on the increase button
Main.viewhas augemented the
dispatchso the message becomes
WidgetMsg Increaseas it is sent along to
Main.updatewith this message and
- As the message was tagged with
Main.updatedelegates the update to
Widget.update, sending along the way the
Widget.updatemodifies the model according to the given message, in this case
Increase, and returns the modified
widgetModelplus a command
Main.updatereturns the updated
programthen renders the view again passing the updated
See the example for details.
Tasks and side-effects
Tasks such as reading a database or making a Web API call are performed using
promise blocks or just plain functions. These operations may return immediately but complete (or fail) at some later time. To feed the results back into the dispatch loop, instead of executing the operations directly, we instruct Elmish to do it for us by wrapping the instruction in a command.
Commands are carriers of instructions, which you issue from the
update functions. Once evaluated, a command may produce one or more new messages, mapping success or failure as instructed ahead of time. As with any message dispatch, in the case of Parent-Child composition, child commands need to be mapped to the parent's type:
Main.updatewith a message
Main.updatedoes its own update and/or delegates to
Child.updatedoes its own update and/or delegates to
GrandChild.updatereturns with its model and
Cmd(of GrandChild message type)
Child.updateprocesses GrandChild's model and maps its
Cmdof Child's message type and batches it with its own
Cmd, if any
Main.updateprocesses Child's model and maps its
Cmdof Main's message type and batches it with its own
Cmd, if any
Here we collect commands from three different levels. At the end we send all these commands to our
Program instance to run.
Cmd module for ways to construct, map and batch commands.
Most of the messages (changes in the state) will originate within your code, but some will come from the outside, for example from a timer or a websocket. These sources can be tapped into with subscriptions, defined as F# functions that can dispatch new messages as they happen.
See the subscriptions example for details.
The core is independent of any particular technolgy, instead relying on a renderer library to implement
setState in any way seen fit. In fact, an Elmish app can run entirely without a UI!
At the moment, there are two UI technologies for which rendering has been implemented: React and React Native.
For details please see elmish-react.
Interacting with a browser
Larger Elmish applications for the browser may benefit from advanced features like routing and explicit navigation control.
For information about these features please see elmish-browser.
Hot reloading requires the new version of the application loop to be started. To faciliate the interaction with libraries that implement this functionality the v4 elmish extends the abstractions with "termination". Use
withTermination function to specify the predicate that can evaluate incoming messages and decide if the dispatch loop should stop processing the messages, as well as specify how to release the resources.
Observing the state changes
Every message going through the dispatch loop can be traced, along with the current state of the app. Just augument the program instance with a trace function:
open Elmish Program.mkProgram init update view |> Program.withConsoleTrace |> Program.run
And start seeing the state and messages as updates happen in the browser developer console.
For more advanced debugging capabilities please see elmish-debugger.