Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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:

  • Apparently, that wasn't one of Blazor design goals back in 2018: https://github.com/dotnet/aspnetcore/issues/16033.

  • Later Steve Sanderson created an (unofficial) library to test Blazor components in isolation, which in theory can be used to get a standalone Blazor component markup: https://stackoverflow.com/a/60457390/1768303.

    Is it the best approach to tackle this problem, so far?

like image 220
noseratio Avatar asked Aug 23 '21 08:08

noseratio


People also ask

How do you reference Blazor component in code?

To capture a component reference in Blazor, use the @ref directive attribute. The value of the attribute should match the name of a settable field with the same type as the referenced component. When the parent component is rendered, the field is populated with the child component instance.

How do you use the Blazor component in Razor page?

Add the Blazor component folder in the View/Shared folder. Then add a Razor component inside the Component folder. Add the following code to the created Component. razor file.

How do you pass a value from one page to another in Blazor?

By using Blazor route parameters, you can pass values from one page to another page. Use NavigationManager to pass a value and route the parameter to another page. Follow this example to achieve passing values from one page to another. Get the passed Page1 parameter value in Page2.


3 Answers

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.

like image 90
Chris Coble Avatar answered Oct 03 '22 00:10

Chris Coble


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.

like image 21
Nicola Biada Avatar answered Oct 02 '22 22:10

Nicola Biada


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>
like image 27
MrC aka Shaun Curtis Avatar answered Oct 02 '22 22:10

MrC aka Shaun Curtis