Reputation: 9360
I am trying to update my Blazor
page on an event generated by js
.Js
with interop invokes C# code on an event and i need to somehow call this.StateHasChanged
in the c# callback :
JS
window.methods = {
timeout: null,
setTimeout: function () {
timeout = window.setInterval(async function () {
console.log("From js timeout occured ,sending to .net");
await DotNet.invokeMethodAsync('[assembly name]', "TimeoutCallback", Date.now().toString());
}, 2000);
},
clearTimeout: function () {
if (this.timeout == null || this.timeout == undefined) {
return;
}
console.log("Clearing timeout");
window.clearTimeout(timeout);
}
}
C#
@inherits StuffBase
@page "/test"
Current value: @val
<button onclick="@(async()=>await StartTimeout())">Start timeout </button>
<button onclick="@(async()=>await ClearTimeout())">Clear timeout</button>
public class StuffBase : BlazorComponent {
protected static string val { get; set; }
protected async Task StartTimeout() {
await JSMethods.SetTimeout();
}
[JSInvokable("TimeoutCallback")] //gets called by js on event !
public static async Task TimeoutCallback(string data) {
val = data;
Console.WriteLine("From callback ,received:" + data); //gets updated
await Task.Delay(1000);
//how can i call this.StateHasChanged
}
protected async Task ClearInterval() {
await JSMethods.ClearInterval();
}
}
Interop
public class JSMethods {
public static async Task SetTimeout() {
await JSRuntime.Current.InvokeAsync<string>("methods.setTimeout");
}
public static async Task ClearTimeout() {
await JSRuntime.Current.InvokeAsync<string>("methods.clearTimeout");
}
}
As you can see first i call from c#
-> setTimeout
that in js attaches the timeout with its handler.
What happens is that i manage to get the TimeoutCallback
called from js
but and while in console
i get my value updated , i somehow need to notify the UI
to update itself.
How can i achieve this since all my .NET
methods that are called from js
have (according to the documentation) to be static
?
Upvotes: 4
Views: 6869
Reputation: 1921
Use this way:
private static App? _app;
public App()
{
_app = this;
}
I used in App.razor
, for component may be not work, by this way can cascading to app then use in all companents.
Then use like this:
[JSInvokable]
public static void TimeoutCallback(string data)
{
...
_app?.StateHasChanged();
}
Upvotes: 3
Reputation: 51
After much futzing with the above answers, I could not get them to work in core 3.1 and Blazor web assembly. For client side blazor, JSInvokable only works for static methods. So my solution was to have javascript in index.html click a hidden button defined in the razor page:
index.html
<script language="javascript">
window.resetjs = function () {
document.getElementById('jspromptbutton').click();
}
</script>
</head>
<body>
<button type="button" class="btn btn-primary"
onclick="resetjs()">
fire prompt
</button>
then in JSInteropRazor, add a hidden button to fire the non-static method to fire statehaschanged, or in my case refresh the data in the razor page.
JSInterop.razor
<button type="button" id="jspromptbutton" style="display:none;" @onclick="TriggerNetInstanceMethod">
Fire statehaschanged and refreshAPI
</button>
@code {
public async Task TriggerNetInstanceMethod()
{
this.StateHasChanged();
string url = "http://myapi.com";
var newApiData = await Http.GetJsonAsync<Tuple<string, string>>(url);
}
}
Kind of a kluge, but I am going with it until a better solution is found..
Upvotes: 1
Reputation: 8521
I think it would be a nicer option to pass the C# instance down to JS and have your JS call back to that C# instance.
StuffBase.cs
public class StuffBase : ComponentBase
{
protected static string val { get; set; }
protected async Task StartTimeout()
{
await JSRuntime.Current.InvokeAsync<string>("methods.setTimeout", new DotNetObjectRef(this));
}
[JSInvokable("TimeoutCallback")] //gets called by js on event !
public async Task TimeoutCallback(string data)
{
val = data;
Console.WriteLine("From callback ,received:" + data); //gets updated
await Task.Delay(1000);
StateHasChanged();
}
protected async Task ClearTimeout()
{
await JSMethods.ClearTimeout();
}
}
JS
window.methods = {
timeout: null,
setTimeout: function (stuffBaseInstance) {
timeout = window.setInterval(async function () {
console.log("From js timeout occured ,sending to .net");
await stuffBaseInstance.invokeMethodAsync('TimeoutCallback', Date.now().toString());
}, 2000);
},
clearTimeout: function () {
if (this.timeout == null || this.timeout == undefined) {
return;
}
console.log("Clearing timeout");
window.clearTimeout(timeout);
}
}
Upvotes: 11
Reputation: 9360
I have solved it thanks to user @Isaac 's suggestion to add an event, and calling StateHasChanged
from its callback:
public class StuffBase : BlazorComponent {
protected static string val { get; set; }
protected delegate void OnTimeout(string data);
private static event OnTimeout OnTimeOutOccured;
protected override void OnInit() {
OnTimeOutOccured += x => {
this.StateHasChanged();
};
}
protected async Task StartTimeout() {
await JSMethods.SetTimeout();
}
[JSInvokable("TimeoutCallback")]
public static async Task TimeoutCallback(string data) {
val = data;
Console.WriteLine("From callback ,received:" + data);
await Task.Delay(1000);
OnTimeOutOccured?.Invoke(data);
}
protected async Task ClearTimeout() {
await JSMethods.ClearTimeout();
}
}
Upvotes: 2
Reputation: 45626
So what if they are static... Does that mean that you can't call the StateHasChanged method. Just type this at the end of the TimeoutCallback method: StateHasChanged();
Anyhow, if that is true, and you can't call the StateHasChanged method from the TimeoutCallback method, you may invoke an event handler from the TimeoutCallback method like this: TimeoutCallbackLoaded?.Invoke(data);
And from there call StateHasChanged();
Edit: OK, I've noticed now that you've defined a class named JSMethods with two methods calling SetTimeout and ClearTimeout. In that case, and it is a better design is to also define the TimeoutCallback in that class. After all the are related, right ? Then define an event handler as mention above which notify a subscriber (your UI code) of the occurrence of the event, passing it the data returned from JSIterop, and in the method that handles the event in the UI code, you can call StateHasChanged();
Upvotes: 2