Creating Anchor Links in Blazor Applications

Published by Mika Berglund on

Anchor

A while ago, I wrote about Blazor Bootstrap, which is a Razor component library for Blazor applications that want to use Bootstrap as UI framework.

The other day, I was working on the Heading component, and wanted to make it possible to turn every Heading into an anchor link. This would allow you to easily link directly to each heading on the page. Here is quite a descriptive explanation of what an anchor link is.

An anchor link is a web link that allows users to leapfrog to a specific point on a website page. It saves them the need to scroll and skim read – and makes navigation easier.

Doesn’t sound too hard, does it? Just add an <a> element to the heading, and specify a unique ID as href, as shown below.

<h2 id="the-heading"><a href="#the-heading">The Heading</a></h2>

So it turns out that it unfortunately is not quite that easy. To make it easier for you to follow, I created a repository on GitHub with the solution to the problems described below. The sample application in the repository is also published on GitHub Pages at https://mikaberglund.github.io/anchor-link-in-blazor-application/

Problem

The problem with anchor links is that when you click on them, the page won’t scroll to the element you’ve specified in the link. Anchor links will always take you to the top of the front page of your application. This has to do with how routing is handled in Blazor and most other SPA applications as well. It’s definitely not just a problem with Blazor.

Luckily there is quite a simple solution. You just need to create your own AnchorLink component (or name it however you like) and use a little bit of JavaScript interop magic.

The AnchorLink Component

