While using blazor, I want to be able to "go back" to a page I was before.
I found this issue and looks like it's a dead end?
This feature is something so basic, that I can't believe it doesn't exits.
Is there any walk around to make this "go back" functionality ?
Please notice that I cannot use window.goBack
or history.goBack
because my app doesn't create any history, and also shouldn't create any history.
The only way to "create" a history is to use the forceLoad
option of Navigation.NavigateTo
but if I do, it will try to load my entire app again, which is slow and I don't want to.
What you need is a page history state manager:
For the following example I'm using Blazor Wasm but you can use this example in Blazor Server as well.
In the client app I've added this class: PageHistoryState:
public class PageHistoryState
{
private List<string> previousPages;
public PageHistoryState()
{
previousPages = new List<string>();
}
public void AddPageToHistory(string pageName)
{
previousPages.Add(pageName);
}
public string GetGoBackPage()
{
if (previousPages.Count > 1)
{
// You add a page on initialization, so you need to return the 2nd from the last
return previousPages.ElementAt(previousPages.Count - 2);
}
// Can't go back because you didn't navigate enough
return previousPages.FirstOrDefault();
}
public bool CanGoBack()
{
return previousPages.Count > 1;
}
}
Then add this class to the services as a singleton:
builder.Services.AddSingleton<PageHistoryState>();
Inject it in your pages:
@inject WasmBlazor.Client.PageHistoryState PageHistoryState
In my markup then I've check to see if I can go back a page:
@if (PageHistoryState.CanGoBack())
{
<a href="@PageHistoryState.GetGoBackPage()">Go Back</a>
}
And I've overwritten OnInitialized()
protected override void OnInitialized()
{
PageHistoryState.AddPageToHistory("/counter");
base.OnInitialized();
}
I've done the same thing in the "fetch data" page, and I'm able to go back without the need of the JSInterop.
I ended up with a bit improved solution that wraps/incapsulates the NavigationManager, keeps everything in a single place and does not depend on Pages or something else. It also keeps the history buffer size in some reasonable range.
Navigation.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
namespace MyApp
{
public class Navigation : IDisposable
{
private const int MinHistorySize = 256;
private const int AdditionalHistorySize = 64;
private readonly NavigationManager _navigationManager;
private readonly List<string> _history;
public Navigation(NavigationManager navigationManager)
{
_navigationManager = navigationManager;
_history = new List<string>(MinHistorySize + AdditionalHistorySize);
_history.Add(_navigationManager.Uri);
_navigationManager.LocationChanged += OnLocationChanged;
}
/// <summary>
/// Navigates to the specified url.
/// </summary>
/// <param name="url">The destination url (relative or absolute).</param>
public void NavigateTo(string url)
{
_navigationManager.NavigateTo(url);
}
/// <summary>
/// Returns true if it is possible to navigate to the previous url.
/// </summary>
public bool CanNavigateBack => _history.Count >= 2;
/// <summary>
/// Navigates to the previous url if possible or does nothing if it is not.
/// </summary>
public void NavigateBack()
{
if (!CanNavigateBack) return;
var backPageUrl = _history[^2];
_history.RemoveRange(_history.Count - 2, 2);
_navigationManager.NavigateTo(backPageUrl);
}
// .. All other navigation methods.
private void OnLocationChanged(object sender, LocationChangedEventArgs e)
{
EnsureSize();
_history.Add(e.Location);
}
private void EnsureSize()
{
if (_history.Count < MinHistorySize + AdditionalHistorySize) return;
_history.RemoveRange(0, _history.Count - MinHistorySize);
}
public void Dispose()
{
_navigationManager.LocationChanged -= OnLocationChanged;
}
}
}
Then you can add this class to dependency injection as a singleton service and initialise.
Program.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace MyApp
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddSingleton<Navigation>();
// .. other services.
var host = builder.Build();
await Initialize(host);
await host.RunAsync();
}
private static async Task Initialize(WebAssemblyHost host)
{
host.Services.GetService<Navigation>();
// .. other initialization calls.
}
}
}
After that you can use it in any place you want using the Inject directive/attribute.
SomePage.cshtml
@page "/SomePage"
@inject Navigation Navigation
<h3>SomePage</h3>
<button @onclick="NavigateBackClick">Navigate Back</button>
@code {
private void NavigateBackClick()
{
Navigation.NavigateBack();
}
}
SomeService.cs
namespace MyApp
{
public class SomeService
{
private readonly Navigation _navigation;
public SomeService(Navigation navigation)
{
_navigation = navigation;
}
public void SomeMethod()
{
// ...
_navigation.NavigateBack();
}
}
}
I modified Diogo's answer above into what I feel is a much more elegant solution.
First, create a BasePageComponent.cs
class, which inherits from the ComponentBase
class:
// Using statements/namespace go here
[Authorize]
public class BasePageComponent: ComponentBase
{
[Inject]
protected NavigationManager _navManager { get; set; }
[Inject]
protected PageHistoryState _pageState { get; set; }
public BasePageComponent(NavigationManager navManager, PageHistoryState pageState)
{
_navManager = navManager;
_pageState = pageState;
}
public BasePageComponent()
{
}
protected override void OnInitialized()
{
base.OnInitialized();
_pageState.AddPage(_navManager.Uri);
}
}
This is what each of your pages will inherit from. It handles injecting the PageHistoryState
service, as well as appending a newly navigated page. It does this all "behind the scenes" of your actual pages.
Now, in a given page, you inherit from BasePageComponent
:
@page "/workouts/new"
@inherits BasePageComponent
/* ...RenderFragments/Razor view here...*/
@code {
/* ...properties here...*/
// This is an example of how to consume the _navManager and _pageState objects if desired, without any boilerplate code.
private void OnCancel()
{
_navManager.NavigateTo(_pageState.PreviousPage());
}
}
In my example component (stripped for brevity) it adds a new element to the page history stack with no markup besides inheriting from BasePageComponent
.
Taylor
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With