Search code examples
javascriptc#blazor-webassembly

Calling JavaScript from Blazor: how to dispose JavaScript within DotNetObjectReference?


I have created a Blazor Webassembly Project and added a key listener in JavaScript, which is listening to every key stroke on the DOM document. Everything works as expected, however when I open the Blazor page where the key listener is registered and later open it again, the following error occurs in the Web Browser:

There is no tracked object with id '2'. Perhaps the DotNetObjectReference instance was already disposed. (Parameter 'dotNetObjectId')

Obviously the object "dotnethelper" is disposed but the Javascript is still listening / getting called.

Basically I implemented the "Component instance .NET method helper class" from the Microsoft Documentation.

Blazor Page:

Note: The IDisposable is injected on the top and the Dispose function is getting called.

@code {
    private KeyListenerInvokeHelper _keyListenerInvokeHelper;
    private DotNetObjectReference<KeyListenerInvokeHelper>? objRef;

    protected override async Task OnInitializedAsync()
    {
        objRef = DotNetObjectReference.Create(_keyListenerInvokeHelper);
        await JS.InvokeVoidAsync("initializeKeyListener", objRef);    
    }

    public void Dispose()
    {
        objRef?.Dispose();
    }
}

Javascript File:

window.initializeKeyListener = (dotnetHelper) => {
    document.addEventListener('keydown', logKey);
    function logKey(e) {     
        dotnetHelper.invokeMethod('OnKeyDown', e.key);
        console.log('key down ' + e.key);
    }
}

KeyListenerInvokeHelper:

public class KeyListenerInvokeHelper
{
    private readonly Action<string> action;

    public KeyListenerInvokeHelper(Action<string> action)
    {
        this.action = action;
    }

    [JSInvokable("OnKeyDown")]
    public void OnKeyDown(string key)
    {
        action.Invoke(key);
    }
}

What have I tried so far?

  • I tried to reset the function on window.initializeKeyListener (i.e. setting window.initializeKeyListener), however this did not achieve anything
  • I tried removing the eventlistener on the 'keydown' event.

Solution

  • When you dispose of your object, you need to remove the event listener as well. You mentioned I tried removing the eventlistener on the 'keydown' event., but perhaps the way you did it was not correct? My javascript is a little rusty, but I think you could do something like the following:

    var logkey;
    window.initializeKeyListener = (dotnetHelper) => {
        logkey = (e) => {
            dotnetHelper.invokeMethod('OnKeyDown', e.key);
            console.log('key down ' + e.key);
        };
        document.addEventListener('keydown', logkey);
    }
    
    window.removeKeyListener = () => {
        document.removeEventListener('keydown', logkey);
    }
    

    and then in your component:

    @implements IAsyncDisposable
    
    public async ValueTask DisposeAsync()
    {
        await JSRuntime.InvokeVoidAsync("removeKeyListener");
        objRef?.Dispose();
    }
    

    Having said that, perhaps calling a static method in C# using [JSInvokable] would be better suited for your use case?