Can we consume a Blazor component as a Web Component within a regular non-Blazor HTML page?
Asked Answered
C

3

7

Can we render a Blazor component as an independent DOM fragment, or somehow else consume it as a standard Web Component within a vanilla HTML/JS page?

This might be a naive question from the Blazor architectural standpoints. I am not a Blazor expert by far, but I think it can be a useful technique for incremental "brownfield" modernization of legacy web applications. I'm surprised this doesn't appear to be officially supported.

To illustrate, consider this simple web component example, which renders a custom element <date-info>:

// define a custom web component
customElements.define("date-info", class DateInfo extends HTMLElement {
  constructor() {
    super();
    // create an "open" (vs "closed") shadow DOM, 
    // i.e., accessible to the outside JavaScript
    this.attachShadow({ mode: "open" });
  }

  async connectedCallback() {
    console.log(`${this.constructor.name}.${this.connectedCallback.name} called`);

    // get the content from the server, 
    // this could be a Blazor component markup
    try {
      const response = await fetch("https://worldtimeapi.org/api/ip");
      const data = await response.json();
      const content = new Date(data.utc_datetime).toString();
      this.shadowRoot.innerHTML = `<span>${content}</span>`;
    }
    catch(e) {
      console.error(e);
      const info = document.createTextNode(e.message); 
      this.shadowRoot.appendChild(info);
    }
  }
});
<!-- use the web component --> 
<p>Current time: <date-info/></p>

Now, instead of fetching https://worldtimeapi.org/api/ip, I'd like to fetch and render a detached markup for a Blazor/Server component, e.g.:

@* this is a Blazor component *@
<p>@(DateTime.Now)</p>

Moreover, I'd expect this markup to remain functional and dynamic, i.e., the client-side DOM events and the server-side updates for this Blazor component to further propagate both ways, through the life cycle of the wrapping web component.

It's surely possible to make it a Blazor @page and load it into an iframe, but I'm rather looking to render it as a part of the outer page's DOM.

So far, I've come across this:

Colvert answered 23/8, 2021 at 8:54 Comment(9)
I'm not a big fun of mixing technologies on the same frontend project. Too many bad experience in the past. A lot of things are under control from Blazor, specially on Server side where Signalr is used to update the UI. I don't remember the library you cite but I think it should works as a prerender HTML integration, leaving the management of the component outside the Blazor circuits. Look at chrissainty.com/… for an example of integration in existing MVC project.Kendy
Thanks @NicolaBiada. I'm not a fan of that kind of mixing either, but some project have very specific constraints, so this could be a way of making incremental changes. The article you linked is useful. Maybe, using Html.RenderComponentAsync inside a partial ASP.NET MVC view could do it.Colvert
Yes, I use the same approach to mix blazor components and MVC view page in the identity server part, that is still in cshtml format.Kendy
@NicolaBiada .cshtml should be fine, as long as I can fetch the functional markup. I'll give it a try.Colvert
@NicolaBiada I'd be really interested in seeing how you've done that - I raised this issue specifically to cover the requirement that I'd like to use Blazor components in the scaffolded identity pages: github.com/dotnet/aspnetcore/issues/35551 Do you have any example code I can look at? It would be very cool to be able to use (for example) MudBlazor components on my login page.Extraction
@Webreaper, on Twitter Florian Rappl mentioned his project Piral is capable of doing that. As I take it: github.com/smapiot/Piral.BlazorColvert
Added answer with some code. Same code added to github.com/dotnet/aspnetcore/issues/35551Kendy
Now a part of .NET 6: Render Blazor components from JavaScriptColvert
Also: visualstudiomagazine.com/articles/2021/09/16/…Colvert
S
8

MS has addressed this limitation, but the solution requires .Net 6.

https://github.com/aspnet/AspLabs/tree/main/src/BlazorCustomElements

This was done by the man himself, Steve Sanderson.

Save answered 23/8, 2021 at 15:45 Comment(2)
Related: github.com/dotnet/aspnetcore/issues/35372Colvert
Is there an updated link or snippet for this? both paths lead to a 404 page.Helles
K
3