The AnchorLink component is built up of the AnchorLink.razor and the anchorLink.js files. The main responsibility of the Razor component is to examine the value of the href attribute and determine whether it is an anchor link. Anchor links starts with a hash (#). If it is an anchor link, then the default click action is prevented, and the scrollIntoView JavaScript function is called when the anchor is clicked using the injected JavaScript interop runtime.

The scrollIntoView JavaScript function supports the Razor component by scrolling the given element into view when the function is called.

Using the AnchorLink Component

When you have completed the AnchorLink component, you can use it in your application as a replacement for the default <a> element. Remember also to add a reference to the anchorLink.js file to the index.html template, so that the browser loads that file too.

You can use any standard attributes on the AnchorLink component as you would on an <a> element. Below are a few samples.

<p>
    The Table of Contents below demonstrate how
    you can use the <code>AnchorLink</code> component.
    You can also use it as a <AnchorLink href="...">normal link</AnchorLink>
    to point to any URI.
</p>
<ul>
    <li><AnchorLink href="#chapter1">Chapter 1</AnchorLink></li>
    <li><AnchorLink href="#chapter2">Chapter 2</AnchorLink></li>
    <li><AnchorLink href="#chapter3">Chapter 3</AnchorLink></li>
</ul>
<h2 id="chapter1">Chapter #1</h2>
<p>
    This is chapter #1.
</p>
<h2 id="chapter2">Chapter #2</h2>
<p>
    This is chapter #2.
</p>
<h2 id="chapter3">Chapter #3</h2>
<p>
    This is chapter #3. The source code to the <code>AnchorLink</code>
    component is available on <AnchorLink href="https://github.com/MikaBerglund/anchor-link-in-blazor-application" target="_blank">GitHub</AnchorLink>.
</p>

Conclusion

If you want to learn more about building components for your Blazor applications, I suggest you have a look at the Create and use AS.NET Core Razor components article. That will give you a very good overview of what’s possible.

And thanks to Chris Sainty, who pointed out some errors in this article, which I’ve now fixed.


19 Comments

Chris Sainty · December 28, 2019 at 15:33

Hi Mika,

I’ve just read your post but I have a concern. Part of your solution is to remove the base tag, this isn’t a good idea. The base tag is essential to routing in Blazor when the app doesn’t sit at the root of the site.

Given your example above, if you want to create an anchor link to #chapter-1 on the introduction page. If you construct your link like this: href=”/introduction/#chapter-1″ then use your custom component as you explain, then there is no need to remove the base tag and the solution should work in all scenarios.

Cheers,
Chris

    Mika Berglund · December 28, 2019 at 18:52

    Thanks for you comment, Chris!

    I know that you can build your anchor links by prefixing them with the page. However, ever since anchor links were invented, links targeting the same page have always been constructed with just href=”#chapter-1″, not “href=”[current page]#chapter-1″. That’s why I was going for a solution that would not require me to change the way I build anchor links, but create them in a way they have always been created.

    If you are not running your application from the root of the site, then you probably would need to specify folder name your application is running from as root, and not the default base=”/” anyway, right?

      Chris Sainty · December 28, 2019 at 21:06

      “However, ever since anchor links were invented, links targeting the same page have always been constructed with just href=”#chapter-1″, not “href=”[current page]#chapter-1″.”

      That’s true for traditional mult-page web applications but I wouldn’t agree for SPAs. Most SPA frameworks seem to have some quirk to deal with anchor links, but we could lose hours talking about all that! 🙂

      “If you are not running your application from the root of the site, then you probably would need to specify folder name your application is running from as root, and not the default base=”/” anyway, right?”

      That’s correct if your app was running from mydomain.com/blazor then you would need to set the base tags href to “/blazor/”. Essentially, Blazor’s routing system uses the base tags href value to know what links it should and should not intercept.

        Mika Berglund · December 29, 2019 at 10:02

        It’s not hours lost, it’s hours spent on learning about others’ viewpoints 😉

        Anyway, I will update the article to reflect this discussion. I’m currently working on publishing the demo application using GitHub Pages for the repository. There, the application will be running in a non-root folder, so I’ll need to put in the base href.

    Mika Berglund · December 28, 2019 at 18:59

    But you are right Chris. With the custom AnchorLink component, we don’t need to remove the base tag, since our custom component will prevent the default action anyway.

    Thank you for pointing this out. I’ll update the article to reflect this.

Troy Gerton · June 15, 2020 at 17:48

This is exactly what I was looking for. Thank you, Mika. I had to modify it to work inside a bootstrap accordion because the onclick event triggered the collapse/expand. This was solved by adding a parameter to AnchorLink.razor and implementing two-way binding on it. Works great! Following are snippets on how I handled it…
AnchorLink.razor:
[Parameter]
public bool Collapsed { get; set; }

[Parameter]
public EventCallback CollapsedChanged { get; set; }

private async Task AnchorOnClickAsync()
{
if (!string.IsNullOrEmpty(this.targetId))
{
Collapsed = !Collapsed;
await CollapsedChanged.InvokeAsync(Collapsed);
// If the target ID has been specified, we know this is an anchor link that we need to scroll
// to, so we call the JavaScript method to take care of this for us.
await this.JsInterop.InvokeVoidAsync(“anchorLink.scrollIntoView”, this.targetId);
}
}

And my anchor usage changed from this…
this.collapsed = !this.collapsed”>Reports

to this…

Good stuff. Thanks again…troy

    Mika Berglund · June 17, 2020 at 07:12

    Great that you got it sorted.

Troy Gerton · June 15, 2020 at 17:52

Oops. This blog didn’t like my markup.

<!– this.collapsed = !this.collapsed”>Reports–>

to this…

<!—->

Troy Gerton · June 15, 2020 at 17:53

Thanks for the solution. Exactly what I needed.

Lukas · June 16, 2020 at 00:48

I have a problem. When I add these lines in my code
Start
Gallery
I see the inscription on my website “AnchorLink” above my list item.
When i go to ctrl+Shift+I at Google Chrome and i choose this inscription i can see:
AnchorLink
Start
On your page i can see only Chapter 1
Could you tell me why?

    Mika Berglund · June 17, 2020 at 07:11

    It’s pretty hard to say without code to look at.

Mike · September 26, 2022 at 02:44

Nice! Thank you! I threw an HTML comment in my code:

Mike · September 26, 2022 at 02:46

Lol, oops, of course you strip HTML even if it is a comment.
Adapted from and courtesy of Mika Berglund https://mikaberglund.com/creating-anchor-links-in-blazor-applications/ –>

Milan Malbasic · November 20, 2022 at 22:04

Hi Mike,

I am sorry if my english is not the best – (in German would be better) but I ‘ll try to explain what my problem is.

Somehow I can’t get that working. I always have a normal string displayed.
Means: there is no clickable link.
I copied the content of the anchorLink.js and put it in root folder, then I added in _Host.cshtml the string:

Then copied the content of the AnchoLink.razor in my razor file, then I created a razor page and called it, lets say “links.razor” in the Shared folder and added the content from your example there.
the content of my “links.razor”
page:
@using msgsolutions.Pages
@using System.Web
@using msgsolutions.Shared

@page “/links”

Links

Counter

Current count: @currentCount

Click me

The Table of Contents below demonstrate how
you can use the AnchorLink component.
You can also use it as a normal link
to point to any URI.

Chapter 1
Chapter 2
Chapter 3

Chapter #1

This is chapter #1.

Chapter #2

This is chapter #2.

Chapter #3

This is chapter #3. The source code to the AnchorLink
component is available on GitHub.

When I start the app (VS2022, ServerApp) I have that all displayed but can’t click on any entry there, looks like no function when I click on any of the “Chapter”, Nothing happens.

Thats what I have:
The Table of Contents below demonstrate how you can use the AnchorLink component. You can also use it as a normal link to point to any URI.

Chapter 1
Chapter 2
Chapter 3
Chapter #1
This is chapter #1.

Chapter #2
This is chapter #2.

Chapter #3
This is chapter #3. The source code to the AnchorLink component is available on GitHub.

But if I click on any of those “Chapter” they dont seem to be clickable, at least nothing happens when I click on any of those.

In the _Imports.razor file I added that: @using Microsoft.JSInterop
in the js folder in wwwroot I added the anchorLink.js, the content of this *js is copied from your *.js.
The anchorLink.razor is in the Shared folder and the content of this razor file is copied from your file.

Do you maybe know what could be the reason / what I am missing there?
Any help would be appreciated as I really need anchor function in my razor ServerApp.
thx
best
Milan

    Mika Berglund · December 8, 2022 at 14:57

    Hi Milan!

    You don’t happen to have the source code to your web application available somewhere on Github or so? Would be easier to check the entire project in my dev environment.

Admir · March 17, 2023 at 00:29

Thanks on efort,
Is tehere a way we can uese this from code behind, so we can navigate to certain link.
An idea is on large forms wehen there is error on submit to scroll user view to section with error.

    Mika Berglund · June 20, 2023 at 07:27

    I guess there should be some way how you could click a link from your code in a Blazor application. According to Chat GPT you can do that using JavaScript interop, but it seems a bit too complicated, so I’ll have to try and look more into that.

Ricardo Sousa · August 12, 2023 at 01:22

Hi Mika
I have the same problem and msft postponed correction/facilitate this very raw html operation for after net8.
I’m not able to replicate on a newest net8 Web app your solution – does it only works for WebAssembly? Any catch to be aware?
Thanks
Ricardo

    Mika Berglund · August 23, 2023 at 10:31

    It’s been a few years since I wrote the article, so I cannot with full confidence say how it works on newer Blazor applications. However, at the time of writing, it worked with both Blazor WASM and Blazor Server.

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *