How to implement basic angular-like routing in plain javascript

I want to write a simple javascript implementation of Angular-like routing, but I can't figure out how to do it. Let's say I have an Index page and I'd like to navigate to Index/2 by a means of <a href="Index/2">Navigate Like in Angular</a> , yet prevent the request being sent to the server and instead get some event which I can handle. I tried using the beforeunload event like this

<script>
        window.onbeforeunload = function (ev) {
 
            ev.preventDefault();
            return '';
        };
 
    </script>

but this approach doesn't work. First, it displays a popup message which I'm not interested in, and second, if I press cancel, the url in the browser address control is still "http://.../Index" and I don't know a way to "silently" modify it to "http://.../Index/2". Any help will be most appreciated.

--- UPDATE ---

In case someone needs, here is a minimal implementation of Angular-like (SPA) routing, thanks to the answer from Quentin.

Index.html, hosted in static website:

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <button title="navigate without request to server" routing-link="/Index/2">Navigate 2</button>
    <button title="navigate without request to server" routing-link="/Index/3">Navigate 3</button>

    <div id="outlet"></div>
    
    <script>

        function updateRoute(){ 
            
            relativeUrl = location.href.substring(location.origin.length + 1, location.href.length);
            document.getElementById("outlet").innerHTML = relativeUrl + ", " + history.length;
        }

        window.addEventListener("click", ev => {

            let routingLink = ev.target.attributes && ev.target.attributes['routing-link'];

            if (routingLink && relativeUrl != routingLink.value) {
                
                history.pushState({ relativeUrl: routingLink.value }, "", location.origin + '/' + routingLink.value);
                updateRoute();
            }
        });

        window.addEventListener("popstate", ev => {

            updateRoute()
        });

        
        let relativeUrl = "";
        
        updateRoute();
                
    </script>

</body>
</html>

If you use, like me, IIS for hosting, don't forget to install the "URL Rewrite" module, and copy the following web.config to site's root:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
  <rewrite>
    <rules>
      <rule name="Angular Routes" stopProcessing="true">
        <match url=".*" />
        <conditions logicalGrouping="MatchAll">
          <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
          <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
        </conditions>
        <action type="Rewrite" url="./Index.html" />
      </rule>
    </rules>
  </rewrite>
  </system.webServer>
</configuration>

This will take care of the cases in which a request is still sent to the server, e.g. due to browser refresh.


Solution 1:

You can't intercept the browser leaving the page to achieve this.

You need to have a click event listener that captures the link being activated instead.

Then you can prevent the default behaviour, modify the DOM however you like, and then manipulate the URL using the History API.

You also need to handle popState events so that the back button continues to work.


Keep in mind that you do need to have the server deliver something useful when the browser asks for that new URL as an entry point to your SPA and that that is best provided by having a server-side rendered version of your application.

A hackier approach is to just deliver some skeleton HTML and bootstrap everything with client-side JS … but then you run into problems with determining when the server should report a 404 error.