IJSRuntime
🟨 Slightly different to Blazor Server
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.
You can download the example code used in this topic on GitHub.
You have the option to place your JavaScript module in the wwwroot folder or to colocate it with a component.
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:
@code { private Lazy<IJSObjectReference> JSModule = new(); }
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(); } } }
@code { ... protected override async Task OnInitializedAsync() { JSModule = new(await JS.InvokeAsync<IJSObjectReference>("import", "./js/JavaScriptModule.js")); } }
@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.
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:
Then you can import the JavaScript module into your Blazor component by following these steps:
@code { private Lazy<IJSObjectReference> CollocatedModule = new(); }
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(); } } }
@code { ... protected override async Task OnInitializedAsync() { CollocatedModule = new(await JS.InvokeAsync<IJSObjectReference>("import", "./Pages/JSInterop/CallJavaScriptFunction.razor.js")); } }
@code { ... protected override async Task OnAfterRenderAsync(bool firstRender) { CollocatedModule = new(await JS.InvokeAsync<IJSObjectReference>("import", "./Pages/JSInterop/CallJavaScriptFunction.razor.js")); } }
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.
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.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"); } }
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"); } }
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"); }
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 } }
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.
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); }
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.
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); }
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:
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.
[JSInvokable]
to your static method.[JSInvokable] public static string LocalStaticMethod()
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:
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#.
[JSInvokable]
to your non-static method.[JSInvokable] public void LocalMethod()
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); }
export function CallLocalComponentMethod(componentInstance) { componentInstance.invokeMethodAsync("LocalMethod"); }
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"); }
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.
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); }
Try to avoid those common mistakes:
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.
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.
The browser will be freezed as the following video:
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.
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.
In Blazor Server, you cannot import JavaScript in the Initial phase.