IJSRuntime

🟨 Slightly different to Blazor WebAssembly

IJSRuntime is a powerful API that allows developers to interact with the JavaScript runtime in a web browser, providing a bridge between the C# code and the browser's JavaScript engine. With IJSRuntime, developers can execute JavaScript code from C# code and vice versa, as well as access browser APIs such as DOM manipulation, browser storage, and other capabilities. This opens up a world of possibilities for developers looking to create rich, interactive web applications with Blazor.

  • Import JavaScript modules.
  • Import standard JavaScript.
  • Invoke JavaScript code from .NET.
  • Invoke .NET code from JavaScript.
  • Common mistakes.
  • Key differences between Blazor WebAssembly and Blazor Server.
You can download the example code used in this topic on GitHub.

Import JavaScript modules

You have the option to place your JavaScript module in the wwwroot folder or to colocate it with a component.

Importing a JavaScript module from the wwwroot folder

Assuming your JavaScript module is located in the /wwwroot/js/JavaScriptModule.js path, you can import it into your Blazor component by following these steps:

  1. Define a property for the JavaScript module in your component:
@code {
    private Lazy<IJSObjectReference> JSModule = new();
}
  1. Implement the IAsyncDisposable interface and dispose of the JavaScript module when the component is destroyed:
@implements IAsyncDisposable

@code {
    ...
    public async ValueTask DisposeAsync()
    {
        if(JSModule.IsValueCreated)
        {
            await JSModule.Value.DisposeAsync();
        }
    }
}
  1. Import the JavaScript module in AfterRender phase of the component:
@code {
    ...
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        JSModule = new(await JS.InvokeAsync<IJSObjectReference>("import", "./js/JavaScriptModule.js"));
    }
}

Should you check for the first render?

No, it is not necessary because the module will only be loaded once. The browser will not send multiple requests for the same JavaScript module. Therefore, skipping the check for the first render will simplify the code and make it easier to understand.

Import a colocated JavaScript module

Assuming your module is colocated with CallJavaScriptFunction component. Then your JavaScript should be in the same folder with CallJavaScriptFunction component and must be named CallJavaScriptFunction.razor.js. The files should appearance like this:

colocate-javascript-module-appearance.png

Then you can import the JavaScript module into your Blazor component by following these steps:

  1. Define a property for the JavaScript module in your component:
@code {
    private Lazy<IJSObjectReference> CollocatedModule = new();
}
  1. Implement the IAsyncDisposable interface and dispose of the JavaScript module when the component is destroyed:
@implements IAsyncDisposable

@code {
    ...
    public async ValueTask DisposeAsync()
    {
        if(CollocatedModule.IsValueCreated)
        {
            await CollocatedModule.Value.DisposeAsync();
        }
    }
}
  1. Import the JavaScript module in the AfterRender phase of the component:
@code {
    ...
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        CollocatedModule = new(await JS.InvokeAsync<IJSObjectReference>("import", "./Pages/JSInterop/CallJavaScriptFunction.razor.js"));
    }
}

Import standard JavaScript

In Blazor, you can import your standard JavaScript in wwwroot/index.html:

<body>
    <script src="js/GlobalClassicJavaScript.js"></script>
</body>

The <script> tag can be placed in the <head> tag in some rare case. However, we recommend you to place the <script> tag in the <body> tag.

Placing a script tag in the head section means that the script will be loaded before the rest of the page is rendered. This can be useful if the script is required for the page to function properly or if it needs to be loaded before any other content on the page. However, this can also cause the page to load more slowly, as the script must be fully loaded before the page can be displayed.

Placing a script tag in the body section means that the script will be loaded after the rest of the page is rendered. This can be useful if the script is not required for the page to function properly or if it is not critical to the user experience. By loading the script after the page content, the page can be displayed more quickly, which can improve the user experience.


Invoke JavaScript code from .NET

The IJSRuntime interface defines a set of methods that can be used to invoke JavaScript functions and access JavaScript objects from .NET code. These methods include:

  • InvokeAsync<T>: Invokes a JavaScript function asynchronously and returns a value of the specified type T.
  • InvokeVoidAsync: Invokes a JavaScript function asynchronously that returns no value.

