Dynamic page size in BlazorBootstrap Grid - c#

I want a page size based on available window height.
I'm able to get window height and calculate how many row can fit but I'm not able to change page size after initialization.
Here's a code
Page.razor
<Grid #ref="grid" TItem="Data.Connection" class="table table-hover table-bordered table-striped" DataProvider="DataProvider" AllowPaging="true" PageSize="#gridPageSize" AllowSorting="true" Responsive="true" >
<GridColumn TItem="Data.Connection" TextNoWrap="true" HeaderText="Name" PropertyName="Name" SortString="Name" SortKeySelector="item => item.Name">
#context.Name
</GridColumn>
<GridColumn TItem="Data.Connection" TextNoWrap="true" HeaderText="Source Type" PropertyName="SourceType" SortString="SourceType" SortKeySelector="item => item.SourceType">
#context.SourceType
</GridColumn>
<GridColumn TItem="Data.Connection" TextNoWrap="true" HeaderText="Location" PropertyName="Location" SortString="Location" SortKeySelector="item => item.Location">
#context.Location
</GridColumn>
<GridColumn TItem="Data.Connection" TextNoWrap="true" HeaderText="Status" PropertyName="Status" SortString="Status" SortKeySelector="item => item.Status">
#context.Status
</GridColumn>
<GridColumn TItem="Data.Connection" TextNoWrap="true" HeaderText="Action" Sortable="false">
<Button #onclick="()=>Edit(context.Id)" Class="btn btn-info oi oi-pencil" TooltipTitle="Edit" />
<Button #onclick="()=>Delete(context.Id)" Class="btn btn-danger oi oi-trash" TooltipTitle="Delete" />
</GridColumn>
</Grid>
Page.razor.cs
public partial class Page
{
[Inject]
ConnectionService service { get; set; }
[Inject]
IJSRuntime jsr { get; set; }
BlazorBootstrap.Grid<Connection>? grid { get; set; }
int gridPageSize { get; set; } = 10;
protected override async Task OnInitializedAsync()
{
OnWindowResize();
}
void OnWindowResize() // this will call when window height change
{
var winHeight = await jsr.InvokeAsync<int>("getHeight") - 20;
gridPageSize = Convert.ToInt32(winHeight/47);
//grid.PageSize = Convert.ToInt32(winHeight/47); // tried with ref obj but not worked
}
private async Task<GridDataProviderResult<Connection>> DataProvider(GridDataProviderRequest<Connection> request)
{
//OnWindowResize() //can I also set it here?
string sortString = "";
SortDirection sortDirection = SortDirection.None;
if (request.Sorting is not null && request.Sorting.Any())
{
sortString = request.Sorting.FirstOrDefault().SortString;
sortDirection = request.Sorting.FirstOrDefault().SortDirection;
}
int totalCount = 0;
data = await service.GetGridDataAsync(request.Filters, ref totalCount, request.PageNumber, request.PageSize, sortString, sortDirection);
return await Task.FromResult(new GridDataProviderResult<Connection> { Data = data, TotalCount = totalCount });
}
}
Here's a article I'm following..
Lib: https://getblazorbootstrap.com
Grid Tutorial: https://getblazorbootstrap.com/docs/components/grid#server-side-filtering-paging-and-sorting

Test.Razor
#page "/Test"
#inject IJSRuntime JsRuntime
#*Use any 3rd party grid with height property*#
<table class="table-bordered" height="#GridHeight">
</table>
#code{
private string GridHeight = "500px";
public class WindowDimension
{
public int Width { get; set; }
public int Height { get; set; }
}
protected override async Task OnInitializedAsync()
{
var dimension = await JsRuntime.InvokeAsync<WindowDimension>("getWindowDimensions");
GridHeight = (dimension.Height-100).ToString() + "px";//calculate based on any header exists(100 is my header height) !!!
}
}
Please try this solution.

Related

Radzen Datagrid seems to be loading twice

