English
Français

Blog of Denis VOITURON

for a better .NET world

Blazor Custom Elements in HTML or React

Posted on 2022-12-22

Introduction

Since version 7.0 of .NET, Microsoft has enhanced and integrated the ability to create HTML Custom Elements. These custom elements offer developers a way to create their own full-featured DOM elements. In Blazor, this allows publishing these components to other SPA Frameworks, such as Angular or React. More info in the Microsoft documentation.

Blazor custom components:

  1. Use standard HTML interfaces to implement custom HTML elements.

  2. Eliminate the need to manually manage the state and lifecycle of Blazor components using JavaScript APIs.

  3. Are useful for gradually introducing Blazor components into existing projects written in other SPA frameworks.

Note that these Blazor custom elements do not support Child Contents or Templated Contents.

Create a Blazor Custom Element

Several steps are required to create a Blazor component to be registered as a Custom Element.

  1. Create a Blazor WebAssembly project in .NET 7.0 (or higher).

  2. Add the NuGet dependency Microsoft.AspNetCore.Components.CustomElements.

  3. Create your Blazor component in the classic way, with [Parameter] attributes for the properties to be exposed.

     <h1>Counter</h1>
     <p>Current count: @Value</p>
     <button @onclick="IncrementCount">Click me</button>
    
     @code {
         [Parameter]
         public int Value { get; set; } = 0;
    
         [Parameter]
         public EventCallback<int> ValueChanged { get; set; }
    
         [Parameter]
         public int Increment { get; set; } = 10;
    
         private async Task IncrementCount()
         {
             Value += Increment;
             if (ValueChanged.HasDelegate)
                 await ValueChanged.InvokeAsync(Value);
         }
     }
    
  4. Register your component as a Custom Element via the following command (in Program.cs).
     builder.RootComponents.RegisterCustomElement<Counter>("my-counter");
    
  5. Publish your project to generate the WebAssembly files (_framework folder) and the CustomElements JavaScript file (_content folder).

⚠️ In order to respect the HTML specifications, the names of the HTML Custom Element tags must respect the [Kebab] cases(https://en.wikipedia.org/wiki/Letter_case#Kebab_case).

Examples:

  • “my-counter” and “my-cool-counter” are valid.
  • “mycounter”, “MY-COUNTER” and “MyCounter” are not valid.

Integrate a Blazor component in an HTML page

A simple index.html page can now host this new component which is known as my-counter.

  1. In the main page, index.html, add a reference to the file blazor.webassembly.js which is responsible for loading and starting the Blazor WASM application, as well as loading the CustomElements.lib.module.js file.

     <html>
       <head>
         <base href="/" />
         <script src="_framework/blazor.webassembly.js"></script>
       </head>
       <body>
         <my-counter id="myCounter" value="7" increment="2"></my-counter>
       </body>
     </html>
    
  2. Start this website via a web server like Live Server (you cannot double-click on the index.html file from Windows Explorer).

  3. For the moment, Blazor does not seem to support the assignment of EventCallback methods via the attributes of Custom Elements. However, it is possible to define a JavaScript function for these methods: myCounter.valueChanged = .... Note the autostart=false attribute to start Blazor manually via Blazor.start().

     <html>
       <head>
         <base href="/" />
         <script src="_framework/blazor.webassembly.js" autostart="false"></script>
       </head>
       <body>
         <my-counter id="myCounter" value="7" increment="2"></my-counter>
         <script>
           Blazor.start().then(() => {
             var myCounter = document.getElementById("myCounter");
             myCounter.valueChanged = (x) => {
               alert("You clicked " + x);
             };
           });
         </script>
       </body>
     </html>
    

Integrate a Blazor component in a React application

First of all, it is necessary to have a React application and to reference the Blazor component created previously. A simple example consists in installing NodeJS and running the following commands.

  1. Create a new Hello World application.
     npx create-react-app hello-world
    

    ⚠️ Place this application in a folder in kebab format (lower case) to avoid compilation problems.

  2. Go to the folder of this application and start it to see if it works properly.
     cd .\hello-world\
     npm start
    
  3. Copy the previously published _content and _framework folders to the public folder of the React website.

  4. In the main page, index.html, add a reference to the blazor.webassembly.js file that is responsible for loading and starting the Blazor WASM application. It is this JS file that will load the CustomElements.lib.module.js file as described in the blazor.boot.json file. For this referencing chain to work properly, it is necessary to add the <base href="/" /> in the header of the index.html file.
     <base href="/" />
     <script src="_framework/blazor.webassembly.js"></script>
    
  5. Now you just have to modify the application to add the new my-counter component, in the App.js file.
     <div className="App">
     <header className="App-header">
         <my-counter increment="5"></my-counter>
     </header>
     </div>
    
  6. By starting this new application (npm start), you can see that the Blazor WebAssembly component is present and functional in the React application.

EventCallback properties

For the moment, Blazor does not seem to support the assignment of EventCallback methods via the attributes of HTML Custom Elements.

<my-counter value-changed="myChangeHandler"></my-counter>

However, it is possible to define a JavaScript function for these methods. Here is an example of a React component that retrieves the onValueChanged property from the React component to use it in the HTML custom component.

import React from "react";

export default class Counter extends React.Component { 
  id = "myId";
  
  onValueChanged = function (value) { };

  render() { 
    return React.createElement('my-counter', { ...this.props, id: this.id });
  }

  componentDidMount() { 
    let element = document.getElementById(this.id);
    element.valueChanged = (x) => {
      this.props.onValueChanged(x);
    };
  }
}
<Counter onValueChanged="myChangeHandler"></Counter>

Languages

EnglishEnglish
FrenchFrançais

Follow me

Recent posts