Flatware is a state management library for Blazor, similar to Elm and Redux. It has the following features:
StateHasChanged()
.with
keyword in record types makes it simple to work with immutable types in your reducer logic. Not to mention that a model with many small types can be created with much less ceremony. F# lends itself well to type driven development. The Blazor project itself and the Razor pages must be C#.Assuming you have Visual Studio 15.7 or newer and the Blazor tooling installed, create a new standalone Blazor project.
Add a .NET Standard F# class library to the solution.
Add a reference from the Blazor project to the F# project.
Add the Flatware NuGet package to both projects.
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 }
}
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
.
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>
FetchData.cshtml
, do the same change to the header and remove the @functions
block.Replace both occurrences of forecasts
with Mdl.Forecasts
.
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);
}
}
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.