localizationblazorblazor-webassemblyblazor-jsinterop

Localization Blazor webassembly does not work if navigation anchor in present on the url


to develop a new website I've used a template, based on HTML, CSS and JS. Making out of it a classical ASP.NET Core (MVC) project works fine. Everything is running as a charm. But I've decided to "convert" it to a Blazor Webassembly project. My Blazor Webassembly App is running fine, except that if I navigate to a page section using an anchor tag, the localization component does not work any more.

My CultureSelector.razor component is:

@using System.Globalization
@inject IJSRuntime JS
@inject NavigationManager Navigation


<ul class="list-inline s-header__action s-header__action--lb">

    <select class="localeSettings" @bind="Culture">
       @foreach (var culture in supportedCultures)
       {
            <option class="localeSettingsOption" value="@culture">@culture.DisplayName</option>
       }
   </select>
    
</ul>


@code
{
    private CultureInfo[] supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("it-IT"),
        new CultureInfo("de-DE"),
    };

    private CultureInfo Culture
    {
        get => CultureInfo.CurrentCulture;
        set
        {
            if (CultureInfo.CurrentCulture != value)
            {
                var js = (IJSInProcessRuntime)JS;
                js.InvokeVoid("blazorCulture.set", value.Name);

                Navigation.NavigateTo(Navigation.Uri, forceLoad: true);
            }
        }
    }
}

My Program.cs file is:

using myProject;
using System.Globalization;
using Microsoft.JSInterop;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

builder.Services.AddLocalization();

var host = builder.Build();

CultureInfo culture;
var js = host.Services.GetRequiredService<IJSRuntime>();
var result = await js.InvokeAsync<string>("blazorCulture.get");

if (result != null)
{
    culture = new CultureInfo(result);
}
else
{
    culture = new CultureInfo("en-US");
    await js.InvokeVoidAsync("blazorCulture.set", "en-US");
}

CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;

await host.RunAsync();

On my Index.razor page I have the following anchor element:

<a href="#js__scroll-to-section" class="s-scroll-to-section-v1--bc g-margin-b-15--xs">
    <span class="g-font-size-18--xs g-color--white ti-angle-double-down"></span>
    <p class="text-uppercase g-color--white g-letter-spacing--3 g-margin-b-0--xs">@Loc["Learn More"]</p>
</a>

To handle scrolling to the desired section I have the following script:

    function handleScrollToSection() {
    let scrollToElement = $('a[href*=#js__scroll-to-]:not([href=#js__scroll-to-])')

    scrollToElement[0].addEventListener('click', (event) => {
        event.stopImmediatePropagation()

        if (location.pathname.replace(/^\//, '') == scrollToElement[0].pathname.replace(/^\//, '') && location.hostname == scrollToElement[0].hostname) {
            var target = $(scrollToElement[0].hash);

            target = target.length ? target : $('[name=' + scrollToElement[0].hash.slice(1) + ']');
            
            if (target.length) {
                $('html,body').animate({
                    scrollTop: target.offset().top - 90
                }, 1000);
                return false;
            }
        }
    })
}

This script makes it possible on a ASP.NET Core MVC (NOT Blazor WASM) that the anchor #js__scroll-to-section is removed from the url.

In the case of my Blazor app, this does not work and the anchor remains in the url https://localhost:7123/#js__scroll-to-section

If that is the case, my localization component does not work any more. I select a language from the dropdown menu and nothing happens.


Solution

  • Basically I've found 2 ways to get it running. They share the same concept and in the second case I've added the cosmetic feature to rewrite the url after clicking to the scroll-to-element link, so that the anchor tag disappear. But keep in mind: disappeared means that it is not more visible, but still in the Navigation.Uri

    So, first of all, substitute the line of code Navigation.NavigateTo(Navigation.Uri, forceLoad: true); with this one: Navigation.NavigateTo(Navigation.BaseUri, forceLoad: true); in the CultureSelector.razor component.

    This makes the whole magic!

    If you need also the cosmetic feature to let the anchor tag disappear from the url while scrolling to the desired page element, than you can add a @onfocusout="ChangeUrl" on your anchor element, like I did here in my Index.razor page:

    <a href="#js__scroll-to-section" @onfocusout="ChangeUrl" class="s-scroll-to-section-v1--bc g-margin-b-15--xs">
        <span class="g-font-size-18--xs g-color--white ti-angle-double-down"></span>
        <p class="text-uppercase g-color--white g-letter-spacing--3 g-margin-b-0--xs">@Loc["Learn More"]</p>
    </a>
    

    Do not modify href="#js__scroll-to-section" as it handles scrolling to the desired element. That is the reason why I'm using @onfocusout.

    In the @code section of the Index.razor add the ChangeUrl function:

    void ChangeUrl()
    {
        JsRuntime.InvokeVoidAsync("ChangeUrl", NavigationManager.BaseUri);
    }
    

    Finally, in your javascript file define the ChangeUrl function as follows:

        function ChangeUrl(url) {
        history.pushState(null, '', url)
    }
    

    This works fine for me and solved my problem.

    Happy coding!