Search code examples
navigationblazorrefresh

How can I refresh the nav menu in my Blazor application?


I have a component that updates options on the NavMenu. I want to refresh it. This is what I have tried:

_categoriesRepository.Create(Category);
await _jsRuntime.ToastrSuccess("Category Created");
_navigationManager.NavigateTo($"/category/{fileType}/{fileRoot}", true);

This actually works but the await on the toastr notification is obliterated by the page refresh, so I thought:

_categoriesRepository.Create(Category);
_navigationManager.NavigateTo(_navigationManager.Uri, true);
await _jsRuntime.ToastrSuccess("Category Created");
_navigationManager.NavigateTo($"/category/{fileType}/{fileRoot}");

But the does not return me to the right page. It leaves me on the page I am on.

Ideally, I'd like to just refresh the NavMenu component. Is this possible? This way I could refresh the component and then navigate to the page that I wish to and then the toaster would work as expected. Or is there another way to refresh the entire page and then show the toaster notification and then navigate to the correct page?


Solution

  • You are using brute force to refresh your display:

    _navigationManager.NavigateTo($"/category/{fileType}/{fileRoot}", true);
    

    Resets the SPA. And destroys the Toast!

    To do this properly your service needs to look something like this.

    Note:

    1. There's an event that gets raised whenever a new link is added.
    2. I use records and IEnumerables to make the data immutable.
    public class NavigationService
    {
        private List<NavigationItem> _links = new List<NavigationItem>();
    
        public IEnumerable<NavigationItem> Links => _links.AsEnumerable();
    
        public event EventHandler? LinksChanged;
    
        public void AddLink(NavigationItem link)
        {
            if (!_links.Any(item => item == link))
            {
                _links.Add(link);
                this.LinksChanged?.Invoke(this, EventArgs.Empty);
            }
        }
    }
    
    public record NavigationItem(string Title, string Url);
    

    Update NavMenu to inject the service and register the LinksChanged event to update the component.

    Note:

    1. The component registers the event hanlder in OnInitialized.
    2. The component implements IDisposable and unregisters the event handler in Dispose.
    @inject NavigationService NavigationService
    @implements IDisposable
    
    //...
    
            @foreach(var link in this.NavigationService.Links)
            {
                <div class="nav-item px-3">
                    <NavLink class="nav-link" href="@link.Url">
                        <span class="oi oi-list-rich" aria-hidden="true"></span> @link.Title
                    </NavLink>
                </div>
            }
    
        </nav>
    </div>
    
    @code {
        private bool collapseNavMenu = true;
    
        private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
    
        protected override void OnInitialized()
            => this.NavigationService.LinksChanged += this.OnNavChanged;
    
        private void ToggleNavMenu()
            => collapseNavMenu = !collapseNavMenu;
    
        private void OnNavChanged(object? sender, EventArgs e)
            => this.InvokeAsync(StateHasChanged);
    
        public void Dispose()
            => this.NavigationService.LinksChanged -= this.OnNavChanged;
    }
    

    And the demo page to add the link (once):

    @page "/"
    @inject NavigationService NavigationService
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <div>
        <button class="btn btn-dark" @onclick=AddLink>Add Link</button>
    </div>
    
    @code {
        private void AddLink()
            => this.NavigationService.AddLink(new("Fred", "/Counter"));
    }