Call a standard JavaScript global function

You can call any JavaScript function that attached to the window object. The following code will call the alert function:

@inject IJSRuntime JS
...
@code {
    private async Task HelloWorld()
    {
        await JS.InvokeVoidAsync("alert", "Hello BlazorSchool!");
    }
}

In the given code example, JS is an instance of IJSRuntime that has been injected from the instance pool using the technique of Dependency Injection, which we will cover later in this course.

The InvokeVoidAsync method is then used to call a JavaScript function identified by its name or object path. In the first example, the function alert is called and passed a message "Hello BlazorSchool!".

If your JavaScript function is nested in multiple objects, you can chain them together using dot notation.

Let's say you have a JavaScript function called myFunction that is defined within an object hierarchy like this:

var MyObject = {
  MyOtherObject: {
    MyFunction: function() {
      console.log("Hello from MyFunction!");
    }
  }
};

To call this function from your Blazor component using the IJSRuntime interface, you can use dot notation to specify the full path to the function:

@inject IJSRuntime JS

@code {
    private async Task CallMyFunction()
    {
        // Call the 'myFunction' method defined in the JavaScript file
        await JS.InvokeVoidAsync("MyObject.MyOtherObject.MyFunction");
    }
}

Call JavaScript functions from a module

After you've imported a JavaScript module, you can call any of its exported functions from your Blazor component using the IJSObjectReference interface. Here's an example of how to call a function called HelloBlazorSchool from a module:

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        JSModule = ...;
    }

    public async Task CallJSModuleFunctionAsync()
    {
        await JSModule.Value.InvokeVoidAsync("HelloBlazorSchool");
    }
}

Call a predictable JavaScript function

A predictable JavaScript function consistently returns the same type of data, regardless of the input or external factors. For instance, the following function always returns the string "Hello BlazorSchool!":

export function PredictableFunction()
{
    return "Hello BlazorSchool!";
}

To retrieve the data returned by a JavaScript function, you can use the InvokeAsync<T> method, where T is the type of the returned data. In this case, the function returns a string, so you can invoke it as follows:

public async Task CallPredictableFunctionAsync()
{
    PredictableResult = await JSModule.Value.InvokeAsync<string>("PredictableFunction");
}

Call an unpredictable JavaScript function

An unpredictable function is one that may return different types of data, or none at all. Consider the following example:

export function UnpredictableFunction()
{
    let randomNum = Math.random() * 10;

    if (randomNum < 3)
    {
        return "Blazor School";
    }

    if (3 < randomNum && randomNum < 6)
    {
        return 10;
    }

    return;
}

To handle such cases, you need to use a try-catch block to catch any JavaScript exceptions that may be thrown. Additionally, since the returned result of the function is unpredictable, you can use the dynamic type to store the result. Here's an example:

public async Task CallUnpredictableFunctionAsync()
{
    try
    {
        dynamic unpredictableResult = await JSModule.Value.InvokeAsync<dynamic>("UnpredictableFunction");
    }
    catch (Exception ex)
    {
        // Handle JS error
    }
}

Call a JavaScript function with parameters

Blazor allows you to easily call JavaScript functions and pass parameters using either the InvokeAsync or InvokeVoidAsync method. This section will demonstrate how to use both methods to send data from your C# code to a JavaScript function.

Pass primitive data to function

Assuming you have the following function:

export function FunctionWithPrimitiveParameters(stringData, numberData, dateTimeData)
{
    alert(`Received: string ${stringData}, number ${numberData}, date time ${dateTimeData}`);
}

You can call this function from your Blazor code as follows:

public async Task PassPrimitiveDataToFunctionAsync()
{
    await JSModule.Value.InvokeVoidAsync("FunctionWithPrimitiveParameters", "Blazor School", 5, DateTime.Now);
}

Pass an C# object reference to a JavaScript function

When you pass a C# object reference in Blazor, the corresponding JavaScript function can only access the public methods of the object, but not its fields or properties. To illustrate this, refer to the image below which shows the JavaScript accessibility to a C# object.

pass-csharp-object-reference-to-js.png

Assuming you have the following function:

export function FunctionWithReferenceParameter(csharpObjectReference)
{
    alert(`Received object not null? - ${csharpObjectReference != null}`);
}

