Fabulous: Static Views and “Half Elmish”

In some circumstances there are advantages to using static Xaml, and static bindings from the model to those views. This is called “Half Elmish” and is the primary technique used by Elmish.WPF at time of writing. (It was also the original technique used by this repo and the prototype Elmish.Forms).

“Half Elmish” is a pragmatic choice to allow, but doesn’t provide the same level of cognitive-simplicity. In the words of Jim Bennett:

As a C#/XAML dev I really like the half Elmish model. I’m comfortable with XAML so like being able to use the Elmish bits to create a nice immutable model and have clean code, but still using XAML and binding as I’m comfortable there. This feels more like how existing C# Xamarin devs would move to F#. Full elmish is how F# devs will move to Xamarin.

Static Xaml + bindings has signifcant pros:

If you want to use static Xaml, then you will need to do bindings to that View. Bindings in your XAML code will look like typical bindings, but a bit of extra code is needed to map those bindings to your Elmish model. These are the viewBindings, which expose parts of the model to the view.

Here is a full example (excluding Xaml):

namespace CounterApp

open Fabulous.Core
open Fabulous.StaticViews
open Xamarin.Forms

type Model =
  { Count : int
    Step : int }

type Msg =
    | Increment
    | Decrement
    | Reset
    | SetStep of int

type CounterApp () =
    inherit Application ()

    let init () = { Count = 0; Step = 3 }

    let update msg model =
        match msg with
        | Increment -> { model with Count = model.Count + model.Step }
        | Decrement -> { model with Count = model.Count - model.Step }
        | Reset -> init ()
        | SetStep n -> { model with Step = n }

    let view () =
        CounterPage (),
        [ "CounterValue" |> Binding.oneWay (fun m -> m.Count)
          "CounterValue2" |> Binding.oneWay (fun m -> m.Count + 1)
          "IncrementCommand" |> Binding.msg Increment
          "DecrementCommand" |> Binding.msg Decrement
          "ResetCommand" |> Binding.msgIf Reset (fun m -> m <> init ())
          "ResetVisible" |> Binding.oneWay (fun m ->  m <> init ())
          "StepValue" |> Binding.twoWay (fun m -> double m.Step) (fun v -> SetStep (int (v + 0.5))) ]

    let runner =
        Program.mkSimple init update view
        |> Program.withConsoleTrace
        |> Program.runWithStaticView

    do base.MainPage <- runner.InitialMainPage

There are helper functions to create bindings located in the Binding module:

The string piped to each binding is the name of the property as referenced in the XAML binding.