Home > other >  Listening to state changes when a property value is changed in a service
Listening to state changes when a property value is changed in a service

Time:09-01

I have a class / service in my Blazor MAUI application that regularly manipulates the data that is stored within it. There is an internal schedular on a fixed interval that regenerates the value.

I have a Blazor component that reads the value from this service. When the value changes in my service, I would like my Blazor component to reflect that change.

Just to keep it simple, lets take the following:

public class EmployeeService {
    
    public int NumberOfEmployees { get; private set; }

    public EmployeeService() {
        // Logic to initialize a fixed scheduled for function
        // RecalculateNumberOfEmployees();
    }

    private async void RecalculateNumberOfEmployees() {
        numberOfEmployees  = 1;
    }
}
@path "/employees"
@inject EmployeeService Service

Number of employees: @Service.NumberOfEmployees

@code {
    
}

I found a recommendation here that uses a timer to invoke StateHasChanged() but I really, really don't like that approach. It seems like it is a waste of resources and an anti-pattern.

My next step is to make EmployeeService accept EventCallback from the Blazor component and store that in a list. This would allow any component to listen for changes in the EmployeeService class. When a component is unmounted, it will delete the callback.

Something like:

EmployeeService.cs

    public List<EventCallback> listeners { get; private set; } = new List<EventCallback>();

    public async void RegisterCallback(EventCallback callback) {
        listeners.ForEach(...notify listeners);
    }

    public async void DeregisterCallback(EventCallback callback) {
        listeners.Remove ... etc
    }

Employees.razor

...

@code {

    // register / deregister callback and listen for changes, invoke StateHasChanged()

}

Before I go down this route, are there any better design patterns that I could use for future components that would be better suited for this purpose? I feel like this is something that should already be baked into the framework but haven't seen anything that addresses it.

CodePudding user response:

You could use an event Action:

EmployeeService.cs

public class EmployeeService
{
    public int NumberOfEmployees { get; private set; }

    public EmployeeService() {
        // Logic to initialize a fixed scheduled for function
        // RecalculateNumberOfEmployees();
    }

    private async void RecalculateNumberOfEmployees() {
        numberOfEmployees  = 1;
        OnChange();
    }
    
    public event Action Change;
    private void OnChange() => Change?.Invoke();
}

Employees.razor

@inject EmployeeService EmployeeService
@implements IDisposable

Number of employees: @EmployeeService.NumberOfEmployees

@code {
    protected override void OnInitialized()
    {
        EmployeeService.Change  = EmployeeService_Change;
    }

    public void Dispose()
    {
        EmployeeService.Change -= EmployeeService_Change;
    }

    private async void EmployeeService_Change()
    {
        await InvokeAsync(StateHasChanged);
    }
}

Another possibility is to use the Event Aggregator pattern. Take a look at this library: https://github.com/mikoskinen/Blazor.EventAggregator

  • Related