-ish
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:
setState
function to produce a view from the model. See theProgram
module 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 thebasics example for details.
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:
program
requests the initial model from the parent, top-level component (Main
)Widget
)Widget.initialModel
returns its initial model to the parentMain.initialModel
wraps child's model and returns the top-level initial model to the programMain.view
Widget.view
Widget.view
returns a renderedHTML
pageMain.view
embeds child'sHTML
page in itsHTML
pageHTML
page is send to the userThen the user interacts with the browser:
Widget.view
dispatches anIncrease
messageMain.view
has augemented thedispatch
so the message becomesWidgetMsg Increase
as it is sent along toprogram
program
callsMain.update
with this message andmainModel
WidgetMsg
,Main.update
delegates the update toWidget.update
, sending along the way thewidgetModel
part ofmainModel
Widget.update
modifies the model according to the given message, in this caseIncrease
, and returns the modifiedwidgetModel
plus a commandMain.update
returns the updatedmainModel
toprogram
program
then renders the view again passing the updatedmainModel
See theexample for details.
Tasks such as reading a database or making a Web API call are performed usingasync
andpromise
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 theinit
andupdate
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:
Program
calls theMain.update
with a messageMain.update
does its own update and/or delegates toChild.update
Child.update
does its own update and/or delegates toGrandChild.update
GrandChild.update
returns with its model andCmd
(of GrandChild message type)Child.update
processes GrandChild's model and maps itsCmd
intoCmd
of Child's message type and batches it with its ownCmd
, if anyMain.update
processes Child's model and maps itsCmd
intoCmd
of Main's message type and batches it with its ownCmd
, if anyHere we collect commands from three different levels. At the end we send all these commands to ourProgram
instance to run.
See theCmd
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 thesubscriptions example for details.
The core is independent of any particular technolgy, instead relying on a renderer library to implementsetState
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 seeelmish-react.
Larger Elmish applications for the browser may benefit from advanced features like routing and explicit navigation control.
For information about these features please seeelmish-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". UsewithTermination
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.
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: And start seeing the state and messages as updates happen in the browser developer console. For more advanced debugging capabilities please seeelmish-debugger.openElmishProgram.mkProgram init update view|> Program.withConsoleTrace|> Program.run