Description
Hi, I have a simple Blazor wasm project where I have javascript invoke / interop's. In a .razor page this works. I have a small illustration project on Github, the page Pages/TestJS.razor invokes a .js function which I print out to the user. I have to capsulate and add that javascript invoke's now to a separate class, so I can standardize and reuse the functions on different pages.
Problem
The new class I want to build in my invoke's is in Classes/JSFunctionHandler_Shared.cs accessing the javascript function in TestEmbed.js. This function I am integrating on the page Pages/TestJSEmbed.razor for test. But now it returns a exception when I initialize the javascript part in the new class.
Blazor page direct js access (works):
@page "/TestJS"
@inject IJSRuntime JsRuntime
<h3>Test JS (in razor component)</h3>
<button @onclick="onTestJS">Test JS</button>
@code {
public async Task onTestJS()
{
await JsRuntime.InvokeAsync<object>("TestJS"); // Test.js
}
}
Blazor page new with js in a class(not wirking):
@page "/TestJSEmbed"
@using Classes
@inject IJSRuntime JsRuntime
<h3>Test JS Embed (interop by class)</h3>
<button @onclick="onTestJSEmbed">Test JS</button>
@code {
JSFunctionHandler JSTest = new JSFunctionHandler();
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
await JSTest.Init();
}
public async Task onTestJSEmbed()
{
await JSTest.TestFunction_JSTestEmbed();
}
}
Class with javascript handling:
using Microsoft.JSInterop;
using Microsoft.AspNetCore.Components;
namespace Classes
{
public partial class JSFunctionHandler
{
[Inject]
public IJSRuntime JSRuntime { get; set; }
private IJSObjectReference _jsModule;
public async Task Init()
{
_jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./JS/TestEmbed.js");
}
public async Task TestFunction_JSTestEmbed()
{
await _jsModule.InvokeAsync<object>("JSTestEmbed");
}
}
}
Note: The javascript file I would like to access in the class "JSFunctionHandler" I have not added to the index.html because I am loading it in the init() method. This just because I have not found any other example do this for example over index.html assignment.. This could be changed of course.
A small example project just for illustration of the two scenarios is available in Github
Exception (when opening page TestJSEmbed.razor)
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component: Value cannot be null. (Parameter 'jsRuntime') System.ArgumentNullException: Value cannot be null. (Parameter 'jsRuntime') at Microsoft.JSInterop.JSRuntimeExtensions.InvokeAsync[IJSObjectReference](IJSRuntime jsRuntime, String identifier, Object[] args) at Classes.JSFunctionHandler.Init() in C:\Users\Operator\Documents\GitHub\testdata\BlazorWASM_JSInvoke\ExperimentalTest\Classes\JSFunctionHandler_Shared.cs:line 16 at ExperimentalTest.Pages.TestJSEmbed.OnAfterRenderAsync(Boolean firstRender) in C:\Users\Operator\Documents\GitHub\testdata\BlazorWASM_JSInvoke\ExperimentalTest\Pages\TestJSEmbed.razor:line 16
Try making the javascript exportable, so instead of
window.JSTestEmbed = async () => {
alert("JSTestEmbed Js interop works!");
}
use
export function JSTestEmbed = () => {// this doesn't really need to be async, right?
alert("JSTestEmbed Js interop works!");
}
Also, if you don't expect a return value, simply use InvokeVoidAsync
instead of InvokeAsync<object>
edit
Since the class you added is not a component and from your exception message it seems JSRuntime
is null, inject the JSRuntime like this:
In the blazor page
// other code, html
@code {
[Inject]
public JSFunctionHandler JSTest {get; set;}
// rest of the code
}
For this to work, you have to add it to the Dependency System in your Program.cs
`builder.Services.AddSingleton<JSFunctionHandler, JSFunctionHandler >();
And finally in your handler class inject it into your constructor as such:
private readonly IJSRuntime _jsRuntime;
public JSFunctionHandler (IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public async Task Init()
{
_jsModule = await _jsRuntime.InvokeAsync<IJSObjectReference>("import", "./JS/TestEmbed.js");
}
Hth