I have a radzen datagrid on a Blazor Server side app, that seems to be loading twice, this is, when first opening the page all data flashes for half a second (I took a screenshot, the data is show, not a blank grid) and then it switched to loading a it takes about 2 seconds and then shows the content of the grid.
I have been using as per radzen examples the "IsLoading" property to fill up data, I'll put an abridged version of the datagrid and of my code to show what I have.
Razor section:
#page "/projectlist"
#page "/"
#inject ISqlData _db
#inject AuthenticationStateProvider AuthenticationStateProvider
#inject NavigationManager navigationManager
#if (IsDetail == true)
{
<ProjectDetail OnDetailShown="UpdateDetailView" CalendarPeriod="CalendarPeriod" Project="Project"></ProjectDetail>
}
else
{
<h3><p class="text-center ">Project List</p></h3>
<RadzenPanel Style="width: calc(100vw - 80px)">
<RadzenDataGrid style="height: calc(100vh - 175px)" AllowPaging="true" AllowColumnResize="true" PageSize="20" IsLoading="IsLoading" AllowSorting="true" ShowPagingSummary="true" AllowColumnReorder="true" AllowMultiColumnSorting="true" AllowFiltering="true" FilterMode="FilterMode.Simple" FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" Data="#Projects" TItem="Project" >
<Columns>
<RadzenDataGridColumn TItem="Project" Property="ProjectNumber" Title="Project Number" Sortable="false" FilterOperator="FilterOperator.Contains" Width="130px" Pickable="false" Frozen="true" >
<Template Context="data"><RadzenButton Click=#(args => OnClick(data.ProjectId, data.ProjectStatus)) Shade="Shade.Dark" Text="#data.ProjectNumber" Size="ButtonSize.Small" ButtonStyle="ButtonStyle.Success" /></Template>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="Project" Property="Name" Title="Project Name" MinWidth="300px" />
<RadzenDataGridColumn TItem="Project" Property="ContractType" Title="Contract Type" MinWidth="300px" />
<RadzenDataGridColumn TItem="Project" Property="PtdUnbilled" Title="Beginning WIP Balance" FormatString="{0:0,0.00}" TextAlign="TextAlign.Right" MinWidth="210px" Width="210px" />
</Columns>
</RadzenDataGrid>
</RadzenPanel>
}
and Code section:
public IEnumerable<Project> Projects;
private Employee Employee { get; set; } = null!;
private string PersonnelNo { get; set; } = null!;
public string EmployeeAdName { get; set; }
public CalendarPeriod CalendarPeriod { get; set; } = null!;
public IEnumerable<ProjectWip> ProjectWipCalculations { get; set; }
public bool IsDetail { get; set; }
public Project Project { get; set; }
public bool IsLoading { get; set; }
protected override async Task OnParametersSetAsync()
{
}
protected override async Task OnInitializedAsync()
{
IsLoading = true;
EmployeeAdName = (await AuthenticationStateProvider.GetAuthenticationStateAsync()).User.Identity!.Name!;
CalendarPeriod = await _db.GetCalendarPeriodAsync();
Employee = await _db.GetEmployeeDataAsync(EmployeeAdName);
PersonnelNo = Employee.PersonnelNumber;
Projects = await _db.GetProjectsAsync(PersonnelNo);
var enumerable = Projects.ToList();
var projectList = enumerable.Select(x => x.ProjectId).ToArray();
ProjectWipCalculations = await _db.GetCurrentMonthWIPData(projectList, CalendarPeriod.PeriodFrom);
foreach (var project in enumerable)
{
var projectWip = ProjectWipCalculations.FirstOrDefault(p => p.ProjectId == project.ProjectId);
if (projectWip != null)
{
project.CurrMonthInvoiceTotal = projectWip.CurrMonthInvoiceTotal;
}
}
IsLoading = false;
}
private void OnClick(int projectId, string projectStatus)
{
IsDetail = true;
Project = Projects.First(x => x.ProjectId == projectId);
Project.ProjectStatus = projectStatus;
}
private void UpdateDetailView()
{
IsDetail = false;
}
If I remove the "IsLoading" property the only difference is that the grid flashess for half a second all filled and then it blanks for about 2 seconds and the is shown, the "IsLoading" just renders an animation in the middle for a bit.
I don't entirely understand what is happening, if maybe the grid is being filled and then the call is done again to fill it? (I have all code in OnInitializedAsync
I've added a small gif showing what I mean below
Thats not a radzen thing.
Is because blazor, loads twice.
That is because blazor first prerenders the page to show the user, and then loads the full page.
If you want to disable this you can disable prerender of the blazor app.
How to disable Blazor server side pre-render?
The other solution is setting onload grid property to true by defaultt , and then in the event onAfterRender method if is first render set loading to false.
Maybe use OnAfterRnderAsync()
protected override Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender == true)
{
ReloadChart();
}
return base.OnAfterRenderAsync(firstRender);
}
Try using fields instead of properties in general to avoid triggering parameter set events.
Also, a trick i use in my projects is to initialize task-fields using ??= then await them. That prevents the method to fire twice.
Edit: Added code example
private Task? _doSomethingTask;
protected override async Task OnInitializedAsync()
{
_doSomethingTask ??= DoSomethingAsync(_arg);
await _doSomethingTask;
}

