Closed yopez83 closed 4 years ago
@yopez83 thanks for contacting us.
I'm not really sure what behavior you think is wrong here. From what you describe everything is working as expected.
Can you clarify what you think the problem is or provide a minimal repro project that helps us understand the issue you are facing?
@javiercn thank you for your prompt response. Using the Blazor WebAssembly project template see below.
namespace BlazorApp2
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddScoped<WeatherForecastStateProvider>();
builder.RootComponents.Add<App>("app");
await builder.Build().RunAsync();
}
}
}
namespace BlazorApp2
{
public class WeatherForecastStateProvider
{
public WeatherForecast WeatherForecast { get; set; } = new WeatherForecast();
public event Action OnChange;
public void UpdateWeatherForecast() => OnChange?.Invoke();
}
}
I moved the WeatherForecast
model from FetchData.razor
to a Models
folder I created. I also added an identifier to be able to select by Id in order to update the item.
namespace BlazorApp2.Models
{
public class WeatherForecast
{
public Guid Id { get; }
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public string Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public WeatherForecast()
{
Id = Guid.NewGuid();
}
}
}
@page "/fetchdata"
@using BlazorApp2.Models
@inject HttpClient Http
@inject WeatherForecastStateProvider WeatherForecastStateProvider
@inject NavigationManager NavigationManager
@implements IDisposable
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr @key="forecast.Id">
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
<td>
<button class="btn btn-primary" @onclick="() => EditWeatherForecast(forecast)">
<i class="oi oi-pencil"></i> Edit
</button>
</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("sample-data/weather.json");
WeatherForecastStateProvider.OnChange += UpdateWeatherForecast;
}
public void Dispose() => WeatherForecastStateProvider.OnChange -= UpdateWeatherForecast;
public void EditWeatherForecast(WeatherForecast weatherForecast)
{
WeatherForecastStateProvider.WeatherForecast = weatherForecast;
NavigationManager.NavigateTo("/forecast/edit");
}
public void UpdateWeatherForecast()
{
var forecast = forecasts.Single(f => f.Id == WeatherForecastStateProvider.WeatherForecast.Id);
forecast = WeatherForecastStateProvider.WeatherForecast;
StateHasChanged();
}
}
@page "/forecast/edit"
@using BlazorApp2.Models
@inject NavigationManager NavigationManager
@inject WeatherForecastStateProvider WeatherForecastStateProvider
<EditForm Model="WeatherForecast" OnSubmit="Submit">
<div class="form-group">
<label for="wf_date">Date:</label>
<InputDate id="wf_date" class="form-control" @bind-Value="WeatherForecast.Date" />
</div>
<div class="form-group">
<label for="wf_celsius">Temperature Celsius:</label>
<InputNumber id="wf_celsius" class="form-control" @bind-Value="WeatherForecast.TemperatureC" />
</div>
<div class="form-group">
<label for="wf_summary">Summary:</label>
<InputTextArea id="city" class="form-control" @bind-Value="WeatherForecast.Summary" />
</div>
<button type="submit" class="btn btn-primary">Save</button>
</EditForm>
@code {
public WeatherForecast WeatherForecast { get; set; }
protected override async Task OnInitializedAsync() => WeatherForecast = await Task.FromResult(WeatherForecastStateProvider.WeatherForecast);
public void Submit()
{
WeatherForecastStateProvider.UpdateWeatherForecast();
NavigationManager.NavigateTo("/fetchdata");
}
}
See, the whole point is:
Edit
button on a forecast from the table,EditWeatherForecast.razor
and update the data,Save
button I want to invoke the OnChange
event from the WeatherForecastStateProvider
and notify the subscriber. In my case, to re-render the forecast item I updated.Sorry, I don't really understand.
In the code you've posted, the only place you're subscribing to the event is inside FetchData.razor
, but the user is not even on that page when you call WeatherForecastStateProvider.UpdateWeatherForecast()
. So how can there be anything listening to this event?
Maybe this isn't a bug and it's working as intended, but then how do I unsubscribe? Most importantly, how to avoid being unsubscribed upon navigation?
Why would you want a component to remain subscribed after it has been removed from the UI?
It sounds like you just want the UI to be up-to-date when the user navigates back there later. Doesn't that already happen by default in your implementation? After the user clicks "save" you call NavigationManager.NavigateTo("/fetchdata")
, which should:
FetchData
component instanceSo if you actually updated the state on the server when the user clicks "save", you would see the updated state in step 2.
If your goal is just to edit the state in memory and not get an update from the server, then FetchData
should render data provided by WeatherForecastStateProvider
instead of data provided by the backend server.
You're right @SteveSandersonMS. Last paragraph outlined my intent. I may have confused things a bit.
@SteveSandersonMS one last question. Say I from the EditWeatherForecast.razor
update the weather.json
file. Then I nagivate back to FetchData.razor
. Now, would the diffing re-render the whole table or just the forecast item it was updated?
It would be inserting an entirely new table as the previous one was removed when you navigated away.
fair enough. Thanks
AppState Pattern
I have component A and at
OnInitializedAsync
I subscribe methodFoo
to an event defined inAppState
. I also implementedDispose
fromIDisposable
where I unsubscribe from the event. So far, it's what most of the docs out there recommend.The bug
From A I navigate to component B using
NavigationManager.NagivateTo("/componentBRoute")
. Before navigating I've checkedFoo
remains subscribed. Upon navigation I've noticedAppState
event was null. Then, I added a breakpoint toDispose
method from A, and indeed it's being called upon navigation. CausingFoo
to unsubscribe fromAppState
. Maybe this isn't a bug and it's working as intended, but then how do I unsubscribe? Most importantly, how to avoid being unsubscribed upon navigation?