JavaScript Interop with Blazor WASM
Tags: dotnet, blazor, javascript, interop
Published: 2024-04-06 09:34:49

---
title: JavaScript Interop with Blazor WASM
published: true
date: 2024-04-06 02:22:58 UTC
tags: #dotnet #blazor #javascript #interop
canonical_url: https://www.kiss-code.com/blog/javascript-interop-with-blazor-wasm-06-04-2024
cover_image: https://brandwebsiteblobstore.blob.core.windows.net/images/e5ee46b9d735-canvas-1-66145afb9001c61abca58386.png
---

In last article [The Role of JavaScript in Blazor](https://www.kiss-code.com/blog/the-role-of-javascript-in-blazor-wasm-02-04-2024) I laid out how a Blazor app can leverage JavaScript without being turned into it.

Now, I'll cover Blazor WASM's JavaScript interopability feature which allows your Blazor app to execute JavaScript. This is commonly used to invoke custom JavaScript methods and browser APIs.

Did you know that it works in both directions? Your JavaScript code can invoke a C# method of your Blazor app. I'll also cover that later on, in this article or you can [watch the video](https://youtu.be/8Fl9gX2Mb44?si=LX_plcbQ874ajOsD).

### Why I use JavaScript in Blazor?

The JavaScript ecosystem has an enormous amount of already built functionality and it can do anything from dynamically updating the layout on static web pages to full-fledged, highly-interactive, single-page web apps. There is a lot of knowledge and function to be learned, borrowed, re-used, ...

That's exactly what I use it for in all of my Blazor WASM projects. I implement the JS interop feature for e.g. automatically starting carousels and other components that would be too time-consuming to rebuild. And, to access the browser APIs like `navigator.share` or the local storage and other useful client-side, browser functionality.

### Call JavaScript from Blazor

For this demo, I scaffolded a new "Blazor Web App" with "RenderMode.Auto" interactivity set globally. The code will be available on my [Patreon](https://www.patreon.com/kisstupid).

I named this project: "JavaScriptInteropWithBlazor" and it contains a server-side project with the same name and a ".Client" project.

Take the typical "scroll to top" functionality, let's add that code into the client project's `wwwroot/js/custom-js.js`.

**1. Add the custom JavaScript code**

```javascript
function scrollToElement(elementId) {
    console.info("Invoked 'scrollToElement'");

    const element = document.getElementById(elementId);
    element?.scrollIntoView({
        behavior: 'smooth'
    });
}

```

For demo purposes, I added a log statement simply to verify that it worked.

**2. Add the script tag**

Then, we'll need to make sure this custom code is included in our application. Let's add the `<script>` tag at the bottom of the `App.razor` in the server-side project.

```html
<body id="first_element_id">
    <Routes @rendermode="@InteractiveAuto" />
    <script src="_framework/blazor.web.js"></script>

    <script src="./js/custom-js.js"></script> <!-- Here -->
</body>

```

**Note:** We're placing this script tag at the bottom of the page so that the target elements are created before trying to manipulate them with our custom JavaScript code.

**3. Create ScrollToTop component**

Create a new component in the client project's `Layout` folder. I'll name it `ScrollToTop.razor` and paste in the code below.

After injecting the `IJSRuntime` at the top, this Blazor code can call the JavaScript method `scrollToElement` and pass a parameter `elementId`.

```csharp
@inject IJSRuntime JsRuntime

<button @onclick="@(() => ScrollToElement("first_element_id"))">
    Scroll to top
</button>

@code {
    private async Task ScrollToElement(string elementId)
    {   
        await JsRuntime.InvokeVoidAsync("scrollToElement", elementId);
    }
}

```

**4. Add the component to a page**

Don't forget to add this component on a Blazor page, I'll put it on the client's `MainLayout.razor` somewhere underneath the `@Body`.

```html
...
    <main>
        <article class="content px-4">
            @Body
        </article>
    </main>
    
    <ScrollToTop /> @* Here *@
</div>

```

Now, running the Blazor app, clicking the "Scroll to top" button, should log "Invoked 'scrollToElement'" in our browser's console.

##### Return a value from JavaScript

I can also use `InvokeAsync<T>` to invoke a method that returns a value. I added this code to demonstrate that.

In `custom-js.js`:

```javascript
function scrollToElement(elementId) {
    console.info("Invoked 'scrollToElement'");
    
    const element = document.getElementById(elementId);
    element?.scrollIntoView({
        behavior: 'smooth'
    });
    
    return element == null; // <-- Here
}

```

In the Blazor component's `ScrollToTop` method:

```csharp
...
var isElementNull = await JsRuntime.InvokeAsync<bool>("scrollToElement", elementId);
Console.WriteLine("Element is null: " + isElementNull);

```

##### Interop with Browser API

Another major reason for me to use the JavaScript interopability feature is to access the browser's APIs e.g. `navigator.share`.

```csharp
...
await JsRuntime.InvokeVoidAsync("navigator.share", new
{
    Title = "Foo",
    Text = "Foo Bar",
    Url = "https://www.kiss-code.com/blog"
});

```

After adding that to the `ScrollToElement` method in the Blazor app, clicking the "Scroll To Top" button should now also open the brower's share menu (if supported). This share menu will be operating system specific (Windows, Android, iOS, ...).

Find more browser APIs [here](https://developer.mozilla.org/en-US/docs/Web/API).

### Import JavaScript into Blazor

What I showed you above is the more common use case in which I included my custom JavaScript code by referencing it in a `<script>` tag at the bottom of the index page (App.razor).

A less common use case may be to **encapsulate** this `<script>` tag into a component. Unfortunately, I can't just move the script tag into the component and expect reliable behavior. A better approach is to use the JavaScript interop feature to **import** the script or a module.

This can be done similarly as invoking a JavaScript method, as follows:

```csharp
await jsRuntime.InvokeVoidAsync("import", "./js/prism.js");

```

After which you can use the methods that come with this module, for example:

```csharp
await jsRuntime.InvokeVoidAsync("Prism.highlightAll");

```

### Call Blazor from JavaScript

To invoke a C# method in the Blazor app from JavaScript, I added the Blazor method:

```csharp
[JSInvokable("DifferentNameOptional")]
public static void LogInBlazor()
{
    Console.WriteLine("Invoked: " + nameof(LogInBlazor));
}

```

I added the following JavaScript snippet into the `scrollToElement` method in `custom-js.js` file:

```javascript
DotNet.invokeMethod("JavaScriptInteropWithBlazor.Client", "DifferentNameOptional");

```

This should work in a client-side Blazor WASM application not in a server-side one or a pre-rendered one, then it gives the following error:

```raw
There are multiple .NET runtimes present, so a default dispatcher could not be resolved. Use DotNetObject to invoke .NET instance methods.`.

```

To resolve that error I had to add this object reference and pass it, the update code:

```javascript
var jsObjectReference = DotNet.createJSObjectReference(window);
DotNet.invokeMethod("JavaScriptInteropWithBlazor.Client", "DifferentNameOptional", jsObjectReference);

```

There is much more you can do in this direction but it can get messy, read more here: [call-dotnet-from-javascript](https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-dotnet-from-javascript?view=aspnetcore-8.0).

I first encountered this use-case in an application leveraging a strong, JavaScript-based library with a lot of custom JavaScript code built on top of it where it really proves it strength. For example, after a user interaction with the calendar, I could then pass the result to Blazor to do the necessary e.g. HTTP call afterwards. I could do this entirely in JavaScript code but I prefer to limit the amount of _untyped_ JavaScript code in my Blazor apps.

### JavaScript Interop Server-side

The JavaScript interop feature is **not** available for Blazor SSR (static, without interactivity) since the Blazor app needs to tap into the client-side. Don't believe ChatGPT when it suggests that, it happened to a freelance client of mine.

A Blazor app with `RenderMode.InteractiveServer` or a WASM hosted (pre-rendered) can use the interop but only once the interactivity kicks in after client-side render. To avoid the server-side project to throw an interop related error, we'll have to (recommended) wrap the interop calls in a conditional of the `AfterRender` lifecycle method. Only for interop calls that would happen `OnInitialized`, not the ones that happen after a button click or a not-instant user interaction. Example:

```csharp
@inject IJSRuntime JsRuntime

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            Task.Run(() => JsRuntime.InvokeVoidAsync("import", "./js/prism.js"));
        }
    }
}

```

* * *

I have covered the JS interop feature multiple on my YouTube channel: [Keep it simple, stupid.](https://www.youtube.com/@kis.stupid).

You can grab yourself a copy of my [.NET 8 Blazor brand website](https://www.patreon.com/kisstupid/shop/net-8-brand-website-with-markdown-blog-33850) which contains multiple uses of the JS interop.

My free NuGet packages contain great examples of this feature as well: [Get them for FREE](https://www.kiss-code.com/products)