In the previous post, Geolocation in Blazor, I started wrapping the browser’s Geolocation API in a Blazor component. That first article showed how to get the device’s current position, including how to get prototype-based JavaScript objects back across the C#/JS interop boundary.
In this post, I’ll look at using a JavaScript callback to raise a C# event when the device moves.

GitHub: https://github.com/darnton/BlazorDeviceInterop
NuGet: https://www.nuget.org/packages/Darnton.Blazor.DeviceInterop
Watch Events
If getting one position is good, getting lots is better. That’s what the watchPosition function is for. In the JavaScript version, the first argument is a callback that’s invoked every time the device’s position changes. I’ll C#-ify this in the GeolocationService and raise an event.

Blazor JavaScript interop is a two-way street. Raising the event in C# means we have to make a call from JavaScript back to C#. The sequence goes like this:
In the GeolocationService I have a WatchPosition method. This creates a .NET object reference to the GeolocationService instance. Then it invokes my JavaScript warpper function, giving it the service instance and the method name to call back to when the device’s position changes.
public async Task<long?> WatchPosition(PositionOptions options = null) { var module = await _jsBinder.GetModule(); var callbackObj = DotNetObjectReference.Create(this); return await module.InvokeAsync<int>("Geolocation.watchPosition", callbackObj, nameof(SetWatchPosition), options); }
I’ve wrapped the Geolocation API’s watchPosition function in my own watchPosition function, which is exported as part of the Geolocation module. When I call the wrapper function, I pass in the C# object reference and method name. The .NET callback reference is used to construct the JavaScript callback functions that the real watchPosition function invokes.
watchPosition: async function (dotNetCallbackRef, callbackMethod, options) { if (!navigator.geolocation) return null; return navigator.geolocation.watchPosition( (position) => { let result = { position: null, error: null }; this.mapPositionToResult(position, result); dotNetCallbackRef.invokeMethodAsync(callbackMethod, result); }, (error) => { let result = { position: null, error: null }; this.mapErrorToResult(error, result); dotNetCallbackRef.invokeMethodAsync(callbackMethod, result); }, options); },
The watchPosition function returns a watchId that we’ll use later to clear the watch.
When the device’s position changes, the callback is invoked. The JavaScript callback invokes the .NET callback with the new GeolocationResult. The GeolocationService’s SetWatchPosition method is decorated with the JSInvokable attribute to let Blazor know this is allowed. It invokes an event handler.
[JSInvokable] public void SetWatchPosition(GeolocationResult watchResult) { WatchPositionReceived?.Invoke(this, new GeolocationEventArgs { GeolocationResult = watchResult }); }
The Test Rig
The test rig for watching the position is almost the same as the one for getting the current position. The biggest difference is that the button is a toggle that turns watching on and off.

When the button is clicked for the first time an event handler is added and the WatchPosition method is called.
public async void TogglePositionWatch() { if (isWatching) { await StopWatching(); ... } else { GeolocationService.WatchPositionReceived += HandleWatchPositionReceived; WatchHandlerId = await GeolocationService.WatchPosition(); } StateHasChanged(); }
When the WatchPositionReceived event is raised, the event handler checks for a successful result and adds a marker to the map.
private async void HandleWatchPositionReceived(object sender, GeolocationEventArgs e) { LastWatchPositionResult = e.GeolocationResult; if (LastWatchPositionResult.IsSuccess) { var latlng = LastWatchPositionResult.Position.ToLeafletLatLng(); var marker = new Marker(latlng, null); if (WatchPath is null) { WatchMarkers = new List<Marker> { marker }; WatchPath = new Polyline(WatchMarkers.Select(m => m.LatLng), new PolylineOptions()); await WatchPath.AddTo(WatchMap); } else { WatchMarkers.Add(marker); await WatchPath.AddLatLng(latlng); } await marker.AddTo(WatchMap); } StateHasChanged(); }
Stop Watching
When you want to stop watching the device’s position, pass the watchId to the clearWatch function. From a blazor component, this can be done explicitly, like I do with the ‘Stop Watching’ button, or automatically when the component is disposed.
private async Task StopWatching() { GeolocationService.WatchPositionReceived -= HandleWatchPositionReceived; await GeolocationService.ClearWatch(WatchHandlerId.Value); } public async void Dispose() { if (isWatching) { await StopWatching(); } }
The JS layer for this is very simple.
clearWatch: function (id) { if (navigator.geolocation) { navigator.geolocation.clearWatch(id); } }
And, with that, all the functions in the Geolocation API are covered.
Get the Code
GitHub: https://github.com/darnton/BlazorDeviceInterop
NuGet: https://www.nuget.org/packages/Darnton.Blazor.DeviceInterop
One thought on “Geolocation II: Position Updates”