0

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

0

1 Answer 1

0

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

Sign up to request clarification or add additional context in comments.

5 Comments

Roland Deschein, thank you for your input. I tried it but as expected no improvement. The issue already happens when I am initializing class objects by ..init() when trying to load the TestEmbed.js. So it fails already at the beginning. To your input with the "Async", yes of course this could be changed but in later implementation I would need async anyway, so will let that because should work also like that..
can you add the exception meesage you get to your question? Also, your class is partial, is there another part somewhere, or is it a component?
I added the exception text in the question part. The class is unique at the moment, no more uses. I've removed the partial, makes no difference.
@spaghettiBox I have edited my answer according to what I think the reason for your problem is. The points above about export are still relevant though.
Thank you very much for your answer and your input here. I integrated your suggestions and played around, it is working now. The only thing is, I had to pass the JsRuntime from the .razor component to the class constructor onInitialized() and what I did is to remove the Init() from the class so I will referencing the .js file defined in the index.html. So nice solution which makes completely sense now :) Thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.