Transfer razor javascript calls in Blazor WebAssembly to a separate class

Scenario Description

Greetings, I am currently working on a Blazor WebAssembly project that involves JavaScript interop. Initially, I had this functionality working in a .razor page. However, I now intend to encapsulate and centralize these JavaScript invocations into a separate class for better organization and reusability across different pages.

Current Challenge

The new class where I plan to consolidate the JavaScript invokes is located in Classes/JSFunctionHandler_Shared.cs, accessing a JavaScript function in TestEmbed.js. This integration is supposed to work on the page Pages/TestJSEmbed.razor for testing purposes, but unfortunately, it results in an exception upon initializing the JavaScript part within the new class.

Blazor Page Direct JavaScript Access (currently functional):

@page "/TestJS"
@inject IJSRuntime JsRuntime

<h3>Testing JavaScript (within razor component)</h3>

<button @onclick="onTestJS">Test JavaScript</button>

@code {

    public async Task onTestJS()
    {
       await JsRuntime.InvokeAsync<object>("TestJS"); // invoking Test.js
    }
   
}

Blazor Page utilizing JS in a Class (not functioning as expected):

@page "/TestJSEmbed"
@using Classes
@inject IJSRuntime JsRuntime

<h3>Testing JS Embed (interop through a class)</h3>

<button @onclick="onTestJSEmbed">Test JavaScript</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");
        }

    }
}

It's important to note that I have not directly added the JavaScript file I aim to access in the "JSFunctionHandler" class to the index.html file. Instead, I load it dynamically within the init() method. This approach was chosen due to the lack of concrete examples using index.html for such task. Naturally, this can be modified in the future.

A sample project showcasing both scenarios mentioned above is available on Github

Exception Encountered (when opening the TestJSEmbed.razor page)

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

Answer №1

Consider making the javascript code exportable. Instead of

window.JSTestEmbed = async () => {
    alert("JSTestEmbed Js interop works!");
}

You can use

export function JSTestEmbed = () => {// It doesn't necessarily have to be async, right?
    alert("JSTestEmbed Js interop works!"); 
}
    

Additionally, if you are not expecting a return value, simply replace InvokeAsync<object> with InvokeVoidAsync

Edit

Since the added class is not a component and based on the exception message it seems like JSRuntime is null, you should inject the JSRuntime as follows:

In the blazor page

// other code, html
@code {
[Inject]
public JSFunctionHandler JSTest {get; set;}

// rest of the code
}

For this to work, remember to add it to the Dependency System in your Program.cs