To pass a C# object reference to a JavaScript function, you first need to wrap it and then pass the wrapped object as a parameter.

public async Task PassReferenceDataToFunctionAsync()
{
    var instanceToPass = new JSRuntimeExampleClass()
        {
            ExampleString = "Blazor School",
            ExampleInt = 100,
            ExampleDate = DateTime.Now
        };

    var wrappedInstance = DotNetObjectReference.Create<JSRuntimeExampleClass>(instanceToPass);
    await JSModule.Value.InvokeVoidAsync("FunctionWithReferenceParameter", wrappedInstance);
}

Pass an C# object data to a JavaScript function

When passing C# object data, JavaScript can read all of the object's fields and properties (read-only), but it cannot access the object's methods. The image below illustrates the accessibility of a C# object in JavaScript:

pass-csharp-object-data-to-js.png


Invoke .NET code from JavaScript

Blazor enables the calling of C# methods from JavaScript code in two ways: Direct calling and Proxy calling. Direct calling applies only to C# static methods marked with the attribute [JSInvokable]. In this case, the call is initiated by JavaScript. Static methods can be placed in any class within Blazor, and are located by their assembly name. To ensure uniqueness, it's important to give your method a distinct name. However, if a unique name is not feasible, you can pass a parameter to the [JSInvokable] attribute to specify an alternative method name.

direct-calling.png

  1. Add the attribute [JSInvokable] to your static method.
[JSInvokable]
public static string LocalStaticMethod()
  1. In JavaScript function, use DotNet.invokeMethodAsync(assemblyName, methodName) to call the C# method.
function CallStaticLocalComponentMethod()
{
    DotNet.invokeMethodAsync("JavaScriptInteraction", "LocalStaticMethod");
}

In this code, the first parameter is the assembly name, and the second parameter is the method name. To find your assembly name, right-click your project, select Properties, and navigate to Packages ⇒ General as shown in the following image:

how-to-see-assembly-name.png

Proxy calling, on the other hand, can be used for any C# method with the attribute [JSInvokable]. In this scenario, C# initiates the call and passes a reference to the JavaScript function. The JavaScript function then uses this reference to make a callback to C#.

proxy-calling.png

  1. Add the attribute [JSInvokable] to your non-static method.
[JSInvokable]
public void LocalMethod()
  1. Pass an instance of the class to the JavaScript function. Assuming that you have your JavaScript module stored in ExampleModule, you can create an instance of the class and wrap it using the DotNetObjectReference class:
public async Task CallLocalComponentMethod()
{
    var wrappedInstance = DotNetObjectReference.Create(this);
    await ExampleModule.Value.InvokeVoidAsync("CallLocalComponentMethod", wrappedInstance);
}
  1. In the JavaScript function, you can receive the reference and use it to invoke the destination C# method:
export function CallLocalComponentMethod(componentInstance)
{
    componentInstance.invokeMethodAsync("LocalMethod");
}

Asynchronous methods

Just like in C# programming, when invoking an asynchronous method from JavaScript, you have the option to either fire and forget or await the completion of the invokeMethodAsync function for the corresponding C# method before moving on to the next block of JavaScript code.

export function FireAndForget(csharpObject)
{
    csharpObject.invokeMethodAsync("ExampleVoidMethod");
    DotNet.invokeMethodAsync("JavaScriptInteraction", "LocalStaticMethod");
}

export async function AwaitForResults(csharpObject)
{
    await csharpObject.invokeMethodAsync("ExampleVoidMethodAsync");
    await DotNet.invokeMethodAsync("JavaScriptInteraction", "LocalStaticMethod");
}

Methods with return data

In C#, methods can return a value, which may either be a primitive data type or a reference to an object. When a method returns a primitive data type, such as a string, it can be treated like a normal variable in a JavaScript function. For example:

[JSInvokable]
public string ExamplePrimitiveReturnMethod() => "Hello BlazorSchool!";

In a JavaScript function, the returned value can be accessed and used as follows:

export async function CallPrimitiveDataReturnMethod(csharpObject)
{
    let result = await csharpObject.invokeMethodAsync("ExamplePrimitiveReturnMethod");
    alert(`The result is ${result}`);
}

