In the previous article, we’ve started a topic about calling JavaScript functions from C# and reached a certain point. Now, it is time to continue that story and explain more about using JSInterop to pass HTML elements to JavaScript functions in Blazor WebAssembly. Also, we are going to learn how to handle JavaScript errors in .NET.
If you want to see complete navigation for this series, you can visit our Blazor WebAssembly Page and find all of the articles from this series and many other articles as well.
Let’s start.
Using JSInterop to Pass HTML Elements to JavaScript Functions
In order to send an HTML element to a JavaScript function, we have to provide a reference to that element and pass it using JSInterop.
So, let’s see how to do that.
First, let’s create another function in the jsExamples.js
file:
export function focusAndStyleElement(element) { element.style.color = 'red'; element.focus(); }
Here we accept the HTML element reference, add some color styles, and apply focus on it.
Now, in the CallJavaScriptInDotNet.razor
file, we have to add some markup:
<div class="row"> <div class="col-md-4"> <h4> Passing HTML Reference to JS Function </h4> </div> <div class="col-md-4"> <input type="text" @ref="_elRef" class="form-control" /> </div> <div class="col-md-4"> <button type="button" @onclick="FocusAndStyleElement" class="btn btn-info">Focus</button> </div> </div>
As you can see in the input element, we have the @ref
attribute that accepts one parameter, which will hold the reference for this element. Also, we have a button that calls the FocusAndStyleElement
method.
Now, in the class file, we have to create a new _elRef
field of type ElementReference
and also to create this additional function:
public partial class CallJavaScriptInDotNet { ... private ElementReference _elRef; ... private async Task FocusAndStyleElement() => await _jsModule.InvokeVoidAsync("focusAndStyleElement", _elRef); }
So, with the _elRef
field, we store the reference of the input element, and then inside our new method, we pass that reference to the JavaScript function.
After these changes, we can run our application.
Note: If you notice caching issues like a new JavaScript function doesn’t exist, try running your app with CTRL+F5. Or run it with just F5 but open it in an incognito window.
As soon as our app starts, we can navigate to our component and press the Focus
button:
As we can see, our input has focus and if we start typing, the text color is red.
Excellent.
At this moment, we know more than we did about using JSInterop to pass HTML elements to a JavaScript function, but there is still more to learn.
Using HTML Element References with Component Lifecycle
In our previous example, we have sent an HTML reference to a JavaScript function after we clicked the Focus button. But, what if we want to do the same thing but in one of the component’s lifecycle methods?
When we work with an element’s reference, we have to be careful about which lifecycle method we have to use. While our page is rendering, Blazor is not updating any DOM at all. Only after the rendering of all components finishes, Blazor will compare the previous and the new render trees and update the DOM with as few changes as it can. Due to that, if we use our element references in the wrong lifecycle method, it can turn out that these elements don’t exist yet. So, passing them to the JavaScript function will end up with an error.
That said, we should always try to use our ElementReference objects in the OnAfterRender
or OnAfterRenderAsync
lifecycle methods. Of course, using the ElementReference instance in the user events, such as button click, is perfectly fine and safe (as we’ve seen it in the previous example).
Now, let’s see how to send HTML element references inside the component’s lifecycle method.
So, all we have to do is to override the OnAfterRenderAsync
method and call our FocusAndStyleElement
method inside it:
protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) await FocusAndStyleElement(); }
Now, we can start our app and navigate to the component. But as soon as we do that, we are going to get an error:
We are receiving this error because the _jsModule
is not yet initialized and has the null as a value. Obviously, when we use it to call the InvokeVoidAsync
method inside the FocusAndStyleElement
method, we get a null reference exception.
So, how to handle this?
Microsoft recommends that if we register a third-party library to work with DOM elements, this should be done in the OnAfterRender/Async method. Even though we don’t have a third-party library, we can use this advice to our advantage. So, let’s move the _jsModule
initialization in the OnAfterRenderAsync
method and remove the OnInitializedAsync
method:
protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./scripts/jsExamples.js"); await FocusAndStyleElement(); } }
At this moment, we can start our app and navigate to the component. As soon as we do this, our input element will get focus and if we start typing, the text color will be red. Of course, all the other functionalities on the page is working as before.
Working with Input Blazor Components
Now, you may wonder how we can do the same thing but with the Input component in Blazor WebAssembly. Obviously, we can’t pass the component as an element reference. So, we have to find a different solution.
Let’s see this with an example.
First, let’s create an HTML markup in our component:
<div class="row"> <div class="col-md-4"> <h4> Passing Input Component to JS Function </h4> </div> <div class="col-md-4"> <EditForm Model="_emailDetails"> <InputText id="dummyInputComponent" class="form-control" @bind-Value="@_emailDetails.Name"/> </EditForm> </div> <div class="col-md-4"> <button type="button" @onclick="FocusAndStyleInputComonent" class="btn btn-info">Focus</button> </div> </div>
Here, we have a simple form with one InputText
component.
Pay attention that we don’t have the @ref
attribute in our InputText
component. This time, we are going to use the id
attribute.
Now, we have to create that _emailDetails
model in the class file:
private ElementReference _elRef; private EmailDetails _emailDetails = new EmailDetails();
And also, we have to create an additional function:
private async Task FocusAndStyleInputComonent() => await _jsModule.InvokeVoidAsync("focusAndStyleInputComponent", "dummyInputComponent");
You can see, we are calling a new JavaScript function and also passing that id
parameter.
Finally, we have to create this new JS function:
export function focusAndStyleInputComponent(id) { const element = document.getElementById(id); element.style.color = 'red'; element.focus(); }
The main difference from the previous JS function is that here we use the id to get the element.
At this point, we can start our application, navigate to the component, and click the Focus button. We are going to see that our InputText component has a focus and the red text color.
Handling JavaScript Errors in C# Code
Our JavaScript functions can sometimes execute with errors and we should create our code in such a manner to react to those errors.
Let’s see, what we mean.
First, we are going to create a JS function that throws an error:
export function throwError() { throw Error("Testing error message from JS file."); }
Then, let’s add one more method in the CallJavaScriptInDotNet.razor.cs
file:
private async Task ThrowError() => await _jsModule.InvokeVoidAsync("throwError");
And finally, let’s call this method:
protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./scripts/jsExamples.js"); await FocusAndStyleElement(); await ThrowError(); } }
Good.
Let’s start our application, and navigate to the component:
As soon as we do that, we can see our app throws an error.
Of course, we want to handle our errors in .NET. To do that, let’s modify the class file:
public partial class CallJavaScriptInDotNet { ... private string _errorMessage; ... private async Task ThrowError() { try { await _jsModule.InvokeVoidAsync("throwError"); } catch (JSException ex) { _errorMessage = ex.Message; StateHasChanged(); } } }
So, all we do here is wrapping our JS call with the try-catch block. Pay attention that we can catch an error that happens during the interop call with the JSException
class.
Now we can just add a new simple markup in the component file:
<div class="row"> <div class="col alert alert-danger"> @_errorMessage </div> </div>
That’s it.
Let’s start our app, and navigate to the component:
As you can see, this time we are handling our JS error.
Conclusion
In this article, we have learned about using JSInterop to pass HTML references to JS functions. Also, we have learned how to combine JS calls with the Blazor component’s lifecycle methods and how to handle JS errors in our C# code.
In the next article, we are going to learn how to call .NET methods from JavaScript.
So, see you there.