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

Something seems to be preventing Div from appearing and there are no error messages appearing

I've been attempting to create a menu, but the toggle div for the menu isn't visible Following a tutorial where an individual sets up a menu, there is a div with a menu icon in the top left corner. Despite meticulously copying the code from the ...

Is it possible to manipulate a parent component's state with a child component?

I have been experimenting with Material UI's react modal to create a modal as my child component. In the parent component, I have set up a state that controls the visibility of the modal and added a button inside the modal to close it. However, this s ...

Preventing the use of double-click on Grooveshark

I am currently in the process of creating a webpage to serve as a jukebox for parties. My goal is to have the page load Grooveshark with a transparent overlay (or any other necessary method) that can prevent users from forcefully adding their song to the f ...

Combining the keys of two objects in JSON

console.log(a) ; // output in console window= 1 console.log(b);// output in console window= 2 var c = {a : b};// Is there a better way to do this? var d = JSON.stringify(c); d = encodeURIComponent(d); I want the final value of d to be {1:2}. ...

Tips for showcasing an item with the chosen value in a dropdown menu

Below is the object I created: export default { data() { return { language: { "en": { welcomeMsg: "Welcome to New York City" }, "de": { ...

The $scope.$watch function is not triggering events within a controller of a ui.bootstrap.modal

Currently, I am utilizing UI bootstrap for Angular and have successfully integrated the ui.bootstrap.modal component into my project. Everything seems to be working smoothly except for one issue I am encountering. Despite setting up a $scope.$watch to trac ...

What could be causing the error with firebase Sign In in next.js?

I set up a sign in page to enter email and password for Firebase authentication. The sign up process works fine, but I'm encountering an issue with the sign in functionality. 'use client' import { useState } from 'react'; import { ...

Troubleshooting a problem with jQuery: alter background color when checkbox is

I recently created a script to change the background color when a radio button is selected. While it works for checkboxes, I noticed that when another radio button is selected, the previous one still remains with the selected color. <script type="text/ ...

Building custom components in Vue.js/NuxtJS can be a breeze when using a default design that can be easily customized through a JSON configuration

Currently, I am in the process of developing a NuxtJS website where the pages and components can either have a generic design by default or be customizable based on client specifications provided in the URL. The URL structure is as follows: http://localh ...

Mastering the art of square bracket destructuring in React through deep comprehension of the concept

import React, { useEffect, useState } from 'react' import { Text } from 'react-native' export default function Counter() { const [count, setCount] = useState(0) useEffect(() => { const id = setInterval(() => setCount((co ...

Use jQuery to display the first 5 rows of a table

I recently posted a query on show hide jquery table rows for imported xml data regarding how to toggle visibility of specific table rows using jQuery. Now, I am seeking advice on how to make the first 5 elements always visible within the same context. Belo ...

There was a hiccup encountered while trying to follow the steps for creating a

Despite others' attempts to solve the errors quickly, the search continues and the symptoms persist. > <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="046c6168686b29766165677044342a352a34">[email protected]< ...

Utilizing JavaScript text variables as hidden inputs

My JavaScript code is responsible for populating a modal from various sections of a website. Essentially, when the modal expansion button is clicked, it collects all data associated with that particular button press. While this functionality works flawles ...

What is the inner workings of stream.Transform in Node.js?

Recently, I stumbled upon a code snippet on a blog showcasing the usage of the stream Transform class to modify data streams and display the altered output. However, there are certain aspects of this code that leave me puzzled. var stream = require(&apos ...

Issue with Angular $compile directive failing to update DOM element

I'm currently working on a project that involves integrating AngularJS and D3 to create an application where users can draw, drag, and resize shapes. I've been trying to use angular binding to update the attributes and avoid manual DOM updates, b ...

How can one effectively showcase date and time information using Linq with Datatable?

In my project, I have set up a LINQ query that selects specific columns with dates and populates a DataTable. The project is built using ASP.NET MVC 5 and utilizes Datatables. My main concern is retrieving accurate DateTime values. So far, displaying date ...

Is it necessary to make multiple calls following a successful AJAX request?

Here is an AJAX call I am setting up. The first step is to hit the endpoint dofirstthing.do. Once that is successful, the next step is to make another call with "param1" as the query parameter. Now, my question is - how can I make a third call with "param ...

Using Jquery to handle different status codes in an Ajax request

Currently, I am handling statusCode of 200 and 304 in a jQuery Ajax call. Additionally, I have defined an "Error" function to catch any errors that may occur. If a validation message is related, we specify the status code as 400 - Bad Request. In this sc ...

Understanding how to extract data from a nested JSON response in ASP.NET MVC

I retrieved data successfully from a simple json using this link and displayed it on the view with the code below. SpeciesController.cs using diversity.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using ...

Tips on downloading a dynamically created XML file using ServiceStack and Angular

Although the code below is theoretically functional, it lacks proper error handling. The issue arises when the XML file starts downloading upon opening a new window with the URL generated by the service stack. In case of a server-side error, you are left o ...