Issues with using cascading parameters for passing values

I have two blazor components the first component calls a method
<CascadingValue Value="this">
#ChildContent
</CascadingValue>
#code{
[Parameter] public RenderFragment? ChildContent { get; set; }
public Debtor? CurrentDebtor { get; private set; }
public async Task<Debtor> ConsolitdateCurrentDebtor(int debtorID)
{
Debtor? currentDebtor = null;
if (DebtorStoreList != null && DebtorStoreList.Count > 0)
{
currentDebtor = DebtorStoreList.Where(d => d.ID == debtorID).Single();
}
else
{
currentDebtor = await _debtorService.GetDebtorInformation();
}
CurrentDebtor = currentDebtor;
// StateHasChanged(); unless called no update takes place
return currentDebtor;
}
}
Now I have a property called CurrentDebtor in this AppState class. One blazor component comes and calls this method and sets the Current Debtor.
From the other component, i am doing something like
#code {
Debtor? debtor;
[CascadingParameter]
AppState AppState{ get; set; }
protected override async Task OnInitializedAsync()
{
debtor = AppState.CurrentDebtor;
}
}
However the CurrentDebtor is coming null, the only way it doesn't come null is if StateHasChanged() is called. Is there anyway without forcing it to call Statehaschanged to populate it correctly?
Trying to "wire" multiple components together using cascading parameters and two way binding isn't always the best way to do things, particularly sub-component to sub-component within a page (which it appears you are trying to do from the information shown in the question).
There is an alternative approach using the standard event model.
My Debtor class
public class Debtor
{
public Guid Id { get; set; }
public string? Name { get; set; }
}
A Debtor view service:
public class DebtorViewService
{
public Debtor Record { get; private set; } = default!;
public event EventHandler<Debtor>? DebtorChanged;
public DebtorViewService()
{
// Get and assign the Debtor Store DI service
}
public async ValueTask GetCurrentDebtor(Guid debtor)
{
// Emulate getting the Debtor from the Debtor Service
await Task.Delay(1000);
this.Record = new Debtor() { Name=$"Fred {DateTime.Now.ToLongTimeString()}", Id = Guid.NewGuid()};
this.DebtorChanged?.Invoke(this, this.Record);
}
}
Registered in services like this:
builder.Services.AddScoped<DebtorViewService>();
A Component to get the Debtor:
GetDebtor.razor
#inject DebtorViewService Service
<div class="m-2 p-3 bg-dark text-white">
<h5>GetDebtor Component</h5>
<div>
<button class="btn btn-secondary" #onclick=this.GetCurrentDebtor>Get Debtor</button>
</div>
</div>
#code {
private async Task GetCurrentDebtor()
=> await Service.GetCurrentDebtor(Guid.Empty);
}
A component to show the Debtor:
ShowDebtor.razor
#inject DebtorViewService Service
#implements IDisposable
<div class="m-2 p-3 bg-info">
<h5>ShowDebtor Component</h5>
#if (this.Service.Record is not null)
{
<div>
Id : #this.Service.Record.Id
</div>
<div>
Name : #this.Service.Record.Name
</div>
}
</div>
#code {
protected override void OnInitialized()
=> this.Service.DebtorChanged += this.OnDebtorChanged;
private void OnDebtorChanged(object? sender, Debtor value)
=> this.InvokeAsync(this.StateHasChanged);
public void Dispose()
=> this.Service.DebtorChanged -= this.OnDebtorChanged;
}
And a demo page:
#page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<GetDebtor />
<ShowDebtor />
<SurveyPrompt Title="How is Blazor working for you?" />
#code{
}
The data and the change event is now in a single shared (Scoped) DI service instance that everyone has access to.
You should be able to build your components based on this code.

Force Blazor component to rerender when url query changes

Everything I've read points me to using NavigationManager.LocationChanged event, however that does not detect when only the query string changes. Therefore, nothing fires.
I've tried what is suggested here and that does not do what I am looking for.
I need my component to rerender everytime the query parameters in the current route changes. Has anyone been able to do this?
UPDATE:
Here is my code example:
#page "/accounts"
#if (Accounts != null)
{
<table>
<tbody>
#foreach (var account in Accounts)
{
<tr>
<td>
<NavLink href="/accounts/?accountId={account.AccountId}">#Account.Name</NavLink>
</td>
</tr>
}
</tbody>
</table>
}
#if (Account != null)
{
<div>
<h2>#Account.Name</h2>
</div>
}
#implements IDisposable
#inject NavigationManger NavManager
#inject AccountService AccountService
#code {
protected Guid Id { get; set; } = Guid.NewGid();
protected Guid AccountId { get; set; } = Guid.Empty;
protected List<AccountModel> Accounts { get; set; }
protected AccountModel Account { get; set; }
protected override void OnInitialized()
{
NavManager.LocationChanged += LocationChanged;
}
protected async override Task OnParametersSetAsync()
{
if (AccountId != Guid.Empty)
{
Account = await AccountService.GetAsync(AccountId);
}
else
{
Accounts = await AccountService.GetAllAsync();
}
StateHasChanged();
}
void LocationChanged(object sender, LocationChangedEventArgs e)
{
Id = Guid.NewGuid();
StateHasChanged();
}
void IDisposable.Dispose()
{
NavManager.LocationChanged -= LocationChanged;
}
}
The problem here is LocationChanged(object, LocationChangedEventArgs) does not fire when only the query parameters change. Which makes sense, the route is the same /accounts.
Bascically I have one page to show a list of Accounts and view a single account. I want the route to be /accounts when view the list and '/accounts?accountId=someAccountId' to be the route when im viewing one. Using standard NavLink component does not trigger a rerender of the component and neither does the LocationChanged event. Not even with setting a new Id value. Seeing as to how StateHasChanged only rerenders the component if a value has changed.
I ended up getting what I wanted to happen work like this:
#page "/accounts"
#if (Accounts != null)
{
<table>
<tbody>
#foreach (var account in Accounts)
{
<tr>
<td>
<NavLink href="/accounts/?accountId={account.AccountId}">#Account.Name</NavLink>
</td>
</tr>
}
</tbody>
</table>
}
#if (Account != null)
{
<div>
<h2>#Account.Name</h2>
</div>
}
#implements IDisposable
#inject NavigationManger NavManager
#inject AccountService AccountService
#code {
protected Guid Id { get; set; } = Guid.NewGid();
protected Guid AccountId { get; set; } = Guid.Empty;
protected List<AccountModel> Accounts { get; set; }
protected AccountModel Account { get; set; }
protected override void OnInitialized()
{
NavManager.LocationChanged += LocationChanged;
}
protected async override Task OnParametersSetAsync()
{
if (AccountId != Guid.Empty)
{
Account = await AccountService.GetAsync(AccountId);
}
else
{
Accounts = await AccountService.GetAllAsync();
}
StateHasChanged();
}
async void LocationChanged(object sender, LocationChangedEventArgs e)
{
if (AccountId != Guid.Empty)
{
Account = await AccountService.GetAsync(AccountId);
}
else
{
Accounts = await AccountService.GetAllAsync();
}
StateHasChanged();
}
void IDisposable.Dispose()
{
NavManager.LocationChanged -= LocationChanged;
}
}
I ended up just calling all the same code I want to run on a first render manually. I was hoping (falsely assuming) that I could tell it to just rereun the component lifecycle from the top automatically by changing one local property and then calling StateHasChanged.
Now when I toggle between "/accounts" and "/accounts?accountId=someId" the component rerenders as I want.
I do this in a wasm app works well
set this on your page where you want to receive the route parameter
#page "/pagename/{text?}"
<div>#Text</div>
then
#code {
[Parameter]
public string? Text { get; set; }
protected override void OnInitialized()
{
Text = Text ?? "fantastic";
}
protected override void OnParametersSet()
{
Text = Text ?? "Name Not Supplied";
}
}
The will re-render any data changes, AFAIK you can't force a component to re-render without reloading it, only the changed subscribers, which makes sense, as re-renders are expensive.
Doc's look under router parameters
You can bind the StateHasChanged() to the function/logic which changes your query string, which will re-render your component.

Is it possible to load dynamically existing page in a Bootstrap Modal body

I'm new to a Blazor and now I'm working to Blazor WebAssembly project.
I have couple razor page with a table where I'm displaying data from SQL. When I click in one of the table rows it opens a page where I can do the CRUD operation.
Now, instead of opening a page to do the CRUD operation I need to open a bootstarp modal and do the CRUD operation.
I'm doing a generic ModalComponent where I have the header and the footer of the modal.
Is it possibile to load dynamically the body of the model, which in this case it will be the CRUD operation pages that I already had done?
The code below demonstrates how to implement a generic modal dialog i.e. a wrapper for any component/page that you want to display in modal mode. The concrete implementation of IModalDialog shows how to implement a Bootstrap version.
I use a more complex version of this. My edit/View components are written to run in modal or full page mode.
Support Classes
First three support classes that are fairly self-evident:
public class ModalResult
{
public ModalResultType ResultType { get; private set; } = ModalResultType.NoSet;
// Whatever object you wish to pass back
public object? Data { get; set; } = null;
// A set of static methods to build a BootstrapModalResult
public static ModalResult OK() => new ModalResult() { ResultType = ModalResultType.OK };
public static ModalResult Exit() => new ModalResult() { ResultType = ModalResultType.Exit };
public static ModalResult Cancel() => new ModalResult() { ResultType = ModalResultType.Cancel };
public static ModalResult OK(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.OK };
public static ModalResult Exit(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.Exit };
public static ModalResult Cancel(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.Cancel };
}
public class ModalOptions
{
// Whatever data you want to pass
// my complex version uses a <string, object> dictionary
}
public enum ModalResultType
{
NoSet,
OK,
Cancel,
Exit
}
IModalDialog Interface
Now the modal dialog Interface.
using Microsoft.AspNetCore.Components;
public interface IModalDialog
{
ModalOptions Options { get; }
// Method to display a Modal Dialog
Task<ModalResult> ShowAsync<TModal>(ModalOptions options) where TModal : IComponent;
// Method to update the Modal Dialog during display
void Update(ModalOptions? options = null);
// Method to dismiss - normally called by the dismiss button in the header bar
void Dismiss();
// Method to close the dialog - normally called by the child component TModal
void Close(ModalResult result);
}
The Base Bootstrap Implementation
There's no JS.
I use a TaskCompletionSource to make the open/close an async process.
The component to display is passed as part of the open call.
#inherits ComponentBase
#implements IModalDialog
#if (this.Display)
{
<CascadingValue Value="(IModalDialog)this">
<div class="modal" tabindex="-1" style="display:block;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" #onclick="() => Close(ModalResult.Exit())"></button>
</div>
<div class="modal-body">
#this.Content
</div>
</div>
</div>
</div>
</CascadingValue>
}
#code {
public ModalOptions Options { get; protected set; } = new ModalOptions();
public bool Display { get; protected set; }
protected RenderFragment? Content { get; set; }
protected TaskCompletionSource<ModalResult> _ModalTask { get; set; } = new TaskCompletionSource<ModalResult>();
public Task<ModalResult> ShowAsync<TModal>(ModalOptions options) where TModal : IComponent
{
this.Options = options ??= this.Options;
this._ModalTask = new TaskCompletionSource<ModalResult>();
this.Content = new RenderFragment(builder =>
{
builder.OpenComponent(1, typeof(TModal));
builder.CloseComponent();
});
this.Display = true;
InvokeAsync(StateHasChanged);
return this._ModalTask.Task;
}
public void Update(ModalOptions? options = null)
{
this.Options = options ??= this.Options;
InvokeAsync(StateHasChanged);
}
public async void Dismiss()
{
_ = this._ModalTask.TrySetResult(ModalResult.Cancel());
this.Display = false;
this.Content = null;
await InvokeAsync(StateHasChanged);
}
public async void Close(ModalResult result)
{
_ = this._ModalTask.TrySetResult(result);
this.Display = false;
this.Content = null;
await InvokeAsync(StateHasChanged);
}
}
Demo
A simple "Edit" component:
#inject NavigationManager NavManager
<h3>EditForm</h3>
<div class="p-3">
<button class="btn btn-success" #onclick=Close>Close</button>
</div>
#code {
[CascadingParameter] private IModalDialog? modal { get; set; }
private void Close()
{
if (modal is not null)
modal.Close(ModalResult.OK());
else
this.NavManager.NavigateTo("/");
}
}
And a test page:
#page "/"
<div class="p-2">
<button class="btn btn-primary" #onclick=OpenDialog>Click</button>
</div>
<div class="p-2">
result: #Value
</div>
<ModalDialog #ref=this.modal />
#code {
private string Value { get; set; } = "Fred";
private IModalDialog? modal;
private IModalDialog Modal => modal!;
private async void OpenDialog()
{
var options = new ModalOptions();
var ret = await Modal.ShowAsync<EditorForm>(new ModalOptions());
if (ret is not null)
{
Value = ret.ResultType.ToString();
}
StateHasChanged();
}
}

Having trouble getting Blazor to rerender component on change in state shared between components

Context
I'm following a pattern that's something like https://chrissainty.com/3-ways-to-communicate-between-components-in-blazor/ or https://jonhilton.net/blazor-state-management/
So I have two razor components Hen.razor and Basket.razor as child components inside index.razor. A button inside Hen adds to the number of eggs displayed inside Basket.
The button calls a service I've injected called EggService that handles the number of eggs in Basket by storing it in local storage using Blazored.LocalStorage.
Problem
Clicking the button increases the number of eggs in local storage but doesn't update the Basket component unless I refresh.
Code
Repository for convenience: https://github.com/EducatedStrikeCart/EggTest/
EggService:
using Blazored.LocalStorage;
namespace BlazorSandbox.Services
{
public class EggService
{
public event Action OnChange;
private readonly ILocalStorageService _localStorageService;
public EggService(ILocalStorageService localStorageService)
{
this._localStorageService = localStorageService;
}
public async Task<int> GetEggs()
{
int currentEggs = await _localStorageService.GetItemAsync<int>("Eggs");
return currentEggs;
}
public async Task AddEgg()
{
int newEggs = await GetEggs();
if (newEggs == null)
{
newEggs = 0;
} else
{
newEggs += 1;
}
await _localStorageService.SetItemAsync("Eggs", newEggs);
OnChange?.Invoke();
}
}
}
Hen:
#using BlazorSandbox.Services
#inject EggService EggService
<div>
<h3>Hen</h3>
<button #onclick="TakeAnEgg">Take an egg</button>
</div>
#code {
public async Task TakeAnEgg()
{
await EggService.AddEgg();
}
}
Egg:
#using BlazorSandbox.Services
#inject EggService EggService
#implements IDisposable
<div>
<h3>Basket</h3>
Eggs: #Eggs
</div>
#code {
public int Eggs { get; set; }
protected override async Task OnInitializedAsync()
{
Eggs = await EggService.GetEggs();
EggService.OnChange += StateHasChanged;
}
public void Dispose()
{
EggService.OnChange -= StateHasChanged;
}
}
Index:
#page "/"
#using BlazorSandbox.Services
#inject EggService EggService
<h1>
Eggs!
</h1>
<div class="d-flex flex-row justify-content-around">
<Hen />
<Basket />
</div>
#code {
}
Program.cs:
using BlazorSandbox;
using BlazorSandbox.Services;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Blazored.LocalStorage;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped<EggService>();
builder.Services.AddBlazoredLocalStorage();
await builder.Build().RunAsync();
Solution
Special thank you to person who deleted their comment. I'm kind of new to asking questions on StackOverflow so I'm sorry if I should've selected your answer as the Answer!
#code {
public int Eggs { get; set; }
protected override void OnInitialized()
{
//Subscribe
EggService.OnChange += UpdateEgg;
//Set value of Eggs on load
UpdateEgg();
}
public void UpdateEgg()
{
// Set value of Eggs to new value and trigger component rerender
InvokeAsync(async () => {Eggs = await EggService.GetEggs(); StateHasChanged(); });
}
public void Dispose()
{
// Unsubscribe
EggService.OnChange -= UpdateEgg;
}
}
There are a few oddities in your code.
if (newEggs == null)
This is an int, so it can never be null. The default value for int is 0. You should be seeing a warning for this.
Eggs = await EggService.GetEggs();
After you set Eggs here, you never update it anywhere in your code! So even if you call StateHasChanged, there is nothing to update.
What you will want to do is keep track of the egg count inside of your EggService and then inside of your Basket component you will need a way to know that the egg count has increased so you can update your Egg property and then call StateHasChanged. Let me know if you need help with this.

Categories