Check out a free preview of the full Vanilla JS: You Might Not Need a Framework course:
The "SPA Router from Scratch" Lesson is part of the full, Vanilla JS: You Might Not Need a Framework course featured in this preview video. Here's what you'd learn in this lesson:

Maximiliano walks through creating a router, managing which navigations get stored in History, and handling deep linking. The created router will be specific to the course application but can be made reusable by adding abstractions.

Get Unlimited Access Now

Transcript from the "SPA Router from Scratch" Lesson

[00:00:00]
>> Okay, so let's code this part. So we are going to create our router, so we are going to create another service, we can call that router.js I'm going to start with an object pretty simple, with two functions init. You understand why we need an init and go, that will ask the router to go to a new URL.

[00:00:35] These will receive a route, an optional add to history, that we can say that by default it's true. Why is this? Because maybe we wanna change the page, but we don't wanna change the history in the browser. So we don't want the user to go back, one example, a login form.

[00:00:57] So you're in a login form in a single-page application. So you log in, username, password, you log in but what happens if I press the back button. Should I go back to the login form? No, so in that case we say false to the second argument, so then there will be no back button available, maybe you have a logout button but it's not just going back in history, okay?

[00:01:25] And then I'm going to export, default, this router, and then I'm gonna inject this router as a global object in our app.js. So, similar to store, we are going to say that we have a router coming from that router that we are importing from the other module. It's empty so far, okay, it's okay.

[00:01:55] To see if this is doing something, something that they can do is console.log going to into and the route. We can call this path if you like as well, it's just the same. Okay. So, next step, we have these two links. When you click on those links if you pay attention to the browser, mostly the refresh button and the URL bar, it's reloading.

[00:02:28] You can see the refresh button is changing with an X. So it's actually making a new navigation to the server. So we don't want that, we don't want links or links that we want to manage to go to the server, okay, we don't want that, okay? So what we need to do is to add hangs those links, With JavaScript, with the DOM API.

[00:02:55] We wanna trap the click on those links, the activation of those links. And we wanna tell the browser, hey browser, don't go to the URL, I will do that, okay, that's the next step, I wanna do that. I wanna take those links and tell the browser not to use those links as usual, okay?

[00:03:20] So for that, we will do that in init, within the init, if we go to index.html, we can see that those links are currently classified as navlinks, that's a class, it's a classification. So we can use the DOM API to get elements by class name or using query selector and get those links and see if we can enhance them.

[00:03:53] We can change its default behavior. Make sense? So how? Well, we can take, for example document querySelectorAll, because we want many links, not just one, and we will take all the links with the class nav link. And because I'm using querySelectorAll, it's a static node list. We can use modern techniques such as foreach and I can say foreach link for each anchor we can call it A or link that's actually each iteration.

[00:04:35] We are looping through all the links. We are going to add an event listener for that link for the click, Event Handler. Again, if you feel is this is too verbose, you can trick the language quickly to change the syntax of this. So far, what happens here is that now we are within this event handler, we are in the click of each of those links, okay?

[00:05:09] So the first thing that we want is to stop the browser for making a new navigation. So every time you wanna do that with vanilla.js, you need to talk to the argument that you receive, the event has a prevent default method. And you can apply this on every DOM event, not every event has something to prevent.

[00:05:37] But when you wanna prevent the default behavior of the browser, you call prevent default. So if I do that, for example, and then I do console.log link clicked. We are not calling the router yet, so it's not going to work. So we're not calling init. Where do you feel it's a good place to call init?

[00:06:03] DomContentLoader, so now that we have a router in the app object, we can call init. In this case it's going to initialize the router, and the router will, for example, go over all the links and enhance those links. Now that we have that, if I click on the links, I'm not seeing that refresh behavior and you can see in console.log, it says link clicked.

[00:06:39] But more importantly, the page is not being refreshed as before, because we are preventing the default operation, okay? So what's the next step? Well we need to call router go so he can go to the route. So how do I know where I am right now? Well. Let's look at the html, the html has an h ref property, that has the target of the link, that now we are preventing, so we are not going to that URL but we can read that property.

