Setting the document title in your Blazor app

Setting the document title in your Blazor app

This article demonstrates an approach to setting the document title in a Blazor app.

With traditional web pages setting the page or document title is very straightforward.

<head>
    <title>Bacon’s MySpace page</title>
</head>

However with an SPA (Single Page App) it is a little different. Instead of loading a new HTML file for each page only one HTML file is loaded and the contents or body of that file is updated.

This means if you want the title to update you need to add a mechanism to do it. Changing the title in JavaScript is simply one line of code.

document.title = 'Bacon’s MySpace page';

Creating a Blazor component

My current thinking with Blazor is, create components for everything! I would much rather create a component that encapsulates all the functionality and behaviours (where possible), than say adding code to a shared layout or individual pages, which is what I could have done here.

The approach I have taken is to create a <document> component which sets the title on load and after navigation has occurred. This covers both internal and external navigation. Let’s have a look at the code.

The first thing to do is create a function that the JavaScript interop will call to set the title.

site.js

var JsFunctions = window.JsFunctions || {};

JsFunctions = {
    setDocumentTitle: function (title) {
        document.title = title;
    }
};

The function accepts a value which will be set as the title of the document.

Now for the <Document> component.

Document.razor.cs

public class Document : ComponentBase, IDisposable
{
    [Inject]
    protected IJSRuntime JsRuntime { get; set; }

    [Inject]
    protected NavigationManager NavigationManager { get; set; }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
            NavigationManager.LocationChanged -= LocationChanged;
    }

    protected override void OnInitialized()
    {
        NavigationManager.LocationChanged += LocationChanged;
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if(!firstRender) return;

        await SetTitle(new Uri(NavigationManager.Uri));
    }

    private async Task SetTitle(Uri uri)
    {
        var pageName = uri.Segments.Last();
        await JsRuntime.InvokeVoidAsync(JsFunctions.SetDocumentTitle, PageTitleGenerator.Create(pageName));
    }

    private async void LocationChanged(object sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
    {
        await SetTitle(new Uri(e.Location));
    }
}

To set the document title when a page is loaded from the server, SetTitle is called after the page has rendered in OnAfterRenderAsync. Blazor server-side by default pre-renders a HTML file on the server before sending to the client. Therefore OnAfterRenderAsync is the earliest opportunity to interop with the browser. Any earlier in the life cycle will result in an exception being thrown.

SetTitle uses the injected JSRuntime to call our JavaScript function with a title created by the PageTitleGenerator, a simple class creating a title based on the last segment of the current URL (code shown later on).

Page load is catered for but for Blazor navigation, where the page is not loaded but modified with new content, the LocationChanged event on the NavigationManager is used.

By handling the LocationChanged event the title can be set whenever navigation has occurred. LocationChangedEventArgs contains the property Location which is a string representation of the current Uri. This is passed to SetTitle and the title is created and set.

Finally PageTitleGenerator creates a simple title for each page based on the last segment of the URL. The generator would produce the following titles for the given URLs.

Url:    https://www.iambacon.co.uk/blog/tag/blazor
Title:  Blazor | iambacon

and

Url:    https://www.iambacon.co.uk/blog/category/visual-studio
Title:  Visual Studio | iambacon

This can be adapted to suit your requirements.

PageTitleGenerator.cs

public class PageTitleGenerator
{
    private const string _domain = "iambacon";

    public static string Create(string title)
    {
        if (string.IsNullOrWhiteSpace(title))
            throw new ArgumentException("Value cannot be null or whitespace.", nameof(title));

        string pageTitle;
        var textInfo = CultureInfo.CurrentCulture.TextInfo;

        title = title.Replace('-', ' ');

        switch (title)
        {
            case "/":
                pageTitle = _domain;
                break;
            default:
                pageTitle = $"{textInfo.ToTitleCase(title)} | {_domain}";
                break;
        }

        return pageTitle;
    }
}

All that is left to do is include the <Document> component in the layout.

MainLayout.razor

@inherits Microsoft.AspNetCore.Components.LayoutComponentBase

<div class="main">
    @Body
</div>

<Document/>

Summary

This is an example of one approach to setting the document (page) title in a Blazor app. There are several ways this could be done, but I like the fact that it is encapsulated into a component which can be re-used on different layout files or in different projects. The PageTitleGenerator is a simple example of creating titles based on the URL but could easily expanded to fit your requirements.

Icon made by Prosymbols from www.flaticon.com