In the meantime you can mix the old cshtml with razor components.
I use this approach to maintain the same graphic layout between the two systems.

An example, the following file is _Layout.cshtml used by Identity.
I've used various Blazor components via static rendering:

@using Microsoft.AspNetCore.Hosting
@using Microsoft.AspNetCore.Mvc.ViewEngines
@inject IWebHostEnvironment Environment
@inject ICompositeViewEngine Engine
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using Project.Server.Shared

<!DOCTYPE html>
<html>
<head>
    <component type="typeof(MainLayoutHead)" render-mode="Static" />
</head>
<body>
    <app>
        <div class="container main">
            <component type="typeof(MainLayoutTopImages)" render-mode="Static" />
            <div class="row navmenu-row">
                <div class="col-md-12 bg-dark navmenu-col">
                    <component type="typeof(NavMenu)" render-mode="Static" />
                </div>
            </div>
            <div class="row content pt-4 pb-0 mt-0">
                <div class="col-md-12">
                    <div class="row">
                        <div class="col-md-12">
                            @*Required for GDPR.*@
                            <partial name="_CookieConsentPartial" />
                        </div>
                    </div>
                    <div class="row body-row">
                        <div class="col-md-12 body-col">
                            @RenderBody()
                        </div>
                    </div>


                </div>
            </div>
            <component type="typeof(MainLayoutFooter)" render-mode="Static" />
        </div>
    </app>

    <script src="~/Identity/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/Identity/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/Identity/js/site.js" asp-append-version="true"></script>
    @RenderSection("Scripts", required: false)
</body>
</html>

The MainLayoutHead, MainLayoutFooter and NavMenu are regular Blazor components.

Kendy answered 23/8, 2021 at 19:18 Comment(2)
This is great. When you say "static rendering", does that mean the components aren't interactive (i.e., you're just rendering styled static components, with no dynamic behaviour)? Or are you referring to something else? (my MVC is rusty, so there may be another meaning I'm not aware of here).Extraction
Static render means that the HTML will be rendered as is, without a js or wasm mutation in second phase.Kendy
L
2

Not sure if this helps but you can definitely do it from a server side page (I'll delete this answer if it doesn't). Here's a test page that renders all three standard "pages" inside a cshtml page with server side content. You need to actually forget the "page" concept in Blazor. EVERYTHING is a Component. Pages are just components with a Page custom attribute.

The problem with this setup is that as soon as you refresh the page you restart the three components and you lose any scoped data.

@page "/test"
@namespace StackOverflow.Answers.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = null;
}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>StackOverflow.Answers</title>
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
    <link href="StackOverflow.Answers.styles.css" rel="stylesheet" />
</head>
<body>
    <div class="m-2 p-s bg-light">
        <h3>A Normal Razor Page</h3>
        <p>
            Lots of server side rendered junk
        </p>
        <component type="typeof(StackOverflow.Answers.Shared.SurveyPrompt)" render-mode="ServerPrerendered" />
    </div>
    <div class="m-2 p-s bg-info">
        <h3>A Normal Header</h3>
        <p>
            Lots More server side rendered junk
        </p>
        <component type="typeof(StackOverflow.Answers.Pages.Counter)" render-mode="ServerPrerendered" />
    </div>
    <div class="m-2 p-s bg-light">
        <h3>A Normal Header</h3>
        <p>
            Lots More server side rendered junk
        </p>
        <component type="typeof(StackOverflow.Answers.Pages.Counter)" render-mode="ServerPrerendered" />
    </div>

    <div class="m-2 p-s bg-light">
        <h3>Yet Another Normal Header</h3>
        <p>
            Lots More server side rendered junk
        </p>
        <component type="typeof(StackOverflow.Answers.Pages.FetchData)" render-mode="ServerPrerendered" />
    </div>

    <div class="m-2 p-s bg-light">
        <h3>Yet Another Normal Header</h3>
        <p>
            Lots More server side rendered junk
        </p>
        <component type="typeof(StackOverflow.Answers.Pages.FetchData)" render-mode="ServerPrerendered" />
    </div>


    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
</body>
</html>
Lammergeier answered 23/8, 2021 at 17:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.