When a method returns a reference object, such as an instance of a class, the object's properties can be accessed in a JavaScript function, but its methods cannot be called. For example:

[JSInvokable]
public JSRuntimeExampleClass ExampleReferenceReturnMethod() => new();

In a JavaScript function, the properties of the returned object can be accessed as follows:

export async function CallReferenceDataReturnMethod(csharpObject)
{
    let result = await csharpObject.invokeMethodAsync("ExampleReferenceReturnMethod");
    alert(`Received object data: string ${result.exampleString}, number ${result.exampleInt}, date time ${result.exampleDate}`)
}
You can't receive a Tuple in JavaScript function.

Methods with parameters

In C#, methods can have none, one, or multiple parameters. These parameters can be passed when calling a C# method from JavaScript. For example:

[JSInvokable("MethodWithPrimitiveParameters")]
public void ExamplePrimitiveParameterizedMethod(string exampleString, int exampleInt, DateTime exampleDate)
{
    ExampleString = exampleString;
    ExampleInt = exampleInt;
    ExampleDate = exampleDate;
}

In JavaScript, the parameters can be passed to the C# method using the invokeMethodAsync function, as follows:

export function CallPrimitiveParameterizedCSharpMethod(csharpObject)
{
    csharpObject.invokeMethodAsync("MethodWithPrimitiveParameters", "Blazor School", 100, new Date());
}

C# methods can also have reference parameters, which allow a JavaScript object to be passed to the C# method. For example:

[JSInvokable("MethodWithReferenceParameters")]
public void ExampleReferenceParameterizedMethod(JSRuntimeExampleClass instance)
{
    ExampleString = instance.ExampleString;
    ExampleInt = instance.ExampleInt;
    ExampleDate = instance.ExampleDate;
}

In JavaScript, a JavaScript object can be passed to the C# method, which will be converted to a C# object with corresponding properties:

export function CallReferenceParameterizedCSharpMethod(csharpObject)
{
    let passingObject = {
        exampleString: "Blazor School",
        exampleInt: 9000,
        exampleDate: new Date()
    };

    csharpObject.invokeMethodAsync("MethodWithReferenceParameters", passingObject);
}

Common mistakes

Try to avoid those common mistakes:

  1. Try to call .NET library method directly from JavaScript.
  2. Make an infinitive loop.
  3. Try to remove a Blazor DOM.
  4. Use JS as script.

Try to call .NET library method directly from JavaScript

JavaScript interaction allow you to call C# methods from JavaScript. However, you need to specify which C# methods can call from JavaScript before actually calling it. Therefore, you cannot call a method that is not specified to call in JavaScript. For example, you cannot call methods from System.Math (or any other libraries) unless you wrap it inside and method and specify that method to be able to call by JavaScript.

Make an infinitive loop

This common mistake usually made by the beginners. When making a call to a JavaScript function A and that JavaScript function A calls the C# method B again then the C# method B making a call to the JavaScript function A, thus create an infinitive loop. The following image illustrates this mistake.

infinitive-loop-mistake.png

The browser will be freezed as the following video:

Try to remove a Blazor DOM

To prevent conflicts between Blazor and JavaScript, it is important to ensure that they don't attempt to modify the same section of the DOM at the same time. If both technologies make changes to the same part of the DOM simultaneously, it can cause the website to malfunction and require a refresh. The following video provides an illustration of this situation.

Use JavaScript as script

It's important to note that Blazor cannot use JavaScript as a script in the traditional sense. Although it can call predefined JavaScript functions, it doesn't have direct access to the JavaScript runtime environment. For instance, you cannot create a new object using the new keyword directly in Blazor. To achieve this, you would need to declare a JavaScript function to create the object, and then call that function from Blazor.


Key differences between Blazor WebAssembly and Blazor Server

In Blazor WebAssembly, you can import JavaScript in the Initial phase.

BLAZOR SCHOOL
Designed and built with care by our dedicated team, with contributions from a supportive community. We strive to provide the best learning experience for our users.
Docs licensed CC-BY-SA-4.0
Copyright © 2021-2024 Blazor School
An unhandled error has occurred. Reload 🗙