torhovland / flatware

Type-safe F# state management (like Elm and Redux) for Blazor
Apache License 2.0
31 stars 2 forks source link

Flatware for Blazor

OBSOLETE! Flatware has been merged with Blazor-Redux, which provides an improved design more like Redux, and supports F# and C# equally.

Flatware is a state management library for Blazor, similar to Elm and Redux. It has the following features:

Getting started

  1. Assuming you have Visual Studio 15.7 or newer and the Blazor tooling installed, create a new standalone Blazor project.

  2. Add a .NET Standard F# class library to the solution.

  3. Add a reference from the Blazor project to the F# project.

  4. Add the Flatware NuGet package to both projects.

  5. In Library.fs, add your message type, model types, and your component base class with the reducer logic:

open System
open System.Net.Http
open Microsoft.AspNetCore.Blazor
open Microsoft.AspNetCore.Blazor.Components
open FSharp.Control.Tasks
open Flatware

type MyMsg =
    | Increment of n : int
    | LoadWeather

type WeatherForecast() =
    member val Date = DateTime.MinValue with get, set
    member val TemperatureC = 0 with get, set
    member val TemperatureF = 0 with get, set
    member val Summary = "" with get, set

type MyMdl = { Count : int; Forecasts : WeatherForecast list } with
    static member Init = { Count = 0; Forecasts = [] }

type MyAppComponent() =
    inherit FlatwareComponent<MyMsg, MyMdl>()

    [<Inject>]
    member val Http = null : HttpClient with get, set

    override this.ReduceAsync(msg : MyMsg, mdl : MyMdl) =
        task {
            match msg with
                | Increment n -> 
                    return { mdl with Count = mdl.Count + n }
                | LoadWeather -> 
                    let! forecasts = this.Http.GetJsonAsync<WeatherForecast[]>("/sample-data/weather.json") |> Async.AwaitTask
                    return { mdl with Forecasts = Array.toList forecasts }
        }
  1. Open Program.cs and configure Flatware in the BrowserServiceProvider:
configure.AddFlatware<MyMsg, MyMdl>(MyMdl.Init);

You will need to add

using Flatware;
using ClassLibrary1;

at the top, assuming your F# library was called ClassLibrary1.

  1. The architecture is now ready for use in your Blazor pages. Open Counter.cshtml. Remove the entire @functions block. Change the header to:
@page "/counter"
@inherits MyAppComponent
@using ClassLibrary1

Replace

<p>Current count: @currentCount</p>

with

<p>Current count: @Mdl.Count</p>

Also replace

<button @onclick(IncrementCount)>Click me</button>

with

<button @onclick(() => DispatchAsync(MyMsg.NewIncrement(3)))>Click me</button>
  1. In FetchData.cshtml, do the same change to the header and remove the @functions block.

Replace both occurrences of forecasts with Mdl.Forecasts.

  1. All that remains is that the application message LoadWeather needs to be dispatched from somewhere. It could be from a button, or it could be from an OnInitAsync() method in the Blazor page. But it seems more natural to load the weather data when the application starts, which is why we'll change App.cshtml to the following:
@inherits MyAppComponent
@using ClassLibrary1
<!--
    Configuring this here is temporary. Later we'll move the app config
    into Program.cs, and it won't be necessary to specify AppAssembly.
-->
<Router AppAssembly=typeof(Program).Assembly />

@functions
{
    protected override async Task OnInitAsync()
    {
        await this.DispatchAsync(MyMsg.LoadWeather);
    }
}

Contributing

Flatware is definitely experimental at the moment, and you should expect breaking changes. But I'd be very interested in discussing the design and potential features. Please open an issue if you have any particular topic in mind.