`builder.Services.AddSingleton<JSFunctionHandler, JSFunctionHandler >();

Finally, in your handler class, inject it into the constructor like this:

private readonly IJSRuntime _jsRuntime;

public JSFunctionHandler (IJSRuntime jsRuntime)
{
    _jsRuntime = jsRuntime;
}

public async Task Init()
{
    _jsModule = await _jsRuntime.InvokeAsync<IJSObjectReference>("import", "./JS/TestEmbed.js");
}

Hope this helps!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating Unique Identifiers in ExpressJS

I am currently utilizing mongoose to display admin and user information on a dashboard, but I am encountering difficulty rendering the id of a user. Below is the code I am using: function ensureAuthenticated(req, res, next){ if(req.isAuthenticated()){ ...

What is the best way to ensure that the state is updated only when the user begins typing in a text

I am currently working on a text editor-related code and my main focus is to update the state of the editor only when the user starts typing in the editor. The state should be updated under the following scenarios: 1. Update the state when the user begin ...

Creating a Javascript countdown timer that does not involve displaying the

I stumbled upon this code on a website, but there's one tweak I'd like to make. Unfortunately, I can't seem to figure it out myself, so I'm reaching out for some help. What I want to achieve is removing the year from the date so that th ...

Detecting unutilized space in a collection of divs with varying sizes using JavaScript and CSS

To better understand my issue, I created a StackBlitz demo: https://stackblitz.com/edit/angular-aqmahw?file=src/app/tiles-example.css Screenshot My tiles can have four different widths (25%, 50%, 75%, 100%). The tiles must fit on only two lines, so if a ...

Alignment issue with Ul dropdown menus and shadow

After stumbling upon a fascinating menu design at this link, I decided to tweak it for center alignment by following the advice on this forum thread. Unfortunately, my efforts have hit a roadblock - the drop down menus aren't aligning as intended and ...

Switching styles in AngularJS without using ng-class

My goal is to allow users to switch the class from incomplete to complete when they click a button and the function(response) returns 1. I have attempted to use ng-class, but it is not effective because the HTML elements are generated with a PHP loop. This ...

Strategies for iterating over an array in React with TypeScript

I'm currently working on looping through an array to display its values. Here's the code I have: ineligiblePointsTableRows() { return this.state[PointsTableType.INELIGIBLE].contracts.map(contract => { return { applied: (&l ...

Tips for splitting the json_encode output in Javascript

Looking for help with extracting specific values from JSON data retrieved via PHP and AJAX. I only want to display the agent name in my ID, not the entire object that includes "name" : "Testing". Here is the console output: [{"agent_module_id":"1","agen ...

The console experienced a forced reflow when a tooltip appeared on the slider handle while executing JavaScript

I am currently facing an issue with my web page that includes various elements along with the Ant.design slider. The values of the slider are being controlled through React states. However, I have noticed that when the slider tooltip is enabled, the respon ...

Ending asynchronous tasks running concurrently

Currently, I am attempting to iterate through an array of objects using a foreach loop. For each object, I would like to invoke a function that makes a request to fetch a file and then unzips it with zlib, but this needs to be done one at a time due to the ...

Verify image loading using jQuery

<img src="newimage.jpg" alt="thumbnail" /> I am dynamically updating the src attribute of this image. Is there a way to verify if the image has been successfully loaded and take action once it is? Appreciate any guidance on this matter. ...

What is the best way to connect individual buttons to a dynamic div that displays different content depending on the button clicked?

Hey there! I'm diving into the world of JavaScript and HTML, and I need some guidance on creating a menu that can toggle visibility of specific content in div(s) depending on which button (picture1-12) is clicked. My idea is to have one div that can d ...

Sending JSON data to a web service using ASP.NET and jQuery

I am currently facing an issue with posting JSON data to a web service. Although the web service is being executed, no data is available. The jQuery code I am using looks like this: var json = {"Results": results}; var jsonArray=JSON.stringify(json); $.a ...

Creating a nested tree structure array from a flat array in Node.js

I have an array structure that I need to convert into a tree array using node.js. The current array looks like this: var data= [ { "id1": 1001, "id2": 1002, "id3": 1004, ... } ...

Opening a modal from a different component in Angular 6

I am attempting to launch a modal that is associated with a separate component. However, I encountered an error ERROR TypeError: Cannot read property 'show' of undefined Here is my code: product-catalog.component.html <app-cart-table-modal& ...

Conundrum regarding setting up configuration for express-session middleware in Express version 4.x

Hello, I'm currently diving into node.js and still trying to grasp the concept of configurations in sessions. Below is a basic example of how sessions are used in my app: app.js var express = require('express'); var bodyParser = require(&a ...

Why is it not performing as expected when removing all non-numeric elements from the array?

let arr = [1, "5", 3, 27, undefined, { name: 'Steven' }, 11]; for (let i = 0; i < arr.length; i++) { if (typeof arr[i] !== 'number') { arr.splice(i, 1); } } console.log(arr); // result: [1, 3, 27, {…}, 11 ...

JavaScript Selenium code encountering an "Element not interactable" error with input textbox element

Currently, I am attempting to utilize Selenium in order to automate inputting a location into the search bar on weather.com. Initially, I wrote the code in Python and it seems to be functioning properly: // this works driver = webdriver.Chrome(ChromeDriver ...

What could be causing the RTCPeerConnection icegatheringstatechange to not function properly?

I have been trying to utilize the icegatheringstatechange event handler, but it doesn't seem to be functioning properly. Is there a different method I can use to monitor changes in icegatheringstate? How can I determine when all ice candidates have be ...

The CloudWatch logs for a JavaScript Lambda function reveal that its handler is failing to load functions that are defined in external

Hello there, AWS Lambda (JavaScript/TypeScript) is here. I have developed a Lambda handler that performs certain functions when invoked. Let me walk you through the details: import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda' ...