State Management – DI Service Demo

Demo chia sẻ state giữa nhiều khu vực bằng DI Service. Service giữ state chung, bắn OnChange, component lắng nghe và gọi InvokeAsync(StateHasChanged).


1️⃣ Khu vực điều khiển state

Count: 0

LastAction: none

Xem code
Razor
@inject CounterStateService CounterState

<button class="btn btn-primary" @onclick="Increase">+ Increment</button>
<button class="btn btn-warning" @onclick="Decrease">- Decrement</button>
<button class="btn btn-danger" @onclick="ResetState">Reset</button>

<p><b>Count:</b> @CounterState.Count</p>
<p><b>LastAction:</b> @CounterState.LastAction</p>
C#

private void Increase()
{
    CounterState.Increment();
}

private void Decrease()
{
    CounterState.Decrement();
}

private void ResetState()
{
    CounterState.Reset();
}

2️⃣ Khu vực chỉ đọc state (dùng chung service)

Shared Count: 0

Shared LastAction: none

Khu vực này không tự thay đổi state, chỉ đọc từ cùng một service DI. Khi khu vực trên bấm nút, phần này cũng cập nhật theo.

Xem code
@inject CounterStateService CounterState

<p><b>Shared Count:</b> @CounterState.Count</p>
<p><b>Shared LastAction:</b> @CounterState.LastAction</p>

3️⃣ Theo dõi State Change bằng event OnChange

Render Count: 0

State Change Count: 0

Logs:
    Xem code
    Razor
    
    <p><b>Render Count:</b> @_renderCount</p>
    <p><b>State Change Count:</b> @_stateChangeCount</p>
    
    <ul>
    @foreach (var item in _logs)
    {
        <li>@item</li>
    }
    </ul>
    
    <button class="btn btn-outline-secondary" @onclick="ClearLogs">
        Clear Logs
    </button>
    
    C#
    
    protected override void OnInitialized()
    {
        CounterState.OnChange += HandleStateChanged;
    }
    
    private void HandleStateChanged()
    {
        _stateChangeCount++;
    
        _logs.Add(
            $"Count = {CounterState.Count}, Action = {CounterState.LastAction}"
        );
    
        InvokeAsync(StateHasChanged);
    }
    
    public void Dispose()
    {
        CounterState.OnChange -= HandleStateChanged;
    }
    
    protected override void OnAfterRender(bool firstRender)
    {
        _renderCount++;
    }
    

    4️⃣ Vì sao cần event OnChange?

    • Service đổi state không tự làm UI render lại.
    • Service chỉ nên giữ dữ liệu và bắn sự kiện thông báo thay đổi.
    • Component lắng nghe OnChange rồi gọi InvokeAsync(StateHasChanged).
    • Dùng InvokeAsync(StateHasChanged) an toàn hơn vì callback có thể đến từ ngoài UI context.

    5️⃣ Mã nguồn service DI

    Xem code service
    
    public class CounterStateService
    {
        public int Count { get; private set; } = 0;
        public string LastAction { get; private set; } = "none";
    
        public event Action? OnChange;
    
        public void Increment()
        {
            Count++;
            LastAction = $"Increment at {DateTime.Now:HH:mm:ss}";
            NotifyStateChanged();
        }
    
        public void Decrement()
        {
            Count--;
            LastAction = $"Decrement at {DateTime.Now:HH:mm:ss}";
            NotifyStateChanged();
        }
    
        public void Reset()
        {
            Count = 0;
            LastAction = $"Reset at {DateTime.Now:HH:mm:ss}";
            NotifyStateChanged();
        }
    
        private void NotifyStateChanged()
            => OnChange?.Invoke();
    }
    

    An error has occurred. This application may no longer respond until reloaded. Reload 🗙
    Web hosting by Somee.com