slaveOftime
fsharpblazorcustom elements

Make blazor lazy with custom elements

2022-11-05

Before I heard blazor supports custom elements which can be used to integrate with blazor with other frameworks like react, angular etc. The way it supports that is by registering blazor component as standard web custom element. When the custom element is loaded it will try to connect to blazor host. For example for blazor server, it will try to reuse or create a websocket connection to asp.net core and initialize that custom element like normal blazor component. For more detail you can check AspLabs.

It would be boring if I just introduce that 😎

Below stuff is using Fun.Blazor as the tech background. Which is just a fsharp wrapper for blazor.

I always use my blog site as a playground to try something. Before I use blazor server to render my site, it works well, but yes not necessary to always build a websocket connection. So I try to render it in static mode and blazor related js is removed, every time you click a link (home page link, and post detail link) it will reload the whole page just like the old times.

There is one feature I want to contact the backend which is when I load post detail page, I want to increase view count. Before I used htmx, so it will trigger a POST api /api/post//viewcount when detail loaded. But in this way the backend api is exposed. (Of course I can expose another GET api /ui/post//viewcount to return a html fragment for view count and inject to the current page by htmx).

But I will have to create multiple endpoints and use a lot of magic url string to hook those up.

[<Route "/api/post">]
type PostController(postService: PostService) as this =
    inherit ControllerBase()

    [<HttpPost "{id}/viewcount">]
    member _.IncreaseViewCount(id) = task {
        do! postService.IncreaseViewCount id
        return this.Ok()
    }
div {
    hxTrigger (HxTrigger(hxEvt.load))
    hxPost $"/api/post/{postId}/viewcount"
    hxSwap_none // I can also swap a <span>View 123</span> if use the /ui/post/{id}/viewcount
}

So I was wondering how can I make it easier. I always start with what I want it to be:

CustomElement.create (fun (hook: IComponentHook, postService: PostService) ->
    hook.AddFirstAfterRenderTask(fun _ -> postService.IncreaseViewCount post.Id)
    viewCount post.ViewCount
)

I expect it will render static content as below for the first render.

<fun-node-custom-element node-render-fragment-key="2010792719" />
<script>if (window.initBlazor) { window.initBlazor() }</script>

Then the script will try to load blazor scripts and create or re-create connection. And automatically, go to the logic in CustomElement.create and return dynamic content.

Things in CustomElement.create can run anything which blazor server can run and should run naturally and normally.

All the ideas are implemented in Fun.Blazor.CustomElements. So magic is already happened.

Summary

<body>
    ...
    <custom-element1 /> // connect to blazor server for interaction with backend.
    ...
    <custom-element2 />
</body>

We can have multiple custom-element and in the same page, they will share the same websocket connection.

I know the use cases will not too much, but for some website which is most for displaying stuff like blog site. Only some part will have complex interaction. And for that part we also want to hide the backend APIs. Then we can use this simple way to do that.

My blog site is updated into this way, most parts are static rendered by asp.net core and the view count in post detail page will connect to server again with a websocket connection, and increase the view count, finally return a html fragment to that static page.

It sounds a little bit complex, but when I write it, it is very easy.

Do you like this post?