Now that I’ve finally had opportunity to work on a notification service for the front-end, I wanted to share with everyone. This service was originally something that I piloted in my team’s project, Fast Pass, that was created to demonstrate how Text Analytics for Health can be used to extract medical information from physical patient documents to persist to an EMR. However, I recently improved it to a level of satisfaction worthy of sharing in an older project, FhirBlaze, which we recently updated with OpenAI to generate FHIR queries.
Nearly every application that involves activity with users is one where you want to present them with feedback on those user actions. A notification service is a great way to decouple that feedback from routes, pages, and other components. Plus it seems really slick!
To get started, our first step will be to define our notifications.
public class Notification
{
public string Title { get; set; } = string.Empty;
public string Message { get; set; } = string.Empty;
public string TimeSinceStr
{
get
{
var difference = DateTime.Now - CreatedAt;
if (difference.Days > 0)
return difference.Days.ToString() + "day(s) ago";
if (difference.Hours > 0)
return difference.Hours.ToString() + "hour(s) ago";
return difference.Minutes.ToString() + "min(s) ago";
}
}
public DateTime CreatedAt { get; set; } = DateTime.Now;
}
Title, Message, and CreatedAt are pretty straightforward, but TimeSinceStr is just a quick method to show how long ago the message was created.
Now that we have our notification class, we need a service to manage our notifications.
In our constructor we create a timer to remove the expired notifications and update our Blazor state from time to time so that later, when we create our component to show the notifications, it can show the continuously updated text saying how long ago the notifications were created.
That timer takes our RemoveExpiredNotifications method as a callback. The callback does exactly what it says on the tin. It checks the list for expired notifications and once it completes, it invokes the OnChange action. That action is what let’s components subscribe to changes to notifications. We also expose methods for adding and removing notifications manually.
Now we need a component to display our notification.
@using FhirBlaze.SharedComponents.Models;
@using FhirBlaze.SharedComponents.Services;
@inject NotificationService _notificationService;
<div class="toast show" role="alert" aria-live="assertive" aria-atomic="true" style="min-width: 300px; min-height: fit-content;">
<div class="toast-header d-flex">
<div class="p-2">
<strong class="me-auto mr-1">@Notification.Title</strong>
</div>
<div class="p-2">
<small class="text-muted">@Notification.TimeSinceStr</small>
</div>
<div class="ml-auto p-2">
<button type="button" class="btn btn-outline-secondary btn-sm py-0" style="font-size: 0.8em;" aria-label="Close" @onclick="DeleteNotification"><span class="oi oi-x"></span></button>
</div>
</div>
<div class="toast-body">
@Notification.Message
</div>
</div>
@code {
[Parameter]
public Notification Notification { get; set; } = default!;
[Parameter]
public string Title { get; set; } = string.Empty;
[Parameter]
public DateTime CreatedAt { get; set; } = DateTime.Now;
[Parameter]
public string Message { get; set; } = string.Empty;
private void DeleteNotification()
{
_notificationService.RemoveNotification(Notification);
}
}
This component is just for a single notification, we’ll next create a container to hold our notifications but it means that we can just take in our notification through a component parameter.
Time to put our Toast components into a container. This container will be helpful for styling and to scope the updating of state that needs to happen when our timer callback is triggered. You can see this where we add our StateHasChanged method as a subscriber to the OnChange event from our NotificationService which this component receives through Blazor dependency injection.
@using FhirBlaze.SharedComponents.Services;
@using FhirBlaze.SharedComponents.Models;
@using FhirBlaze.SharedComponents.Components;
@inject NotificationService _notificationService;
<div>
@* this random div is used to make scoped css work... *@
<div class="toast-container">
@foreach (var notification in _notificationService.Notifications)
{
<Toast Notification="@notification"></Toast>
}
</div>
</div>
@code {
protected override void OnInitialized()
{
_notificationService.OnChange += StateHasChanged;
}
}
Now we can just add these styles. Except for this style and inline styles, all the styling thus far has been Bootstrap which comes out of the box in the Blazor template. Please know that CSS is not my forte so if you have any comments or suggestions, I’d love to hear them.
/* ::deep required on everything to make scoped css work */
::deep .toast-container {
position: fixed;
bottom: 0px;
right: 20px;
display: flex;
flex-direction: column;
min-height: 0vh;
margin-top: 3.5rem;
}
Now we can put our ToastContainer component on our shared layout page and…
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4 auth">
<LoginDisplay />
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>
</div>
<ToastContainer></ToastContainer>
…then we can add notifications wherever we like in our components through the application and trust that our users will get the message.
catch (Exception e)
{
_notificationService.AddNotification(new Notification
{
Title = "FHIR query error",
Message = e.RecurseErrorMessages()
});
// use null to indicate error
if (ExecuteFhirOnCLickCallback.HasDelegate)
await ExecuteFhirOnCLickCallback.InvokeAsync(null);
}
It was quite easy to have add a notification service and provide a simple, effective way to consistently provide feedback to users on synchronous or asynchronous activities. We can eventually improve this notification service with state persistence, more control over the appearance of the notification based what kind of notification it is, adding snooze capabilities on the notification, etc.
If you liked this post or want to be part of a community of healthcare developers sharing knowledge and resources, check out our HLS Developer discord at https://aka.ms/HLS-Discord. We have links to all our content there and a bunch of channels to communicate with us and like-minded tech and healthcare people. Hope to see you there.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.