[00:07:23] Okay, so we're going to read that property. There are many ways to read the property. So let me show you a different way to read the property. Just remember we are in an event listener for the link, for the anchor element for the a element. One way to get the URL.

[00:07:43] Is to take a.href, that's one way but I will show you a different ways. Another way is to use a.getAttribute. There is a DOM API, where you can get attributes by their name. Is it the same, should't use the same variable name, is it the same? Kind of, kind of, this is the property and this is the attribute.

[00:08:16] I'm not sure if you remember, earlier that I mentioned that it's not exactly the same but for this case, it's the same. Also, instead of using, by the way, can I use a as a variable within the event handler that is a different function? So a, it's a local variable to this function, function one, and now I am in function two.

[00:08:43] Can I use local variable from the outer function? Yes, that's a closure, okay, that's known as a closure, anyway I'm not getting into those details but instead of using a a closure. Instead of using a i can also use event.target. Before you ask, I know there is another one known as current target and they will explain that later.

[00:09:14] So, in this case, is the target of the event, so this case the click happen over one element, that's the target and I subscribed the event over that element. So event.target is the subject of my event. In this case for this particular case, every option will work, okay?

[00:09:43] Makes sense? So I'm getting in this case the hrefAttribute, for what? For getting the URL so then we can call go over that URL. Just for that, we are upgrading the links, the anchor elements. So when you click on a link, I'm reading its href and passing that value into my router go function, that later, it will do something.

[00:10:16] Make sense? So the only thing different that I should see now is that Reloading, when I'm clicking on those icons, I'm seeing going to/ going to order. So it's actually getting here, getting into go. Again, I'm not creating a router that is abstract, that will serve for every web app.

[00:10:46] I'm creating one from scratch specifically for these web app, later that's an idea you can create a more abstract router that is reusable. Okay, but I'm trying to teach you how that works step by step, so then you can add more abstraction layers on top of it. So one more thing in init that we should do is to when we are initializing the app is to check the initial URL.

[00:11:14] Why is that? Because maybe the user is loading the app directly in /cart. So maybe we don't wanna start always with the homescreen. Remember that we are a single page application now. Even if it's the same index.html, maybe the user is directly because the user has just copied and pasted the URL or someone send you a text with a URL, and it's a deep link.

[00:11:42] To do deep linking into a single page application means that we should start here, checking the current path name of the location, that's the current URL and asking the router to go to that URL. So based on the current URL, it will load the homepage, the order menu or other sections, okay?

[00:12:04] Make sense? Again, this is not still doing anything in the DOM and that's the next step. So the next step is to actually do something where you go. So we are going to write a lot of code here now. And again, the code that I'm going to write is actually not reusable for other apps, because I wanna go step by step and then you can add abstraction layers.

[00:12:30] You can create your own mini framework. So then you can create a router that is multi-solution, multi-app, or you can use a library. There are a lot of mini libraries that are solving this with just maybe 1K of JavaScript. Instead of using like a big framework, you can just take a library just for this part.

[00:12:53] So one of the things that we will check is that we wanna add to history. So that's the second argument. If we wanna add to history, we need to add to history and for that, we're going to use push state. And you can see here that I wasn't lying, that the second argument is called unused, okay?

[00:13:15] So anyway you can pass an empty string or no or anything we work. And the first one is data, that says any, by the way, that's kind of a TypeScript annotation, okay? Any means, anything. So we can pass, for example, an object that will include the route inside, and later we can add more metadata, for example, we can pass on a scroll position.

[00:13:43] So then when we go back, we know where the scroll, or we can make a lot of interesting things here but right now we can pass just the route. I'm passing this as an object, so, it's more future-proof. I mean, I can pass just the route directly, I don't need an object wrapping the route, but yeah maybe in the future we will add more things.

[00:14:04] An empty string, or null for unused and then the URL that is in this case the route.