JavaScript Isolation in Blazor Components

  • Gérald Barré

In a previous post I explained how to use CSS isolation in a Blazor component to ensure the style of a component is not impacted by the global style or other components. JS isolation is similar but for JavaScript code. It allows to load JavaScript code for a specific component. One of the goals of JS isolation is to make reusable components that are not impacted by the global scope. In the previous version of Blazor, you needed to expose JavaScript function in the global scope (window) and to add the <script> element manually. JS isolation fixes both issues!

JS isolation is based on standard ES modules. ES modules can be loaded on-demand using let mymodule = import("path") and the functions they expose are only accessible from the returned object. They are not accessible from the global scope. This is a game changer when creating reusable components as consumers of your library no longer need to manually import the related JavaScript in a <script> tag! Combined with CSS isolation you can create easily reusable libraries 😃

In .NET, you can use ES modules using the IJSRuntime and JSObjectReference. IJSRuntime.InvokeAsync allows to call the import function and JSObjectReference allows to keep a reference to the loaded ES module.

// Load the module and keep a reference to it
// You need to use .AsTask() to convert the ValueTask to Task as it may be awaited multiple times
private Task<JSObjectReference> _module;
private Task<JSObjectReference> Module => _module ??= JSRuntime.InvokeAsync<JSObjectReference>("import", "./js/demo.js").AsTask();

For this demo, I've created a component that asks for your name and shows a modal to say hi. The JS file contains the sayHi function which just calls alert with the provided name. The razor component loads the ES module and calls the sayHi function using the name the user enters in an input. The solution looks like:

The JavaScript file is very basic. It simply exports a function named sayHi:

// Use the module syntax to export the function
export function sayHi(name) {
    alert(`hello ${name}!`);
}

Here's the full code of the Blazor component:

@inject IJSRuntime JSRuntime
@implements IAsyncDisposable

Enter your name: <input @bind="name" />
<button @onclick="Submit">Submit</button>

@code {
    private string name;

    // Load the module and keep a reference to it
    // You need to use .AsTask() to convert the ValueTask to Task as it may be awaited multiple times
    private Task<IJSObjectReference> _module;
    private Task<IJSObjectReference> Module => _module ??= JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/demo.js").AsTask();

    async Task Submit()
    {
        var module = await Module;
        await module.InvokeVoidAsync("sayHi", name);
    }

    public async ValueTask DisposeAsync()
    {
        if (_module != null)
        {
            var module = await _module;
            await module.DisposeAsync();
        }
    }
}

In the demo, you can see that the demo.js file is loaded only when clicking on the submit button:

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee