I have a ToolbarItem that has a text string bound to an extension method attached to my CurrentUser. It only updates if I specifically call StateHasChanged from within the component itself, eg. by adding a click event:
@inject ICurrentUser _currentUser; <div class="btn"> <i class="far fa-chart-pie" @onclick="ShowNotifications">@_currentUser.CurrentDivision()</i> </div> @code { private async Task ShowNotifications() { await Message.Info("TODO: Switch Divisions"); StateHasChanged(); }
Changing the value of CurrentDivision outside of the component doesn't trigger a refresh.
There is only one point in the application where this value changes, when a user selects a value from a dropdown list. I tried getting a reference to the component, with the view that I would add a "Refresh" method to it that would call StateHasChanged ... but I can't cast the object:
var toolbar = _toolbarManager.GetAsync("Main"); var item = toolbar.Result.Items[0]; ToolbarNotificationDivision notification = item as ToolbarNotificationDivision; // this won't compile
Is there another way I can force a StateHasChanged on the main toolbar component? Or, preferably, call a custom method (eg. "Refresh") on a toolbar component?
Thanks!
T
3 Answer(s)
-
2
Hi @TonyH
You can publish an event inside the application to call StateHasChanged() method.
I'll share an example for UnreadCount for notifications. Firstly I'll create a NotificationData class and register it as scoped into service collection.
Scoped
is important here because if you define it as Transient, a new instance will be created each time and it can't keep its state. If you define it as Singleton, it's ok in Blazor WASM but it won't work on Blazor Server, all the connected users will use the same instance of that object. So, Scoped is the best way in here.1- Define
NotificationData.cs
first.public class NotificationData : IScopedDependency { private int unreadCount; public int UnreadCount { get => unreadCount; set { unreadCount = value; UnreadCountChanged?.Invoke(this, new EventArgs()); } } public event EventHandler UnreadCountChanged; }
2- Create a notification component with name
NotificationsComponent.razor
for toolbar@inject NotificationData NotificationData <div class="nav-link"> <i class="fa fa-bell"></i> @if (NotificationData.UnreadCount > 0) { <span class="position-absolute top-0 badge rounded-pill bg-danger"> @NotificationData.UnreadCount <span class="visually-hidden">unread messages</span> </span> } </div> @code{ protected override Task OnInitializedAsync() { NotificationData.UnreadCountChanged += (s, e) => StateHasChanged(); return base.OnInitializedAsync(); } }
3- Add it to the toolbar
public class MyToolbarContributor : IToolbarContributor { public Task ConfigureToolbarAsync(IToolbarConfigurationContext context) { if(context.Toolbar.Name == StandardToolbars.Main) { context.Toolbar.Items.Add(new ToolbarItem(typeof(NotificationsComponent))); } return Task.CompletedTask; } }
4- Don't forget to configure it in the module file
Configure<AbpToolbarOptions>(options => { options.Contributors.Add(new MyToolbarContributor()); });
5- Inject the
NotificationData
wherever you want and make changes. (In my case I've injected it into Index.razor)@inject NotificationData NotificationData <Button Color="Color.Success" @onclick="@(()=> NotificationData.UnreadCount++)" >Increase Unread Count</Button> <Button Color="Color.Danger" @onclick="@(()=> NotificationData.UnreadCount--)" >Decrease Unread Count</Button>
6- See the result:
-
2
PERFECT!
Not only is this exactly what I needed to achieve, your explanation and code samples are some of the best explanations I've seen so far. Nothing assumed, nothing left out.
Might be worth adding this sample to your official documentation?
Thanks!
T
-
1
Thanks for your feedback.
Additionally, Local Event Bus can be used in this scenario, but regular csharp events are much more useful for UI changes. Also, creating a new object like NotificationData allows to manage component's data independently from the component, so it's an abstraction over UI.