Getting Started with the Vue.js CLI
Hello. This is Jim Cooper, and welcome to this Vue.js Fundamentals module on getting started with the Vue.js CLI. In this module, we'll spend a few minutes gaining a high-level understanding of Vue.js applications, and then we'll dive into creating our first project with the Vue CLI. We'll even start writing some of our own code in this module, including building this home page for the Build-a-Bot application that we'll build throughout this course. We're going to have some fun in this course using Vue to create a simple website for building and buying custom robots. This application will be rich enough to demonstrate all the fundamental concepts you need in order to create your own applications with Vue, but it will also be simple enough that we won't waste our time building unnecessary features that have no educational value. And this is what it'll look like when you're finished. As you can see, I can come to the build page and start customizing my robot. And I can build the robot that I want and then click Add to Cart to add it to my cart. And then you can see, it shows up in my cart here, and I even show which robots I saved money on, if they happen to be on sale. And back on the Build page, I can click on any of these parts and learn more about the individual parts. This site doesn't seem like much, but there's a ton of concepts and learning built into this. It will be just enough to allow us to dive into things like creating components and child components, communicating between those components, routing and navigation, state management, and even API calls to a separate server. We'll even explore how to build and deploy this application to production. So let's jump right in and start learning about Vue.
Gaining a Conceptual Understanding of Vue
At the heart of a Vue.js application, you find the Vue instance. A Vue instance is created whenever you call new Vue. In a typical Vue application, you will only ever have one Vue instance. When you create a Vue instance, you will usually tell it to render a component, which you do by passing in an options object with a render function, which tells it to render an App component. This App component is the top-level component in what will become a hierarchical component structure. So you can imagine your Vue instance wrapping your entire application, and inside that, you have your App component. And the App component may have a child component, which then may have other child components, etc. This should feel familiar if you've written any Angular or React applications. The component structure is similar, other than the Vue instance. In a Vue application that includes routing, which is probably most Vue applications, there's another piece. Basically, instead of the App component containing other components, or maybe in addition to it containing other components, it will also contain a router-view, which shows the content for the current URL. And as the URL changes, the content inside the router-view is updated. And while there is a lot more to a Vue application, this is a pretty good conceptual overview of the main structure of a Vue app. To understand the rest of the details, let's jump into creating an app from the ground up.
Setting up the Environment
We're going to need a few tools installed to get going. First, we'll need Git installed. I'm on Windows, so I'm going to use Git for Windows, which can be downloaded from here. If you're on a Mac, you can install SCM Git from here. And if you're on Linux, you can just run a command like this one from the terminal. I'll go ahead and download and install Git for Windows, and then I'll run that, and I'll just use the defaults throughout the install. And now that that's installed, I have this new Git Bash console that I can run, so we'll use that in a minute. And actually, while I prefer to use this Bash console, sometimes Windows users have issues with it, so if you're getting weird errors, try using the Windows console window instead. The next thing I need is Node. I prefer to install Node using Node Version Manager, just because it makes it easier to switch between versions of Node as I develop different applications. On Windows, I can download that from here. For Linux and Mac, you can just run this command from the terminal. So I'm going to go ahead and install NVM for Windows. And I'll just use all the default options here too. Okay, now that that's installed, I'm going to open my Git Bash console. On Windows, be sure to open it as an administrator. Okay, now that I have NVM installed, I can just run nvm install, and then the version of Node that you want to install. It's recommended that you use this version for this course, if you're following along, to avoid any compatibility issues. And now I can just tell NVM to use this version of Node. Okay, we're good to go with Git and Node now. If you have problems using NVM to install Node, you can try installing it directly from here. Just choose the correct installer for your system. Okay, let's get coding.
Creating a New Project with the Vue CLI
Creating the Home Page
Alright, in this clip we're going to start creating some of our own content. We won't be exploring any new concepts just yet. We're just going to spend a little bit of time replacing this generated content with some of our own content. This will get us a little more familiar with what the CLI generated and with the layout of our new app before we start digging into key Vue.js concepts. So let's start by renaming this HelloWorld component. This will become our new home page, so let's name it HomePage.vue. We could just name it Home.vue, but one of the Vue style guide rules says that components should be two words at a minimum. So we'll name it HomePage. And let's update the component name down here too. And then we'll have to update the import for this in the App component, so instead of importing HelloWorld, we'll import HomePage from HomePage.vue, and then we'll use that down here. And we'll change it up here in the template. And we don't need this msg attribute anymore, so we'll delete that. And I'd like to put all of this inside a main element. And we don't want this Vue logo up here anymore, so we'll delete that. All right, and then let's add a couple of styles and clean up some styles that we don't need. So down here in the style section of our App component, I'd like to add a gradient to the body of our application. So I'm going to give it a background, I'm going to set linear-gradient to a gradient from top to bottom, and let's fix that spelling error, and then I want to set the background-attachment to fixed. And then down here on the app element, I'm just going to remove everything except our font family. And by the way, this app element is just referring to this div up here that has an id of app. And then I just wanted to add a few styles for this main div, so down here I'll add a main style. And I want to set margin to 0 auto, so this will center the main content, and we'll add some padding and give it a background-color of white and a width of 1024px and then just a min-height of 300px. Okay, we'll talk more about components and styling them in the next module. We're just trying to get a decent starting point set up here, and then we'll delve into this stuff more. So let's go see what our site looks like now. Okay, ignore the main content area, but notice that our background has a gray gradient now, and our main content area has a white background. Now let's go replace all of this content with our HomePage content. So back in our HomePage component, up here in the template, all right, so let's delete all of this content inside the outer div, and we'll change this class to home. And then I'm just going to go ahead and paste a little bit of content here. If you're following along, go ahead and paste this in. And then notice that we just have a couple of divs here. One just has an image of a robot, and the other has a get-started link. So notice that this image is loading robot-home.png file out of the assets folder, but that file doesn't exist in here. This is just the Vue logo that came with our Vue CLI sample page. So let's go download that robot-home.png file. To help us with this and a number of other tasks throughout this course, I've created a little helper repo over here in GitHub for the Vue.js Fundamentals course. And there's just a few files in here that we will occasionally use to download things as we go throughout this course. So in the src folder, notice we have an assets folder, and here is the robot-home.png file. And we'll actually need this other image, too, so we might as well download them both. So first I'll click on robot-home, and then click on Download, and then Save image as, and then I just need to go find my build-a-bot folder. And in the assets folder, we'll just save it here. And then we can go back and do the same thing for this build-a-bot logo. So we'll save that here too. Okay, cool. So now that robot-home image exists in our assets folder. Now we just need to clean up some styles down here. And we don't need any of these, so I'm just going to add a few classes here. So we have a home class, and I just want that to be text-align: center, and then a robot class, and I just want to set the height to 300px. This is for that robot image. So let's go see what our site looks like now. Okay, cool. This is looking great now. And you may have noticed that I didn't need to hit refresh on this page in order to see my changes. That's because we are using, behind the scenes Vue CLI is using the webpack DevServer, including Hot Module Replacement. So as we make changes to our code, the changes immediately show up in our browser without us having to refresh. Okay, awesome. So now we've created a new home page. But let's add one more thing that we're going to use throughout this course. Our site is missing a navigation header, so let's add that quick. And we could add that to our home component, but then when we navigate away from our home page, we would lose that navigation header. So really we want it to be on our App component. So up here just above our main element, we're going to add a header element. And that's going to have a nav element, and this nav element is nothing special. It's not Vue specific; it's just a normal HTML element that comes with HTML 5. And then inside here, we're going to use an unordered list to list out each of the nav elements. And for now, that's just going to have one list element, and that will have a nav-item class. And then inside here we're going to have an image, which will be our logo, so we'll give that logo class. And this is going to be the other image that we downloaded, so in our assets folder, it is the build-a-bot-logo.png file. And then we'll just say Build-a-Bot here. And now we just need to add some styles to style that. Again, we'll cover styling in more depth in the next module. But rather than have you type in all the styles, let's go over to our helper repo, and in the src folder, there is this header.css. And we're just going to click on the raw version of that and copy all of it, and then let's just paste that all in right here. Okay, so you can see we're just adding some header and nav item styling here and some styling for our logo. Okay, let's go take a look at that. All right, awesome. So you can see here, again, without having to refresh, Hot Module Refreshment just updated our page for us, and we now have a navigation bar at the top with a list item here on the left for navigating to our home page. And that's not a link yet or anything. We'll update that later. But now we have a home page with a navigation bar in our App component. And I realize that this clip was just a bit of just following along, not a lot of new concepts. But hopefully as we moved around in the app and replaced some HTML in our home page component and our App component, you began to become a little bit more familiar with the layout of our application, and hopefully that will help a bit going forward. In the next module, we're going to dive into components and templates in text and start learning a lot more concepts. So in this module, we just learned some high-level things about Vue, and how to use the Vue CLI to create a project, and a little bit about working within components and making some HTML and CSS changes. In the next module, we're going to take a much deeper look at components and templates in text, so let's go jump into that.
Creating Vue.js Components and Using Template Syntax
In this module, we're going to explore all the fundamentals of creating Vue.js components and working with component syntax. This includes creating components, using bindings to display data and handle events, conditionally displaying elements with v-if and v-show, showing elements with v-for, styling components, working with component livecycle hooks, and reducing duplication with mixins. There's a lot here, so let's get started.
Global Components vs. Single-file Components
Creating Your First Component
All right, now the real fun begins. Let's start creating our own components. We already have a HomePage component; now we want to create a page where we can build our custom robots. A new page means a new Vue components, and I could just put that new component right here in the components directory, but I don't think that scales very well as applications get larger. I prefer to structure my apps by feature areas, so let's make that change now. We'll start by creating a new home folder, and then we'll move the HomePage component into that folder. And now I can delete this empty components folder. All right, now we just need to update the reference to our HomePage component over here in our app component. It's now in the home folder. And that should be rendering fine, so let's go take a look at that. Okay, yep, that's working just fine. Now let's create our new component. So this is going to be a robot builder component, so let's create a new area for building. We'll call that the build folder. And then in here we'll create a new single-file component called RobotBuilder. Remember, component names should be two words. Okay, so let's create our three sections in here, so we'll have a template section and a script section and a style section. All right, our template's complaining just because we don't have a root element in here yet. We'll fix that in just a minute. First, we'll go ahead and create out component element and export it. So we're just going to export an object, and we'll give it a name, so this is our RobotBuilder. And notice that ESLint ran when I saved it, and it just removed some extraneous spaces at the beginning. So ESLint will run every time I save. Okay, now for the HTML and CSS. I don't want to bore you with typing in a bunch of HTML that isn't really Vue specific, so let's copy this from our GitHub repo. So back over here in the this src folder in the build folder, you can see I've created an html and css file for this component, so let's click on the html file, and then we'll click on Raw and copy this, and then we'll paste that back into our template here. Okay, so you can see that we just have a bunch of HTML in here. There's nothing specific about Vue here, but you can see that we have a top section, a middle row section, and a bottom row section. And you can see the part selectors here. They basically are comprised of an image and buttons for left and right or up and down for changing the selected part. Just as a reminder of what we're actually building here, it's this Build page here. So this is our top section, our middle section, and our bottom section, and then here are the part selectors, what I'm calling part selectors, which consist of an image and buttons to select the individual parts. And so that's what all this HTML is here. And these buttons are not wired up to do anything yet, but you can see I have a lot of classes here, so let's go grab the styles for this file also. So back over here in our repository, we'll go back and back again, and I'm going to click on this RobotBuilder.css file and grab the content out of here. And then we're going to go ahead and paste that into the style section down here. So this is just all the styles for positioning each of those part selectors and the buttons and everything. And again, there's nothing really Vue specific here with regards to what I pasted in. This is all just CSS styling. The only thing that's Vue specific here is that I put the CSS inside this style tag in our single-file component. Okay, and then you can see these image tags are pointing to images that we haven't downloaded yet, so let's go grab those. And back over in our GitHub repo, you can see inside the build folder, there's an images folder, and there's a lot of images here. I could just click on each one of these and download them one at a time, but it will be easier to just clone this repo and then copy them locally. So I'm going to go back over to my terminal and stop my server, and then I'm going to move back a directory. And then I'm going to git clone this repo here. Okay, let's go ahead and clone that. Okay, now that that's there, I can go back into my build-a-bot directory, and then we'll change into the src/build directory, and we'll make a images directory here. And we'll move into that directory. Okay, now I can just copy those images from the cloned helper repo like this. So we'll just grab everything out of the images directory and copy it into this directory. Okay, so now you can see we have all those images in here. All right, now that we have those, let's move back to our application directory, and we'll go ahead and start the server. All right, now let's go take a look at what our new page looks like. Of course, how are we going to get to this page? We don't have routing set up yet, and so how are we going to view this RobotBuilder instead of the home page? Well, for now, we'll just have to import it over here also. (Typing) And then we'll just go ahead and render this here instead of the HomePage component, and we'll comment this out for now. And then we just need to use this up in our template here. Okay, so now this should be showing our RobotBuilder instead of the HomePage, so let's go check that out. Okay, perfect. So you can see that we have our new RobotBuilder component rendering here, but these buttons don't do anything yet. But this is awesome. We have a new component, and it's just got some really basic HTML and CSS, and all we're doing so far with the component is just exporting it and then using it here in our main app component. So let's make this page more interesting next by adding some bindings so that we can start selecting the individual parts.
Binding Attributes to Data with v-bind
In order to make our new Build page a little more dynamic, we need to start by binding it to data from the component instead of these images being hardcoded in the HTML template. In order to provide data to our component, we need to update our component options to have a data function. So down here, we'll add that right here. So this is just going to be a function, and right now we'll just return an empty object. If we look at our Build page, we have all these different parts, heads, arms, torsos, and bases. And eventually, when we click these buttons, the corresponding part will be swapped out for another one. We're not going to wire up these buttons just yet, but let's get the images wired up to a list of parts. First, we need some data. Eventually we'll get the data for the parts from an API, but for now, we'll just create a file with all the data in it. So let's create a data folder inside our src folder, and inside that folder, we'll create a new parts.js file. And this folder is also going to contain our images, so let's move that images folder from our build folder into the data folder. Okay, now we need our parts data, so let's go back to our GitHub repo, let's go into the data folder, and grab this parts.js content, and we'll paste that into here. So this is mostly just JSON data, but you can see up here, this first line, there's some web pack goodness here. So we're using web pack's require.context here to make web pack aware of the image URLs so that we can just use them throughout this JSON data as relative URLs. So down here on line 10, you can see that we're calling images function and passing in a relative URL. When this is compiled by web pack, this will point to the relative URL of this image file. So other than that, this is all just JSON, and you can see that we have this parts variable up here, our constant, and that we're exporting it down here. And just so we understand the shape of our data a little bit, if I collapse each of these sections, you can see that the parts object is an object with properties for heads, arms, torsos, and bases. Each one of those is an array of parts. So this data is perfect for what we need. So let's jump back over to our RobotBuilder, and then let's import that data here. So we will import availableParts from data/parts. So the ESLint plugin for Visual Studio Code is a little bit funny. You might notice that I hit Save multiple times there, and it reformatted according to my linting rules a couple of times. Okay, so now we just want to return these available parts as part of our data object, so we can just return it like this. So now our data object is an object with an availableParts property that is set to that JSON data. Now let's see how we can use this data in our template. At the top of our template here, we have this image that is displaying currently the head image for a robot, and instead of setting the src to a hardcoded string like this, we want to bind it to an expression. We can do that like this. So this says bind the source attribute of this image element to this expression in the quotes. And inside that expression, we can access availableParts. So this is the availableParts object that we exposed in our data function. And then availableParts has a heads property which is an array, and we'll grab the first head and then grab the src property. So the src property, thanks to web pack, and we talked about this just a minute ago, this is the relative URL to the image for this head. Okay, so let's copy this expression down to each of the other parts. So we'll set these to v-bind also. And for now, we'll just bind them all to the heads. Okay, and there's just one more here. Okay, so if we look at this, we now have a robot with all heads, not a very useful robot, so let's just update each one of these to point to the correct part type. So here instead of heads, this is going to be arms, and this is going to be torsos, and this one is also arms, and then this is bases. Now if we refresh, there we go, now we have a functional robot again. And that's all thanks to the v-bind syntax, which is binding the source attribute of the image element to these expressions. Of course, you can bind any attribute of any element using v-bind. So now our image source attributes are all bound, but we can't change them. We still are not changing the image when we click these selector buttons. To do that, we're going to have to bind to the click event. Let's see how to do that next.
Binding to Events with v-on
In order to react to actions by the user such as clicking on these buttons, we need to bind to the events of those elements. That's just as easy as binding to data was. All we need to do is use the v-on syntax. So on this button, I'm going to add a v-on binding to the click event. So now whenever this button is clicked, the expression inside these quotes would be called. So what I want to do here is I want to call a function, so I'm going to call selectNextHead. So this is the next button for the head image, so when the user clicks on this button, we're going to call this selectNextHead function. And that function needs to exist on our component. To do that, we're going to introduce a new property on our component called methods, and that's an object, and then you define each of the functions inside of here that you're going to call from your template. So we need selectNextHead, and for now, let's just console.log selectNextHead called. So let's go see if that works. So over here, let's open our console and then click this button. There you go; you can see each time I click it, this selectNextHead called gets logged. So our v-on click binding is working. But what we really want to do is have it select the next head instead of just console.logging. So we're going to have to track some index and increment and decrement it when the buttons are clicked. To do that, let's add a selectedHeadIndex to our data object, and we'll initialize that initially to 0. And then in our selectNextHead function, we'll increment that like this. And then to make that work up here in our template, instead of hardcoding this to 0, we're going to use our new selectedHeadIndex. So now this will grab the head that matches that index and bind the source property to the URL of that head. Okay, let's go check this out. Cool, so now as I click on this button, you can see that Vue is calling our selectNextHead function, which is incrementing the index, which updates the binding for that image. Okay, let's do the same thing for selectPreviousHead. So we need, on this previous selector, we need a v-on click binding, and that will call selectPreviousHead. So now let's go create that function down here, and that will decrement that index. Now let's go check that out. Close our console here. All right, cool, now I can move back and forth between the heads. Of course, we have a slight problem here. If I open the console, clicking on this next button, you can see that at some point I start getting an error. This is because we are incrementing that index out of range. So basically, we need to make it so that when we get to the end of the heads, then when you click the next button, it resets the index back to 0. So to fix that, I'm going to create a couple of helper functions up here at the top of my script block. Okay, so we have a getPreviousValidIndex function and a getNextValidIndex function. And if you're following along, go ahead and type this in, but you can see it's just going to, when you're clicking getPrevious, it will deprecate it until it gets to 0, and then it will set it to the last head, and then vice-versa with the getNextValidIndex function. And then notice that these functions take in the index to increment and the length of the current parts array. So down here, instead of incrementing this, we're going to call getNextValidIndex, and we will pass in this.selectedHeadIndex, and the length of the heads array. Okay, and then we'll do the same sort of thing for getPreviousHeadIndex. Okay, so that should fix this problem. And then these functions don't mutate the index; they just return a new index, so I need to set the selectedHeadIndex to whatever's returned from these. And our linter is complaining because these lines are too long, so let's just wrap them, format them a little bit better. Okay, let's go check this out. So now as I increment, you can see that it is wrapping around, and we're not getting any errors and same for going to the previous heads. Awesome. So now we know how to bind to the click events. And, of course, we could bind to any event that is fired by any element. So we've updated the head, but we have not yet updated the other parts, so let's go fix those bindings. So if we go back up to our template, I'm basically going to need to copy these bindings down to all the other buttons. So there's the previous binding and then the next binding. And then this will have to call, this is the left arm, so I'm going to call selectPreviousLeftArm and selectNextLeftArm. Okay, I'm going to go ahead and do the same thing for all of the rest of the parts, but I'm going to go ahead and do that off-screen because it's not too interesting to follow along. Okay, so you can see here that I've updated the bindings for selectPreviousTorso and NextTorso, RightArm, and Base. And then notice also that in the v-bind source bindings, I've updated the hardcoded 0 to the selectedBaseIndex, RightArmIndex, and TorsoIndex, and same thing for LeftArmIndex. And then you can see down here that I've initialized them all to 0 here in our data function, and then I've added methods for each one of them, selectNextHead and PreviousHead, LeftArm, Torso, RightArm, and Base. So if you're following along, go ahead and copy and paste all those and update them appropriately. Okay, so let's go check this out. So now we can move to the next and previous head, and left arm, torso, right arm, and base. Awesome. We have a functioning robot builder. In the next clip, we'll take a look at an abbreviated syntax for these v-bind and v-on bindings.
Using Shorthand Bindings
We just created all of these bindings in our RobotBuilder HTML, and I just wanted to take a second to call out a more abbreviated syntax that's really nice. Anywhere we use this v-bind syntax, we can replace it with just a colon, so this is a shorthand syntax for v-bind. And then anywhere we use v-on, we can replace that with an at sign, so no colon here. I really like this syntax because it's very non-obtrusive, but it's also really clear that we're creating bindings. So let's replace all of these with a search and replace. So I'm going to search for v-bind and replace it with just colon, so I'm replacing v-bind: with just a colon. And I'll just replace all of those. And then I'm going to search for v-on: and replace it with just an at sign. Let's replace all of those. And our RobotBuilder should still be working. Let's check it out. There we go. We can still see our image are, bindings are working, and our button bindings are all working. So from here on out in the course, we'll be using this abbreviated binding syntax. Next, let's take a look at how we can create and bind to computed properties.
Binding to Computed Properties
Sometimes you want to be able to easily bind to something in your HTML template that involves a complex calculation. Doing complex calculations in your HTML template isn't really a great idea. We can solve that and move the complexity into the component with computed properties. All of these expressions are a little ugly. What would be nice is if we had a selected robot property on our component that we can use. So if we come down to our component, we can add a computed property here. And like the methods property, this contains methods. So I'm going to add a selectedRobot function, and this will return an object that represents our selected robot. And then that object will have a property for each part like this. Now let's add the other parts. So there's the left arm, and then we'll do the torso, and then the right arm, and finally the bases. So now selectedRobot.head will return the currently selected head and so forth. So now up here, instead of these expressions, we can just replace these with selectedRobot.head.src. So let's copy this down to each one of these. So there's the leftArm, and then here is the torso, and now the rightArm, and finally the base. And then I think I might've had a typo down here. Yeah, I didn't say LeftArmIndex here. Okay, so that should be working great. So if we come over to our robot builder, now we can still use our robot builder, but our code is a lot more simple in our template where we don't have this complex expression here, but here just binding to the head of the selected robot. So this is better, and while this one just helps simplify our template a little bit, there are cases where it can simplify them a lot. You really want to be careful not to do too much logic in your templates, and computed properties make that easy.
Displaying Data with Interpolation
Our robot builder is looking pretty good, and we've learned how to bind to attributes and events, but we haven't yet learned how to simply just display raw data on the page. We do that with interpolation. To demonstrate that, let's display the name of our robot just above the robot head. If we take a look at the parts data over here, you can see that each part has a title. And the names of the heads actually make for a pretty good name for our robots. So let's display the name of the currently selected head above our robot. Because of our computed property, this is going to be really easy. So let's add a new div right here, and we'll give it a class of robot-name. And let's add that class down here in our styles. And I'm going to position this absolute. And I want to position it about 25 pixels above the top of the head, and then we'll align the text to the center, and then we'll set the width to 100%. Okay, now for the interpolation part. Back up here inside our div, we're just going to add an expression like this. So the double brackets is used for interpolation, and inside there, we'll just put an expression. So we just want to use selectedRobot.head.title. And this is it. That's all there is to interpolation, and you can put any valid expression in here. Although, again, it's recommended that you only use simple expressions in your template. Okay, let's go check this out. All right, awesome, you can see here that our robot has a name, and then you can see, as I click through the robot heads, that the robot name up here is changing due to that interpolation expression. So interpolation is really simple. Before we wrap up on interpolation though, let me mention one more performance option. We can add a v-once tag to any element like this, and when you do this, any bindings inside that element will be evaluated once and then never again. This is handy for performance reasons if you have a page with a lot of bindings that are going to render once and then you don't expect the data to change. In our case though, this isn't what we want. If I come over now to our robot builder and cycle through the robot heads, you can see that the binding isn't updated. It updated once when the page was rendered, and then it's not being updated anymore. So we don't want that here, so let's remove it. But it's good to know about for performance reasons. Now that should be updating again. Okay, cool. So next, we're going to take a look at ways to dynamically show and hide our content on our page.
Conditionally Displaying Content with v-if and v-show
Vue supplies two directives that we can use to conditionally show or hide content, v-if and v-show. Let's check them both out and talk about when to use each one. To demonstrate this, let's add an indicator next to this title right here that indicates whether the robot is on sale. If we look at our parts data again, you can see that some of the parts have an onSale attribute. So we'll bind to the onSale property for the heads, and if the head shows that it's onSale, we'll indicate that the robot is on sale. So back in our RobotBuilder, right here next to the interpolation expression that's showing the title, we'll add a span with a class of sale. And then we'll just add the text Sale! inside here. Okay, let's go add that class to our CSS down here. Okay, so if we go look at this right now, it appears that all of the heads are on sale, but that is not accurate. There is actually only one head that's on sale, and it's our Friendly Bot, so let's use v-if to only show this sale span if the head is on sale. So right here I'm going to add a v-if binding, and then the expression inside of the v-if just needs to be a Boolean. If the expression evaluates to true, the element is shown, so we'll just put selectedRobot.head.onSale. Okay, so now if this onSale element exists on the head and it's set to true, then this span element will be displayed. Otherwise, it will not be displayed. So let's check that out. Okay, cool, that's working better. Now only our Friendly Bot appears to be on sale, which is correct. Okay, so that's v-if. Now let's check out v-show. It's actually works identically. So I could change v-if to v-show, and then if we go look at this, it's still working just the same. So you might wonder, well, what's the point? Well, the difference is what's happening behind the scenes. If we take a look at this element, we'll inspect it here, and then let's navigate to one that is not on sale. Okay, you can see that the element is actually still here, but it has an inline style now that says display: none. And so that is how Vue is showing and hiding this element when we're using v-show. It does so by adding style=display: none. However, if we change this to v-if again, and then look at this again, you can see that actually the element isn't even here. It's been removed completely from the DOM. And as I move through this, if I hit the one that is on sale, then the element shows up again. And so v-if actually adds and removes elements from the DOM, whereas v-show leaves the elements there and just styles them differently. So the question is, when should we use v-if, and when should we use v-show? Basically, if the content that you are showing and hiding is expensive to generate, and it's going to be shown and hidden frequently, then you should use v-show because Vue won't have to go through the expensive rendering process each time it's redisplayed. But if your content is not expensive to render like our current example, or if it's not going to be hidden and redisplayed frequently, then v-if is perfect. Right now we're just showing and hiding a span element, but imagine if this was a Vue component that we were showing and hiding, and imagine that that component had a child component which also had child components. You can see how that can get really expensive to rerender. In that case, v-show would be better. In our case, v-if is perfect because it's just a span; it's not expensive to render at all, so we'll leave it with a v-if. Next, let's take a look at how to repeat elements using v-for.
Repeating Elements with v-for
By now, you've probably gotten used to the idea that we can style our components down here in the style section. Although we haven't talked about this directly, as we've proceeded this far in the course, we've been adding to these styles, and you've seen how it immediately affects the component. But we haven't really explored what's actually happening here or how far-reaching these styles are. To explore this a little more, let's jump over to our App component. Notice in the styles of our App component that we're styling things like this main tag and the header tag, and you can see those main and header tags up here in our template. But what about this body tag? That main tag does not show up at all up here in our template. And yet, if we look at our app, the gradient that we're adding to he body tag is showing up here in the background of our app. So how is it that the body style that we're adding here is getting applied to a body tag that's not part of this component? The body tag is actually part of this index.html file that was generated by the CLI. So notice we have a body tag here. So this index.html file is what is actually loaded when our app first loads, and it has a body tag. And then you can see down here on line 14 that we have this app element, and this is what the render function is binding to, and so this is where the rest of our app is displayed. But it's concerning that we have a style in our app.vue component that is styling something outside of that component. But not to worry, there's an easy solution to this. Back here in our App component, on the style tag right here, we can add a scoped attribute. And if we go back now to our page, you can see we've lost our gradient. So we just essentially scoped all of the styles inside this style element to only this App component. That's pretty awesome, but how does Vue accomplish it? Let's take a look at the source of our page in the browser. So I'm going to inspect this, and then notice on this header element that it has a data attribute with a hash after it. And if I go back over here and remove this scoped attribute and then refresh, notice that the randomized hash has disappeared. So first of all, the scoped attribute is adding those data attributes. So if we go back over here and put that scoped attribute back, and then take note of the header styles that are being applied here, the background-color, the width, and the margin. Now if we go look at the source again and refresh, take a look over here in the inspector that is showing the styles for header. Notice that it's not just being applied to any header element; it's actually only being applied to header elements with this data attribute. So that's how Vue handles scoping styles. It puts these randomized hashes on scoped elements and then targets them specifically with that hash. So a header element on another component would either not have this data attribute at all, or it would have a different data attribute because it's in a different scope. And the same concept applies to child components, as we'll see in the next clip. With some exceptions, if you add the scoped element to a parent component, its styles won't bleed into the child component. We'll take a closer look at this in a minute. But first, let's talk a bit about scoped versus global styles. As a general rule, you should not have global styles in any component other than your top-level app component. So let's go fix our RobotBuilder so that it has scoped styles. So over here in the style element, we'll add the scoped attribute, and so now we don't have global styles in our RobotBuilder. But the App component is a good place to have some global styles, so let's do that. And the interesting thing is that in our App component, we want some global styles, but we also want some styles that apply only to the App component. But that's okay; you can do that. You can have multiple style blocks, and one can be scoped and the other not. So now we have a global style block here and a scoped style block down here. And let's move this body style up here to our global style. And the rest of these styles are specific to the App component, and so we want to leave those in the scoped style block. So now if we go back and refresh, and let's close our console, you can see that we have our gradient background again. And so the body style is being applied globally, whereas the rest of these are only being applied to this component. Okay, cool. So now we'll take a look at how this all affects child components.
Styling Child Components
It's important to understand what it means when you add the scoped attribute to a parent component and how that affects child components. Right now, our RobotBuilder is a child component of our App component, and if we take a look at our RobotBuilder, you can see right here we have this robot-name class. Let's add an element in our App component with that same class. So right here just above our RobotBuilder, we're going to create a div element with a class of robot-name, which happens to match the robot name class inside our RobotBuilder. And then we'll close that div here, and we'll just say This is a test. And then down here in the scoped style, we will add that robot-name class with color: red. Okay, so if we go take a look at this, you can see that the robot-name styles got applied to the element in the App component but not to the element with the matching class name in the child component. So this is what we would expect; it is scoped to our parent component. But I want to point out something. Watch what happens if I come up here and wrap this div around the RobotBuilder. So now this div is wrapping the RobotBuilder. So let's see if that changed anything. Ah, now you can see that both the parent component and the child component got styled. So it appears that the parent component's styles are bleeding into the child component. And this is sort of true, but not really. At least, it's not true as far as Vue is concerned. Let's see what happens if we also add a border to this style. So down here, I'm going to also add border, and we'll add a 2px solid blue border. So now if we come back over here, notice that we do have a border that is wrapping the div in the App component, and that div stretches across this because the RobotBuilder is wrapped by this div. But the interesting thing is that even though the border is being applied to the robot-name class, it is not bleeding down into the child component's robot-name class. Otherwise, we would see a border around this robot title here. So it seems that the font color is bleeding into the child component, but the border is not. So when Vue says that its styles are scoped, what it means is that it's not going to apply that style directly to classes or elements outside of the scope, but that doesn't mean that the child elements won't be affected due to CSS inheritance. That's just the nature of CSS. In CSS, there are a number of CSS properties that get inherited. Font color, for example, is inherited, whereas border is not. That's why you see a difference here. So know that when you style something in a parent component that wraps a child component, traditional CSS inheritance will still be at play. But Vue won't specifically target elements outside of the component if the style is scoped. So it's important to understand that difference between Vue targeting an element or applying classes to an element in a child, versus styles just getting inherited because they are applied to a parent element by CSS. But what if you do want to be able to style a child component from the parent? Vue does allow a parent to style a child component within the bounds of scoped CSS, so let's take a look at how to do that. The easiest way to think about this is to consider the root element of a child component part of the parent's scope. So over here in our RobotBuilder, this element is the root component, or the root element of our RobotBuilder. And notice that it has a content class. So if we go to its parent component, the App component, and let's remove this test div; we don't need this anymore. And now down here, instead of robot-name here, let's change this to just content. So now we are applying these styles to the content class. And there is no content class in our App component; it's only in our RobotBuilder. So let's go back over here, and there, now you can see that the red text and blue border are being applied to the content class in our child component. This is only because that content class is on the root component of the child, and so you can use the root component of a child component to allow a parent to apply styles to the child component. But what if we want to access this robot-name from our parent and style it? So over in our App.vue, we could add .content .robot-name. So generally, you can target things in CSS like this where you can target a robot-name class that's contained within the content class. And since we're able to target the content class from the parent, the question is, are we able to target elements inside of that content class? So we would expect to now to see a blue border and red text around the robot name. So if we come take a look at this, yeah, you can see that our style's not getting applied anywhere. If you want to be able to do deep targeting like this, you need to use the deep selector that looks like this. And now if we take a look at it, there, now you can see that we can target it. So you can target child elements within scoped styles like this by starting with the class of the root element and then using the deep selector to select other items within the child component. But you need to be aware that this deep component is truly deep. It is not just the child component, but this will also affect child components of your child component, so it will keep going down the chain. And it's possible that this won't work with some CSS preprocessors, and so in that case, there is also the deep selector, and that has the same effect. Okay, we don't want to keep this here, so let's delete that. That covers everything for styling child components. Next, let's take a look at conditionally applying styles to elements.
Conditionally Applying Styles with Style Bindings
Conditionally Applying Classes with Class Bindings
Let's see how to replace this style binding with a class binding so that we can use classes instead of inline styles. First, let's start by adding a class down here. We'll call it sale-border, and that will just have a border that is 3px solid red. And then back up here where our style binding is, instead of this we'll use a class binding. And then a class binding is basically an object where the keys are the names of the classes that you would like to toggle. So in this case, we have a sale-border class. And then you just set that to a Boolean expression that will evaluate to true when you want this class to be applied. So in this case we want it to be applied when the selectedRobot.head is onSale. So this is just saying apply the sale-border class when the onSale property is true. And if we wanted to toggle multiple classes, we would just add more properties to this object. So this should be working just like the style binding was. So the Friendly Bot is on sale, and the rest are not on sale. Perfect. Now one thing that you might not have noticed, if we inspect this, this div where we applied the sale border, also has the classes top and part. If we go over and take a look at our template, you can see that this element has both a class binding and a class attribute on it, so the top and part classes are going to always be applied, and the sale-border property is going to be applied conditionally, so you can combine them like this. Of course, this class object could get fairly complex if you were toggling lots of classes. So you could always move this expression to a computed property like we did with the style binding. But like style bindings, there's another syntax that we could use here, an array syntax. So I could just provide an array here, and that array contains the classes that I would like to apply. So this is not conditional; this will always apply a class, so we should be seeing that everywhere. But we could then take this and, let's undo a little bit and grab this expression, and with this array syntax, let's put in a, let's use a computed property like we did with our style bindings. So I'm going to create a computed property called saleBorderClass, and we will add the value of that to our array. So now down in our computed properties, I can change this to saleBorderClass. And then this is just going to return the saleBorder string if the robot head is on sale, so we'll just use a ternary like this. So if it's on sale, then return sale-border. Otherwise it'll return an empty string. And we need this here. Okay, so that should be conditional again. There we go. And now that we have that using the array syntax, we could actually collapse these class expressions into a single expression. So I'm going to take these out of here and make them strings in here. And then I can get rid of this class attribute, and now it's all taken care of in our class binding. Cool, I like that final syntax, so let's go with that. And next, we'll take a look at using CSS preprocessors like Sass.
Using SASS and Other CSS Pre-processors
Vue.js fully supports using CSS preprocessors if, for example, you want to use something like Sass instead of plain old CSS. In fact, the zero-config web pack configuration that comes with the basic CLI includes all of the web pack config to support Sass, Less, and Stylus. All you have to do is npm install the appropriate loaders if you want to use them. So here in our terminal, I'm going to just npm install node-sass and sass-loader and save that. Let's go ahead and install that. And since the built-in web pack config is already set up to handle the Sass loader, we can just jump in using that in our project. So all we have to do to support Sass is come down to the Style section, and up here at the top where we define our style tag, all we need to do is specify the language used here, so I can say lang=scss. And now I can start using Sass syntax in here, so I could, for example, wrap this inside of the part class, and then if we come back over here and start our server again and then come back over to our browser and refresh, you can see that all our styles, so these images, are still the correct size, even though we added the Sass syntax for sizing them. So that was surprisingly easy, and you can see how the zero-config environment made that really simple. Of course, if you wanted to use a CSS preprocessor other than Sass, Less, or Stylus, you could do that, but you would have to go in and modify the web pack config, and we'll talk about that in the deploying to production module. But out of the box, those three are already supported. And now our robot builder is using Sass. This wraps up our discussion of styling our components. We're going to next take a look at the different component lifecyle hooks that we can tap into.
Using Component Lifecycle Hooks
There are several lifecycle hooks that we can tap into to take an action based on the various lifecycle states of a component. Let's take a look at the created lifecycle hook. A typical use case for lifecycle hooks is to fetch data from a API when a component is first created, but we're not ready to do that just yet. We'll implement that exact use case later in the module about state management when we start using an API. For now, let's just make it so that whenever this RobotBuilder component is created, we log something into the console. So all we need to do is add a created function like this. And then we can just console.log in here. And tapping into a lifecyle hook is that easy. Let's go check this out over in our browser. So let's open our console and clear it, and then I'm going to refresh this. And when this page loaded, the RobotBuilder component was created. And then you can see down here we got this component created message. And again, this isn't a great use case, obviously, but it's pretty common that you'll do things in here like fetch data from an API that's needed for this component. There are a handful of lifecycle hooks, but covering all of them is beyond the scope of this course. If you want to explore more about lifecyle hooks, you can check it out here in the Vue documentation. They provide this lifecyle diagram that shows all of the different lifecycle hooks, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, and beforeDestroy, and finally, destroyed. And it shows you in this diagram here where in the lifecycle of a component each of these hooks gets called. And again, we'll touch on this more later in the course. If you want to look at it right now, to explore lifecycle states further, you can check it out in the Passing Data to Parent Components clip, which is inside the Enabling Intercomponent Communication module, or you can also look at it in the State Management module when we start talking about making API calls. For now, let's move on and take a look at mixins.
Reducing Duplication with Mixins
Mixins are a way to share functionality across multiple components. Basically, any component option can be extracted out into a mixin file, and then when you import the mixin, it will be merged into the rest of your component options. Let's see how to do that. Let's take this created lifecycle hook and move it into a mixin. So we'll grab it from here, and then let's create a new file called created-hook-mixin.js. And then here we'll just export an object. So basically, we're just export a component configuration object here, and whatever is in this object will be merged in with any components that you use it in. So to use it, let's go back into our RobotBuilder, and we're going to import our createdHookMixin from created-hook-mixin. And then we're just going to use this down here in a mixin property, so mixins, and then this is just an array of mixins, and we're only going to have one, the createdHookMixin. Cool. Now this mixin can be used in multiple components. Right now, we're just going to be using it in this one. And if we come back over to our web app and open our console and refresh, you can see our component created messages still getting logged. And so our created hook is still working, but it is now extracted out into a mixin that could be reused. Again, our application isn't quite mature enough yet for a great example for mixins. If you want to see a better example of this, in the Managing State with Vuex module, we talk about it when we have a few components that all need the same data, so we'll create a mixin in order to share the logic that will be used to fetch that data across multiple components. You can find that in the clip on Using Actions to Work with APIs and Asynchronous Data. I just wanted to talk about it a little bit here in the components section so that you knew about it. And just be aware, you can use a mixin to share any component option across multiple components including lifecycle hooks, props, computed properties, etc.
In this module, we learned about the following component concepts, creating components, using bindings to display data and handle events, conditionally displaying elements with v-if and v-show, repeating elements with v-for, the various ways to style components, working with component lifecycle hooks, and using mixins. In the next module, we'll export various ways to communicate between components.
Enabling Inter-component Communication
In this module on intersection-component communication, we'll explore how to communicate between components. You don't get far in building an application before you run into situations where one component needs to know something about what's going on with another component, so it'll be good to learn how to accomplish this with Vue. There are a few topics to cover including using props to share data with child components, validating component props, passing data to parent components with events, and injecting content into a child component with slots. Understanding how to do this will allow us to build rich client-side applications with Vue, so let's dive in.
Creating Child Components
So far, we haven't really done much with creating child components, other than the fact that our App component uses our RobotBuilder component. If we take a look at this RobotBuilder template, you can see a lot of repeating code here for each of the individual part selectors. Let's turn this into a part selector component and reuse it in each of these places. When we do that, we'll need to pass down to the PartSelector component the parts that we want it to select from, and that will be a great way to show how to communicate between components. So we'll start by creating a PartSelector component, and rather than completing this whole thing from scratch, let's start with a partially completed component. So over here in our helper repo in the src, build folder, we have this PartSelector.vue file. So let's grab that and paste that into our new component. So if we take a look at what we've pasted in here, you can see that at the top here we have some HTML that looks a lot like the HTML that was being repeated multiple times over in our RobotBuilder. And then down here in the component script, you can see that we have some functions for getting the next and previous index, and down here methods on our component for selecting the next part and the previous part that call those functions. This should look a little bit familiar because this same code, or code a lot like it, existed over in our RobotBuilder. So we've just moved that over into our PartSelector. And then notice up here, we're importing all of the available parts, and we're setting, here on line 13, this parts variable to the heads from the availableParts. This is just temporary so we can get up and going. The parts for the PartSelector will actually be passed into us by its parent component, so we'll delete this pretty quick here. So just to bring you up to speed with what the plan is, if we look at our robot builder, each one of these parts is going to be a different instance of the PartSelector, and the RobotBuilder will be responsible for giving each instance of the PartSelector the correct list of parts for it to work with. So in the RobotBuilder, when we create this instance of the PartSelector, it will pass in a list of heads, and when it creates this one, it will pass in a list of arms, etc. This will be a great way to learn how to communicate with child components. And now that the PartSelector will be taking care of all of the selecting of the parts, then over in our RobotBuilder we can delete a bunch of stuff here. So we can delete these functions here that we saw exist now over in the PartSelector, and then we can delete all of these functions down here. And notice how many of these there are that we're going to be deleting, and all of these will be replaced with just two functions in the PartSelector. So you can see how this is going to be eliminating duplication, and it's going to be a lot cleaner. All right, and then we're not going to needs these selected indexes here, either. And then we're just going to initialize each one of these parts in our selected robot to just an empty object, so each one of these will be just initialized like this. And actually, now that this doesn't have any complex calculations anymore, it doesn't really need to be a computed property, so let's just move this up to our data object. And this will just be a property instead of a function, and it'll just return this object. And now we're just going to go ahead and import our PartSelector. All right, and then this is something that you may not have noticed in the App component, but whenever you reference a child component, not only do you need to import it; you also need to list it in a components array on the parent component like this. This is what makes our component aware of this selector so that we can now use it up here. Okay, so now we're going to replace each one of these with just a PartSelector. So we have one at the bottom for our base, three in the middle for the left arm, torso, and right arm, and then up here in the head, we have a little bit more going on. So we can go ahead and replace all of this here with the PartSelector, and we can get rid of this div here. This stuff, however, we're going to want to reuse later, and so I'm just going to comment that out for now. Okay, so now we can use these PartSelector elements because we have declared them down here. Oh, and actually, this is not supposed to be an array; this is an object. So this is really an object where we're saying that we have a PartSelector component that is set to that PartSelector, but we can just use the shorthand syntax like this. But you can see already how much cleaner our template is because we cleaned up so much duplication. So let's go check this out. Okay, well, we've got all heads again, so not exactly what we want, but it is what we expected since our PartSelector is currently just hardcoded to return heads. To fix that, each instance of our PartSelector needs to receive its list of parts from its parent component, so we'll do that next. But you'll also notice that our arrows on our buttons are missing, and the buttons aren't exactly in the right place. So depending on where the PartSelector is, sometimes the buttons need to be on the top and bottom of the image, and sometimes they need to be on the left and right. So the PartSelector will also need to know what position it's in before it knows what type of buttons to display, so we'll fix that too. We'll get into all of that in the next clip when we start talking about how to pass data down to child components.
Using Props to Share Data with Child Components
We need to update our RobotBuilder to pass in the parts to the PartSelector for it to cycle through. So we'll start with this first one, which should allow the user to select which robot head they want. So that means over in our RobotBuilder, right here we want to tell the PartSelector which parts to use, and we'd like to do that like this. So availableParts is already available to us in our RobotBuilder's data object, and then we're just grabbing the heads off of it. And notice that we're binding it to a parts attribute of the PartSelector. This won't work yet because the PartSelector doesn't have a parts attribute to bind to. We'll add that next. We can do that over in the PartSelector by just adding props to the component like this. And then the most simple syntax for this is just to pass in a string array. And that's all there is to making props available to be bound to by parent components. And now that we have that, we can stop hardcoding the parts up here, which means we don't need this import here. And then down here inside the component wherever we're using parts, this needs to be this.parts now, since we're getting that off the props. Okay, so now that we have that prop, let's go ahead and pass in the correct parts in each of these instances. I'll just copy and paste this. And this is torsos, and this is arms again, and then this one is bases. Cool, let's go see how this looks. Awesome, now we're getting the correct parts, even though the arms aren't quite positioned right. And even though our buttons are still missing their arrow icons and aren't quite positioned right, if I click them, they do work. And you can see our PartSelector even supports indicting if a part is on sale. Now in order to fix the arm positions and display the buttons correctly, we need to apply a CSS class to each one of these PartSelectors based on the position of each part selector. So for example, this part selector for the head is in the top position. This left arm selector is in the left position, this one's in the center position, this one's in the right position, and this one's in the bottom position. And if we take a look at the PartSelector down in the styles, you can see that I've already created CSS classes to handle displaying those correctly. But these position classes aren't being applied yet anywhere in our PartSelector component, and that's partly because the PartSelector component doesn't know what position it's in. So let's come back over to the RobotBuilder, and in addition to specifying the parts for each one, let's specify the position. So I'm going to set position=top. And notice there's something different about this binding. Notice the parts binding is using a colon, but the position is not. That's because position isn't really actually a binding. It's really just an attribute on the PartSelector, and we're setting it to the hardcoded string top. Whereas parts is an attribute, but we're binding to that attribute and passing along availableParts.heads, which is an expression that needs to be evaluated. So let's go ahead and add this position to each of these others too. So we've got left and center, and then this is going to be right, and then finally down here for our base, this is going to be bottom. Okay, now we're passing to our PartSelector what position it's in, so over here we need to add a position prop so it can receive that value. And now we're just going to use that in a class binding. So up here in the template, right here on this div, in addition to the part class, I also want to bind to the position. So remember, we can specify both a class and class binding, and they'll get combined. And so in the case of the head, this div will have two classes, a part class and a top class. So whatever value is passed in to the PartSelector for the position prop will become a class here. And then based on those classes and the styles that we specified below, everything will get styled correctly. So let's go take a look at our component again. There we go. Now our buttons are all positioned and you can see that some are on top and some are on bottom versus left and right, and the arrows are all there. And that all came from our CSS styling. So just to review the important parts of this, we can pass data into child components using attributes like this, and for each attribute, you just need to specify props in the child component. It's really that simple to pass data to a child component. We can, however, take this a little bit further and actually validate the props that are passed to make sure that they're the right type, etc. Let's take a look at that next.
Our PartSelector component now takes in two props, parts and position, but there are no restrictions on the type of data or values that could be passed in to these props. We'd like to provide some validation to prevent parent components from passing in bad data. That's actually quite easy to do with a little different syntax. Let's start by requiring the parts prop to be an array and the position prop to be a string. So instead of using an array here, we'll use a keyed object where the keys are the names of the props, and the values are objects that specify validation information. So for the parts prop, we expect its type to be an array, and for position we expect its type to be a string. And now that we have these in place, if we come back over to our RobotBuilder, and on one of these, instead of passing in an array for the parts, we'll just pass in a string. And now if we come back over here, obviously, our left arm selector isn't going to be working because it's a string, not an array of parts. But if we take a look in our browser, you can see that we have a validation message here because we passed in an invalid data type for the parts. And you can see right here it says Expected Array but got String. So we now have type checking that can help us identify problems during development. So let's go back and fix that. And then we can also make our props required. So here I'm going to, in addition in to specifying type, specify required: true, and then we'll do that for position, too, because it's also required. And now over here in our RobotBuilder, let's not pass in the left position. So if we come back over here, let's clear this and refresh. Well, you might be surprised to see here that there is no message. There's no validation error, and that's because, if we look at what we passed in, we didn't pass in nothing. We actually passed in an empty string. And so because we are providing a value for a position, it's not triggering the validation message. If we remove this altogether, however, and come back over here, there, now you can see it's saying that there is a required prop that's missing. But what if we actually do want to ensure that users cannot pass in an empty string here? Essentially, we want to indicate what are the valid values for position? So we can do that with a validator function. So in addition to specifying that position is required, we're going to supply a validator function, and that is a function that takes in the value that's being passed in to the prop. And we just want to ensure that it is one of these values, left, right, top, bottom, or center. So basically, I just want to make sure that the value is included in that array, or that this array includes the values that's passed in. Okay, now because we are passing in an empty string, we should now be seeing an error over here, and sure enough, if we refresh here, you can see that we are getting an error that the custom validator failed for prop position. Cool, so let's go back to our RobotBuilder and pass position back in here, and then if I clear this and refresh, you can see we're not getting that error anymore. And our RobotBuilder looks great. So awesome, we're now validating the props that being passed into our child component. Next, we'll take a look at how to pass data back to a parent component from a child component.
Passing Data to Parent Components with Events
We now have a fully functioning PartSelector, and we're passing data from our RobotBuilder down to the PartSelector via its props. However, notice when I click this Add to Cart button that I'm getting weird results down here in the cart. This is because our RobotBuilder, which is the parent component, isn't aware of changes that are happening inside the PartSelectors, which are child components of the RobotBuilder. If we look at the code, you can see that the Add to Cart method expects there to be a selectedRobot, and in our data function up here, you can see that we are initializing each of the parts in that selectedRobot to an empty object. And before we extracted the PartSelector component, each of these parts was getting updated whenever the user selected a new part, but now that selection is happening inside our PartSelector child components. So when we click on each of these buttons, the RobotBuilder has no idea that anything has changed because these buttons are inside the PartSelector component. So we need a way to tell our parent component when a different part is selected. Let's take a look at how to create events in the child component that the parent component can bind to. This will allow the parent component to react to changes in its child components, and it will allow us to pass data back to the parent component from the child component. So over here in our PartSelector component, down here in the methods, you can see that we have these two methods, selectNextPart and selectPreviousPart. We want both of these to emit an event that parent components can listen to by binding to them. This is surprisingly simple with Vue. Each Vue component has an emit function available that we can call to emit an event. So right here in the selectPreviousPart function, down here at the bottom, I'm just going to call this.emit, and then I'm just going to pass in the name of the event that we want to emit. So let's emit the event partSelected. This now means that a parent component can bind to a partSelected event when it creates the PartSelector component. We'll see how to do that in just a sec. First, when we emit this event, we want to be able to pass along some data with it. It won't do us much good to let the robot builder know that a part was selected if we don't tell it which part was selected, so let's pass along the currently selected part like this. Remember, we have a computed property up here on our component called selectedPart so we're just using that here to pass it along. All right, and we're going to also want to emit that inside the selectNextPart function, so let's copy this and paste it here. Okay, cool. Now let's go see how to bind to this in the parent component. So over here in our RobotBuilder, up here we have our PartSelectors, and we'll start with this one here. We just want to bind to this new partSelected event. Binding to a component's event is done the exact same way you bind to any other element's event, with the v-on syntax or the at shorthand syntax. So we're going to bind to partSelected, and then we could bind that to a method on our component like this, but actually, I think that this is going to be simple enough. I just want to handle it inline like this. So this is a function that takes in a part. And then this is the PartSelector for the robot head, and so I want to set the selectedRobot's head to this part, so we'll do that like this. So remember, the selectedRobot is an object that we already have on our component, and we're just setting the head to this part that's being passed in. And that's it, easy peasy. All you have to do to pass data to a parent is emit an event in the child component and then bind to it like this. So let's go ahead and do this for the other PartSelectors. So this is the left arm, this is the torso, this is the right arm, and this is the base. Okay, so that's it; the RobotBuilder is now aware of the selected parts because we're binding to the partSelected events and setting the selectedRobot parts. So if we go back over to our browser now and select each of these parts and then click Add to Cart, you can see that that works just fine because the RobotBuilder is now aware of the data that's being passed back to it. But actually, we still have a small problem here. If I refresh this page and then click this Add to Cart button without changing anything, you can see that we're still getting our broken state. So it doesn't work when the page first loads, but if I change all of the parts and then click Add to Cart, you can see that it works just fine. This actually makes sense when you remember how we implemented the events. If we go back over to our PartSelector, you can see that we only emit the events when the selectNextPart or selectPreviousPart functions are called, and these are only called when the user clicks the buttons. So when the page first loads, the PartSelectors haven't emitted these events yet, and so the RobotBuilder doesn't have any selected parts. This is actually a great opportunity to tap into a component lifecyle method. So let's add a created function, and then let's emit the partSelected event when the component is first created. But instead of copying this line again, let's create a new function called emitSelectedPart, and then we will move this up into here, and then right here, we can just call this.emitSelectedPart. And then we'll do the same thing down here. And then in our created event, we'll also call it here. So now the currently selected part will get emitted when this component is created, in addition to when clicking the buttons. And you can see up here that in our data function, we initialize the selectedPartIndex to 0, so when the selectedPart component is created, the first part in the list of parts that's passed in is always the selected part. So we have a default selected part that will get emitted whenever this component is first created. So now if we go over to our page here and refresh, you can see that I'm not going to change any of these parts, and I'm going to go ahead and click Add to Cart, and now it's working just fine because those events got emitted. So this is working great, but it's a little bit hard to see the events as they flow in as we click on the buttons because we don't see that the events are working until we click the Add to Cart button. For that reason, in order to see this a little bit better, let's add a little preview image up here above this Add to Cart button that shows a preview of what our currently selected robot looks like. And as the events are emitted from the child components, you'll see the parent component changing. So we'll grab the HTML for that preview element over here in our GitHub repo. In src, build there is this RobotBuilder-preview.html, so let's grab that. And then we'll come back to our RobotBuilder, and right here at the top of the template inside the content div just above the Add to Cart button, we'll add that here. And then there's some styles for this too, so let's go grab those, so that's this RobotBuilder-preview.css. So let's copy that, and then back over here we'll just drop those in the bottom of our style section. Okay, let's go take a look at that and see what we've got. Okay, so you can see our preview is showing up here, so that's great, but the Add to Cart button is on top of it, so let's go fix that. So over here in our template, let's move our button into our new preview section. We'll just move that up here. And then down here in the CSS, we just need to find the Add to Cart style, and we'll no longer need this right position, and we want to change the width a little bit to just 210 pixels. Now let's go take a look at that. Okay, there we go. Now the Add to Cart button's below, and there's our preview image. And now that we have that preview image, we can see the events coming in as we change them by clicking the parts here. And you can see that the preview image, which is on the parent component, is changing as we change the parts in the child component. Okay, this is awesome, and this is a great example of how to emit events from child components to pass data back to parent components. Now I just want to take a short detour here to talk again about lifecycle hooks because we have a great use case now. If we go back and look at our PartSelector component, you can see that right here we are emitting the selected part in the created lifecycle hook. And then down here in the selectNextPart and selectPreviousPart methods, we are emitting it again. We could simply this a little bit by using the updated lifecycle hook, and that is called whenever the data for the component is updated. So let's change this created hook to an updated hook, and then let's come down here and just delete these so that they're not emitted. These aren't going to be necessary because when we click the buttons, we end up changing the data, and because the data is changed, the updated lifecycle hook will get called and emit our selected part. So now if we come back here, you can see that our data is not being updated, but if I click this, then updated is being called, but this isn't quite what I want because we started out with it blank. And so we actually need both updated and created. We need those both to called emitSelectedPart. So now if I refresh, now as I change the parts, then you can see that the preview looks accurate. Cool. I just wanted to take that little detour to show you how lifecycle hooks work now that we had a better use case. And this is awesome. Now we've seen how to pass data to parent components with events, and we learned a little bit more about lifecycle hooks in the process. Next, let's learn how to inject HTML or Vue content into a component.
Injecting Content into a Child Component with Slots
Sometimes you want to create a component that you can pass content into and have it be displayed in the appropriate place within that component. Imagine, for example, a component that is used as a collapsible drawer that expands and collapses as you click on a header, showing and hiding its content when clicked. In that case, you want a component to be reusable so that you can just wrap some part of your template in a collapsible component and it shows and hides that content. To experiment with this, we'll do just that; let's create a collapsible component that we can use to show and hide this preview image over here. And so over in our RobotBuilder, we want to be able to use it like this. So around this preview content, I want to have a CollapsibleSection component, and I'll wrap all of this content, the preview image but not the Add to Cart button, in that CollapsibleSection component. And then basically, this CollapsibleSection component will have a header that says expand and collapse, and when we click on it, it will show and hide this content. So let's go create this component. And I'm going to put that in a shared folder, since this is likely to be used by other components. And in there, I'll create a new file, CollapsibleSection.vue. And the template for this will be really simple, so I'll just type that in. So here's the template section of our component, and it's going to have an outer div, and then it's going to have an inner div that is the header, so we'll give it a header class. And then it's going to have a couple of spans in here, and those spans will say Collapse and Expand. And these are going to be shown or hidden based on whether the CollapsibleSection is currently open or not, so we will add a v-if, and we'll say if it's open, then show Collapse. And down here, if it is not open, then show Expand. And then on each of these spans we're going to bind to a click event, and we will just toggle open to not open, and we'll do that for both of these. And then I just want a down and up arrow here, so I'm going to add these Unicode characters. So that is an up arrow, and then we want a down arrow here. Okay, so there's our header, and then basically we want to be able to add right here our togglable content here. So this is where we want our injected content to appear, so we'll come back to that in a second. First, let's add our script block. And we're going to export our component definition object, and this component will be named CollapsibleSection. And it will have a data object which has the open variable that we're toggling with our click handlers up above. So our data object will just return a new object with open set to true by default. Okay, and that's it for the component script, and then we just need a few styles here, and we'll have those be scoped as always. And we just need to style our header, and I wanted it have a background-color of gray and a little bit of padding. And we want it to have a pointer cursor. Okay, so now we have a component that has a header, and when we click on it, it toggles the open flag between true and false. Now we just need to know how to display injected content right here where we want our togglable content. For that, we use slots like this. So this slot element is a Vue-specific thing, and now that we've added it here, when we create a CollapsibleSection element like we have over in our RobotBuilder over here, this content that we put inside of our CollapsibleSection will be shown right here where this slot element is. It's really that simple. Of course, since this is a collapsible section, we want to hide this slot when it's not open, so we'll put a v-if on here and only show it when open is true. Okay, so if we go back to our RobotBuilder component and we want to import this, and it looks like I must've missed something here. Yep, this CollapsibleSection needs to go down here actually. You may have already noticed that when I did it before. Okay, so if we come down here now and import our new CollapsibleSection component, so I'm going to import CollapsibleSection from ../shared/CollapsibleSection.vue, and then we can use this down here in our components. Now that is available to be used. And so now, this CollapsibleSection component should be working. Let's go take a look at that. There we go. Now you can see that we have a header here above our preview, and actually that's now working. I must've got my click handler wrong. Oh, yep, here I meant this to be equals not open. So basically, clicking on these spans will toggle the open variable. So now let's come back over here. And when I collapse this, there we go, so our collapsible content is working, and you can see how we injected content into our collapsible content using a slot. And then one last thing to be aware of, this may be a little less common, is the concept of default content for a slot. So in our CollapsibleSection, inside this slot element, I can provide any content that I want, so I could say here, div DefaultContent. And then this this will be displayed if no content is passed in. So let's add another CollapsibleSection here just above this one, and we won't provide any content for that. And now if we come over here, you can see that we have DefaultContent here instead of any data that was passed in because we didn't pass in any content here. And as soon as I pass in something here, then that will show instead of the default content. And that's all there is to working with slots. I'm going to go ahead and delete this. And that wraps up this module on inter-component communication.
In this module, we've learned about using props to share data with child components, validating component props, passing data to parent components with events, and injecting content into a child component with slots. In the next module, we'll explore Vue.js routing and navigation.
Routing from Page to Page
All modern front-end frameworks provide some sort of routing mechanism for defining routes and navigating between pages in your single-page app. Vue.js, of course, is no different. In this module, we'll explore how to do this with Vue. This will include adding routing to our app, linking to routed pages, styling links based on the active route, navigating from code, working with route params, using nested routes, using named views, enabling HTML5 History Mode, and preventing navigation with navigation guards. Our build-a-bot app really needs some routing love, so let's go check it out.
Adding Routing to Your App
If we take a look at our App component, you can see we're importing the HomePage component and RobotBuilder component down here, but without routing the only way we've been able to see each of these pages is by replacing this element up here. What we really want do is make it so that we can get each of these pages and others by changing the URL or by clicking on links. In order to do that, we'll need to install routing. If you remember, when we first created our project with the Vue CLI, we had the option to select routing, but we opted not to. However, adding routing to an existing project is really quite easy. First, we just need to install the vue-router package. So over in my terminal, I'm going to stop my server from running, and then I'm going to npm install vue-router, and then I'm going to save that. Okay, and then let's go ahead and start up our server again. And then back over in our app, we just need to have a place where we can define all of the routes for our application, so let's create a new router folder, and inside there we'll create a new index.js file. Okay, now inside here we're just going to import Vue, and then we just need to import the router, and we'll import that from vue-router that we just installed. And now we just need to tell Vue to use the router like this. So now Vue is aware of the fact that our application is going to use routing. And then we just need to export a new router. And then we just need to define the routes in here, so we pass into this a router configuration object, and then it has a routes property, which is an array. Okay, now we're ready to add our first route. This will be a route to our HomePage, and it will look like this. So it's an object that has a path property, and this is the URL that when Vue sees that this is the URL in the browser it should navigate to this route. And we'll give this route a name; we'll call it Home. And then we need to tell it which component to load when this route is navigated to. So we're going to tell it to load our HomePage component. And then we'll just have to import that up here. Okay, so this is our first route. This just says when I navigate to this URL, then display this component. And then this is just the name that we're giving to our route, and we can use that in various places in our code. So this is pretty simple. And while we're in here, let's go ahead and create a route to our RobotBuilder too. So let's copy this, and we'll add another one, paste that in here. And the route for our RobotBuilder is /build, and we'll name this route Build, and then we just need it to render the RobotBuilder component. And we'll just have to import that. Okay, so this is our routes file all built out for the two pages that we currently have in our application. Now we just need to pull these routes in when we create our Vue instance so that Vue is aware of them. So let's import our new router, and then we just need to add that to our Vue configuration object here. Okay, so now our Vue instance is aware of our routes. The last thing we need to do is tell our app where to display routed components. So over here in our App component, right here where we've been hardcoding the component that we want to display, what we really want to do instead is display the component that matches our current route. We can do that by replacing this with the router-view tag. So now Vue will display the component that matches the current route right here, and we don't need these imports anymore, and we don't need this components property anymore. Okay, cool, let's go check this out. So if we come over to our browser and put in this URL, we're navigating to the root of the website, you can see that it loads the home page. You may notice, however, that it also added this hashtag, and this part of the URL after the hashtag is currently what Vue is looking at for matching the routes. And we'll talk later in this module about how to get rid of that hashtag using the HTML5 history mode. But for now, I can navigate to my pages by changing the route here, so now I want to navigate to /build, and you can see that takes me to my robot builder page. Okay, awesome. So our routes are working great, but the only way we have to get around is by manually changing the URL. Let's go see how to create links to these routes.
Linking to Routed Pages
Now that we have a couple of routed pages, let's see how to link between them. We'll start with this Get started link right here. So over in our HomePage component, you can see that we have this Get started link right here, and it's just an anchor tag. In order to link to a route, we instead need to use Vue's router-link element like this. And then instead of an href attribute, we use a to attribute, so we're going to route to this URL, or this route, and I'm going to just say /build here. So I can put in a string here that represents the URL for one of the routes that we've added, and then I just need to add the correct closing tag here. And now if we go check this out, I can now click on this Get started link, and there we go, it navigated me to the build page. So that's working now, but how do we get back to the home page? Let's make this logo and text here a link to the home page. This will give us chance to look at a little bit different syntax. So this is going to be in our App component, and that's up here in our nav section. So you can see here, here is my logo, and here is the Build-a-Bot text, so let's go ahead and add a router-link around the logo and text. And then in our last example, we just used to, and we bound that to a URL like this. Instead of using this syntax, let's actually use a binding here, and we're going to pass into it an object. And in this object, I'm going to specify the name of a route, so I want to navigate to the home route here. And if you remember, over in our router index file, each of our routes we gave a name, and so we're routing to the route that has a name of home, which is going to be this route here. So let's go check that out. So over here, you can see that we have a link now for our text up here, and the logo, it's also a link. So if I click on that, you can see it takes me to the home page. The only problem is that we don't want this link styling that we have here, so let's go add a class for that. So back over here on our router-link object, let's add a class, and that class will be nav-link. So notice that I can put a class on a router-link element just like I can any other element. And then down here in the styles, let's just add that nav-link class, and we'll set text-decoration to none, and we'll tell the color to be inherited. So this will avoid changing the color of the link. Okay, so if we come back over here, there we go, now our link is styled so I can go back to the build page and then back again to the home page. And this looks great. So now that we know how to link to our routes, let's go see how to style the currently active link based on the current URL.
Styling Links Based on the Active Route
Navigating from Code
Working with Route Params
Our PartInfo component is just currently hardcoding the title and description here on the page. What we'd like to do is to be able to specify the part type, like heads, and the part index, and then use that information to look up that part and display its info. In order to do that, we're going to have to update our route to expect those parameters, and then we need to grab those parameters off of the route in our component. So let's update the route first. So first, we need to update the URL for this route to add the params and use a colon here followed by a variable name, so partType, and then :id. So this route now expects a part type like head, arm, torso, etc., and then a part id. With this information, we'll be able to look up our part from our parts list and display its information. So now let's go over to our PartInfo component and update it to grab these route parameters. So first we'll import our parts data. We'll import parts from data/parts. And now we'll need a computed prop, so let's add computed, and we'll have a part property that is a computed prop. So we'll return an object here, and that will look like the one down here in data. I'm just going to cut this out of here and paste it into here, and now we can get rid of data; we don't need that anymore. And then this is what we want to do the lookup for us from the params. So we want to look up the part and get the title and description for that part based on the params that are passed in on the URL. So the route params are available to us on the component's route property, so we can get the partType, for example, like this. We're going to say this.$route.params.partType. And then this partType right here needs to match the parameter name over here. So with that information, Vue can parse the partType off of the URL, and then we can get the ID in the same way. So id = this.$route.params.id. Okay, and this is red because our linter is telling us that we should prefer destructuring here, and so I can change this to look like this, set partType and id using destructuring, and we'll set it to this.$route.params. Okay, so now I can use this to look up the parts, so instead of returning this hardcoded object, I'm going to do a lookup on the parts array where the partType is the partType that I get off of the params. So remember that the parts object that we're getting, that we're importing, is a keyed object where the partType is the key, and then that returns an array of that partType, so then I'm going to do a find, and I'm going to find where the part.id equals the id from params. But actually, there's a problem here, and that's because the params that are pulled off a URL are always strings, whereas the id's on the parts in our data are integers. And so this triple equals actually isn't going to work because it's not going to find a part with an id that is a string. So we can fix that by casting id to be a number here. Okay, so now we have our router set up with params, and we have our component set up to use those params. The last thing that we need to do now is just pass the params from the PartSelector component. So over here in our PartSelector, down here in our showPartInfo function, we want to pass the URL parameters here for PartType and id, but we can't pass part params using the string syntax; we actually need to use the object syntax in order to pass parameters. So I'm going to change this to an object and use a named route where the name is Parts, and then I can pass params. And then I'm going to specify that the id param is this.selectedPart.id, and the part type is this.selectedPart.type. Okay, let's clean this up a little bit. We'll bring this down here, bring this down, and we'll bring this down. Okay, so now we are saying navigate to the parts route and pass in the params id and partType, and we're going to get those off of the currently selected part. Okay, let's go check that out. So back over here, now if I click on one of these parts, there we go. Now we are navigating to parts/head/1, and we get a description of that part, a Large Cyclops. And if I change this to this part, now it is the Friendly Bot head. Okay, before we wrap up on this, let's just demonstrate that this is also possible using links instead of code navigation by changing this link to be a router-link rather than navigating from code. So back in our PartSelector up here, let's get rid of this click binding, and then let's wrap this img in a router-link, and then we'll use a to binding. And let's grab the route expression down here from this function. I'm going to cut this out of there, and then we don't need this function anymore. And then I'm just going to paste that expression in here. And I'm just missing a closing bracket here, there we go. Okay, so this is just the same as when we were navigating with code, so I can click on these and go to the parts page with the params. Awesome. So this is working great, and we're passing parameters between components using router-link. But next we'll see how to decouple our PartInfo component from the router by passing the route params as props instead.
Passing Params as Props
One downside of using this route expression is that it couples this component to the router. What if we wanted to use this component as a child component somewhere else and pass the partType and id as props instead of pulling it off of the route params? Well, we can make this work both as a routed component and as a child component by updating the route for this component to pass the params as props. So we can do that over in the route simply by setting the props property to true on this route. So now Vue will pass the route params that it identifies on the path for this route as props instead of as route params. And with that simple change, we can come over to our PartInfo component and add props for the partType and id, and now instead of getting partType and id off of this.route.params, we can just get it off of this, which is where the props are. And it's that easy to decouple our component from the route. You can see over here that that is still working just fine. But let's add a little bit of validation to these props. So we'll change the props to an object, and it will have a partType, and we want it to be of type: String, and it will have an id, which will be of type: Number. But the problem with this is that when this component is used as a routed component, this id will still be passed in as a string, so we need to allow either a number or a string. But just to be sure that nobody passes in an invalid value here, let's also add a validator. And then we'll just verify that this is a number, so we'll call Number.isInteger, and then we will cast the value to a Number. Okay, so now we have some validation around our props, and if we come back over here, this should all still be working just fine. Great, now we know how to use route params and how to pass those parameters in as props. Now let's explore nested routes.
Using Nested Routes
In order to understand what we're going to build in order to look at nested routes, let's take a look at the final result. So we're going to be building this Browse Parts page, and notice up here the URL is parts/browse. And when we navigate to that, it shows us a list of all the different parts. But then as I click on each of the different part types, you can see that the bottom part of this page here is changing, but this top part that includes this menu is not changing. And notice the URL up here as I click on each of these is also changing. And so the URL is changing, but really all Vue is replacing is this bottom section down here. That is possible because of nested routes, so let's go take a look at how to build this. To get started, we'll create a new BrowseParts component. So I've created that here in the parts folder. Let's go grab the contents from that from our GitHub repo. So in the src folder, in the parts folder, there is the BrowseParts.vue file. So let's grab that and paste that into here. So there's not a lot going on here. We have a bit of CSS, and then up here we have a menu for the different parts. And then I'm going to need a new page for each of the different types of parts, so I'm going to create a new RobotHeads.vue and a new RobotArms component, and a RobotTorsos, and finally a RobotBases. And then I'm going to get the contents for each of these out of our GitHub repo from their corresponding files also. So here's RobotArms, and I'll paste that into RobotArms here, RobotBases, paste that in here, and then RobotHeads, and finally, RobotTorsos. And each one of these is really quite similar where you have a title and a description of the type of parts that we're dealing with on this page. And then you can see here that we have a v-for that is looping over all of the, in this case, all of the arms and displaying the title and description, whereas here, we're looping over bases and torsos and heads. And then in each one, you can see that there is a data function that's returning the corresponding part, so we have a heads property on the RobotHeads component, and we have an arms property on the RobotArms component, etc. So now the trick is how do we get each one of these pages to be viewed on the browse page underneath this menu whenever we click each one of the links for the different part types? Well, it starts with adding another router-view. You'll remember that we have a router-view already in our App component, and so we're adding a second one here. And now we can add routes for each one of these individual pages, RobotArms, RobotBases, etc. This is going to consist of a parent route for the BrowseParts page and child routes for each of the PartTypes. So over here in our routes we're going to create a BrowseParts route, and it's important that this goes above the existing parts route. I'll explain that in a second here. So right here I'm going to create a new route, and it's going to have a path of /parts/browse and a name of BrowseParts. And the component is going to be the BrowseParts component, and we'll need to import that. And that has to have the .vue extension on it. Okay, now back to why it was important where this goes. Notice that this parts path where we're navigating to a particular part that it has parts followed by a partType. Vue has no idea when we type in browse here that we're not trying to type in a partType. And so if we would've put this down below, it would've got picked up first by this route, and so the order is important. Okay, so now we have this BrowseParts route, and we're going to need to add the child routes now for the individual part types. We do that by adding a children array like this. So the BrowseParts route now has child routes, and then we just need to specify the child route for each part type. So we're going to have BrowseHeads, and that's going to have a path of heads, and resolve to the component RobotHeads. And let's copy this and paste it for each of the types. And this one will be BrowseArms, path will be arms. Notice that this path is relative to the browse path, so the full path for arms is parts/browse/arms. And that will use the RobotArms component, and then we have BrowseTorsos, and the RobotTorsos component, and then finally BrowseBases. That's the bases path and the RobotBases component. So now let's go ahead and import each of those. Okay, so now we've created our nested routes, so we have our BrowseParts that has child routes for each one of the parts pages. Now all we have to do is create links for each one of these on the browse page. So back over here in the BrowseParts page, I just need to add a router-link around each one of these menu items. So, have a router-link, and then I'm going to use a to binding, and here I'm just going to specify the name of a route. And notice here that I can just provide the name of the child route. Okay, and let's fix this so that it wraps a little bit better. Okay, now we just want to do a similar thing around the rest of these. Okay, so we should be good to go. So we added a BrowseParts route, and it had child routes, and then we linked to each of the child routes here. Okay, so let's take a look. So instead of build here, we're going to navigate to parts/browse, and here we go. So now I can go to Heads and Arms and Torsos and Bases, and you can see that the data for the correct corresponding parts is loading down below. Awesome. So pretty simple to add child routes. Now let's go take a look at one more place where we can use a router-view, and that's with named views.
Using Named Views
We just looked at nested child routes; now let's take a look at named views. You can almost think of named views as sibling routes. Whereas nested routes have a parent-child relationship, named views allow you to have multiple router-views on a single component, and the route for the component specifies which two or more views to display. To demonstrate that, let's add a sidebar to our app that will show up here on the left of every page. And each route in our app will be able to specify a different sidebar to display. Right now in our App component we have this main section down here that as a router-view in it. Let's also add an aside that we'll show to the left of this main section. So we'll give that a class of aside. And we'll wrap both of these in a container div. And let's go ahead and add those classes, so we have a container, and it's going to be display: flex with a margin of 10px auto 0 auto. And we will set justify-content to center. And then we need an aside class, and it's going to have a 30-pixel padding a background-color that is gray, 100-pixel width, and a min-height of 300 pixels, which matches the min-height of our main content area. Okay, as we add these asides to our site, we're going to have to make a couple of other small CSS adjustments in order to fit those in. So on our main element we're going to remove the margin auto, and we're going to change its width to 964 pixels. And then we're going to change the width of the header to 1184 pixels. Okay, that should take care of the styling. So now up here in the aside, we'll go ahead and add another router-view. However, so that we can target this router-view, let's give it a name. So we're going to add a name attribute, and we will name it sidebar. And we can leave this one without a name. And by default, if we don't give it a name, it has the name default. Okay, now we need some content to display in this sidebar. So let's create a sidebars folder, and in here, I will create a SidebarStandard component, and I will also create a SidebarBuild component. So the build page will have its own sidebar. And our standard sidebar is just going to look like this. So if you're following along, go ahead and pause this, if you'd like to type it in. And the only content up here is just the text, Standard Sidebar, and we have a sidebar class here that is giving it a font size of 50 pixels and rotating it 90 degrees. And that's really all that's going on here. And then let's copy and paste this into the build sidebar, and we'll just change this to say Build Sidebar, and we will name it Build. And just to make it stand out a little bit, let's give it a green font color. Okay, now let's come over to our routes, and up here on the route for our HomePage, we're going to change this component property to components, plural, and then we'll change that from a simple property to a keyed object. And this will have two properties, default and sidebar. Remember that these names now correspond to the names of our router views. This one has a name of sidebar, and this one has a name of default. So when our home page is displayed, we want the default router-view to display our home page. And then we want the sidebar router-view to display our standard sidebar, and we'll need to import that up here. And then let's do the same thing for our build route, except that the default router-view will display the RobotBuilder, and in this case, we want to display the build sidebar, so let's import that also. Okay, cool. So now because our main App component has two router-views, we have to specify them both, or we can specify them both, and we can indicate in the route which content to display in each one. So let's go take a look at this. And it looks like we have an error in our CSS, so let's go back to our App component, and down here in the styles I'm missing a colon right here it looks like. Okay, let's check this out. Okay, there we go. So we are on the build page and notice that on the build route that I have my robot builder, and I have the green build sidebar. And yet, if I go home, I have a different sidebar. This is the standard sidebar. And notice that if I go to the parts/browse page, that there actually is no sidebar here, or I should say there is no content in that sidebar. So this gray background is coming from the aside in our App component, which exists right here. So we have the aside here, and that has a gray background. But there is no content in it because we didn't give it a router-view on that page. So our route is not specifying a component for the sidebar router-view, and so it's blank. So it is optional to provide data here, but this is how you use named views in order to specify different content for different parts of the same page. Next let's go take a look at how to get rid of this hash sign in our URLs.
Enabling HTML5 History Mode
All of our URLs so far have had this hashtag in them. That's because we're not yet using HTML5 history mode. So let's turn that on, and then we'll talk about a challenge that is presented by using it. So all we have to do to turn it on is to come over to our routes, and in our router configuration object we just need to set the mode to history. And that's it. Now if we come over here and start navigating around our site, you can see that there are no longer any hash signs in our URL. I really like this approach because the URLs look more professional, but there is one consequence to this that you need to be aware of. Imagine somebody comes to this part/heads/1 URL, and then they bookmark it. And then they come back, they close their browser, and they come back later, and the click on that bookmark. Or let's just say they open a new browser and they type in this URL. Well, this works locally in development because behind the scenes we're using web pack DevServer in order to serve this, and it can handle this. But imagine you have this running on your server, and a user types in this URL or clicks on a bookmark to this URL, it's going to request this full URL from your server, so it's going to request /parts/heads/1 from your server. And your server doesn't actually have a document at that location. It has an index.html and some other files that are used for serving up your Vue.js single-page application, and your Vue.js application knows how to serve this URL, but your server doesn't actually know how to use it. And so you would actually get a 404 when you try to run this if you have not configured your server correctly. And that is not the case if you're using hashtag routing because only the part before the hashtag is sent to the server. Everything after the hashtag is handled client side. And so this is a problem only when you turn on history mode routing. So if you don't configure your server correctly, all of these URLs that people deep-link to will 404 when they try to hit them. Basically, you need to set up your server to always return the main index.html file whenever you load any URL like this. The way to do this varies depending on which webserver you're using. So the Vue documentation contains some instructions for some popular webservers. I'll talk more about this later in the Deploying to Production module in the clip on handling deep linking. I'll demonstrate how to do this with Express. But just be aware that when you turn on the HTML5 history mode that this is a concern that you have to take care of on the server. Next, we're going to take a look at navigation guards and how we can prevent users from navigating to or away from certain pages.
Preventing Pages from Loading with Navigation Guards
Navigation guards allow you to either prevent a page from loading or prevent a user from leaving a page based on specific criteria. We'll take a look at both of these scenarios. Let's start with a beforeEnter guard. To demonstrate this, let's prevent this PartInfo page from loading if the id that's passed in is not numeric. You might think that we've already taken care of this when we added a prop validator that required the prop to be numeric, but this doesn't prevent the page from loading; it is more of a dev time warning that just throws errors in the console if the id isn't valid. We'd like to actually prevent navigation from happening. Notice that if we come over to our PartSelector and change id so that instead of passing in a valid id, it's just passing in a string. You can see that if I then try to navigate to this that it actually does navigate. You can see my URL has changed, but I just have a blank page. This is not ideal. What I'd like to do is to prevent the navigation from occurring completely. So let's go back here and change that back, and let's add a route guard to prevent this. So we can add that on a component or on a route. Let's see how to add one on a route. So we'll come over here, and right here in our PartInfo route, I'm going to add a beforeEnter function. And that takes three parameters, the route being navigated to, the route being navigated from, and a next function. And if we want to allow the navigation to proceed, we call this next function with a value of true. If we want to prevent the navigation, then we call it with false. So inside here, we want to prevent navigation if the id is not numeric. So to get the id, we can get that off of the to route, and then on there, there is a params object, and that should contain the id parameter. So basically, if this is a valid integer, we want to allow the route to proceed. So I'm going to set a variable where const isValidId equal to true if the number is a valid integer. So we can do that like this, Number.isInteger, and we cast this to a Number. And now isValidId will be true is the id param is a valid integer. So now we just need to call next, and we need to call it with false if the id is not a valid integer. So we can just pass in isValidId here, and then if that's false, it will prevent the navigation. Otherwise, it will allow it. So this should still be working normally now. I should be able to navigate to each of these parts, and that's working fine. But if we come back over to our PartSelector and change this again to a string and then try to navigate, you can see I'm clicking on these, but nothing is happening, and that's because our route guard is stepping in and preventing the navigation because the id is not valid. So before we forget, let's go undo this and put that back how it was. And you can see how creating this route guard was super easy, and it can be really handy. We could've defined this on the component instead, or we could even define it globally. In the next clip, we'll create a beforeLeave navigation guard to prevent the user from leaving a page, and we'll do that in the component so you can see how that works.
Preventing Navigation Away from Pages with Navigation Guards
Sometimes we want to prevent or warn a user before they leave a page. For example, imagine I spent some time configuring the perfect robot that I want here, and then I click the home link up here to navigate away. This could cause me to lose all of my progress on the robot I've built, so I may want to warn the user before they lose their progress. Let's create a beforeLeave route guard to handle this. This route guard is going to need some information from the component, so we'll create this one inside of the component rather than on the route like we did with the beforeEnter guard in the last clip. So over here in the RobotBuilder, we're going to add a beforeRouteLeave function right here, and notice that this has the word route in it. Well, it's supposed to. Now it has the word route in it. So this beforeRouteLeave, and we could add a beforeRouteEnter here, but you may remember that when we were adding this on a route as opposed to in a component, we actually didn't have the word route in here. So this is different when you're adding it on a component. So we want a beforeRouteLeave guard, and again, this has the to, from, and next parameters. And basically, we want to warn the user if they try to leave the page without adding the robot to the cart first. So let's add a property in our data function down here that will track whether they've added it to the cart. So we'll create an addedToCart property, and we will default that to false. And now up here in our route guard, we will check to see if that's true, so if this.addedToCart. And if it is true, we will allow them to leave the page. So we'll call next, passing in true, which will allow the navigation. Otherwise, we'll use a confirm dialog to ask them if they want to leave or not. So I'm going to set, I'm going to say const response equals, and then we're going to use confirm, and here we're going to say You have not added your robot to your cart, are you sure you want to leave? So if you're not familiar with confirm, it'll pop up a dialog kind of like an alert, but it has an OK and a cancel button. If they click OK, it returns true. Otherwise, it returns false. So if they click OK, then they're saying that they want to leave, and so we will pass response into next. And this is all we need. However, our linter is getting angry because it doesn't like us using confirm. Generally, alerts and confirms are not a great idea, but this is good for demoing this. And it's not bad to use it in the cases where it makes sense. And we're also getting an error because we're using a global function, so we're just going to disable those alerts here. So I'll just put in these two ESLint directives here, and now it's stopped complaining. There's just one last thing that we need to do here. Remember, we're checking to see if adddedToCart is true, and it's always going to be false right now because that's what we defaulted it to. So down in the addToCart function, once they've added to their cart, then we're going to set this.addedToCart = true. Okay, let's go check this out. So when I first load this page, if I try to navigate away, I should get a warning. There we go. You have not added your robot to your cart. Are you sure that you want to leave? And if I say Cancel, then it does not navigate away, but if I say OK, then it does navigate away, so that's working great. And if we go ahead and configure a robot and add it to our cart, then addedToCart is set to true, and now we can navigate away without a warning. Great, so now we know how to use route guards to prevent users from leaving a page.
In this module, we learned all about routing in Vue, including adding routing to our app, linking to routed pages, styling links based on the active route, navigating from code, working with route params, using nested routes, using named views, enabling HTML5 history mode, and preventing navigation with navigation guards. In the next module, we'll learn how to manage state with Vuex.
Managing State and Server Communication with Vuex
Welcome to this module on managing state and server communication with Vuex. In this module, we'll explore how to reliably handle data in a Vue application using Vuex. Vuex helps us by creating a central store where we can store and retrieve data in a way that works well with change detection across an entire Vue application. At the heart of Vuex state management is the Vuex store. The store provides not just a storage mechanism, but an interface for mutating data, requesting asynchronous mutations to data, and managing complex logic for fetching computed data with getters. The interface it provides ensures a shared data store for your application that will work well with Vue's change detection. At the heart of the Vuex store is the shared state tree. This is essentially a single object that contains all of the data for your entire application. When you first create your store, you provide an initial state object with reasonable defaults. This is important to Vue's change detection. We'll talk more about that later, but just know that the store contains a single state object that can be accessed throughout your application. Once the store is created, if you want to make changes to data in the store you do so by committing mutations. Mutations are synchronous and shouldn't be confused with actions, which are asynchronous. You might wonder what the purpose of a synchronous mutation is. Why not just change the state directly? Essentially, by telling the store to commit a mutation it keeps everything within the store consistent and working well with change detection. If you want to kick off an asynchronous call that will eventually mutate the store, such as making a call to an API to fetch data, that's where actions come in. Actions contain asynchronous calls using constructs like promises or async/await, which when finished commit mutations to the store. Actions and mutations work hand in hand to make asynchronous calls and then mutate the state. Sometimes when you want to access data from the store, you want to perform some complex calculation logic before returning the data. For example, you may have an array of unicorns in your store and perhaps it's common in your application to want to retrieve only unicorns who can heal fatal wounds. Well, all over your application you could retrieve the list of unicorns from your store and then filter it as needed. But this distributes and duplicates the logic all throughout your application. For purposes like this, Vuex provides getters. Getters are basically functions in your store that fetch data from the state and do some sort of calculation on or manipulation of the data before returning it. So these four items, state, mutations, actions, and getters make up a Vuex store. In this module, we'll cover all the fundamentals of working with Vuex including creating a Vuex store, changing state with mutations, retrieving data from the state, using getters to return calculated data from the state, using actions to work with asynchronous data, using modules to organize a store, and using built-in helpers to work with the store. This will really help us turn our Build-a-Bot app into a full-feature Vue application. So let's get started.
Creating a Vuex Store
The first need that we have in our app for state management is a place to store our cart. As it is right now, we've had to have our cart on our build page because we've had no way to manage state across pages. What we really want though is to create a cart page and then be able to add a robot to the cart from the build page and then have it show up on the cart page. Vuex will allow us to do that, but we'll first need to create a Vuex store. So let's create that. But first, we need to install Vuex. So, I'm going to stop my server here, and then I'm going to run npm install vuex. And then we'll save that. Now typically, this is all you'll do to install Vuex. But for this course, if you're following along, go ahead and specify version 3.0.1. This will just make sure that you don't have any inconsistencies with different Vuex versions in the future. So let's go ahead and run that. Alright, now let's go ahead and start our server up again. Okay, and now to create our Vuex store. So, back over in our project here let's create a new folder. We'll call that store. And then inside that store we'll create a new index.js file. Okay, now in our store file we're just going to go ahead and import Vue, and we're going to import Vuex. And now we just need to tell Vue to use Vuex like this. So now our Vue instance is aware that we're using Vuex. And then finally, we'll just export a new Vuex store like this. Alright. And we'll come back to this and add more to this shortly. But first we need to come back over to our main.js and configure our Vue instance to use our new store. So let's import that up here. And then we'll just use it down here like this. Okay, so now we have a new store that we can start working with. In the next clip, we'll see how to start adding data to our store using mutations.
Changing Vuex Store State with Mutations
Alright, now that we have a store let's start storing data in it. If we look at our RobotBuilder, we have this addToCart method. And right now, it's just storing the cart items in this array. We'd like to instead add that to our store so that it can be accessed by other components. Specifically, by a cart page component when we create it in the next clip. First off, our store needs a state object. The state object is where we'll store all of our data. So, we're going to create a cart property in our state object and default it to an empty array. This step is surprisingly important. You might think that we could just add this cart array to our state object somewhere else in our code later. Like, say, when we first mutate the state to add stuff to a cart. But it's important for Vue's change detection that you start with a default value for everything that you add to the store. This is because Vue's change detection works by tapping into the getters and setters of objects in the store. And if you don't add a default property to the state to start with Vue won't even notice changes when you add that data later. So, for everything that you're going to store in the state you need to add a default property for it here in the state object. Okay, so now that we have a default cart object, let's go ahead and add a mutation. So we'll just add a mutations object. And as we discussed previously, all changes to data in a store must happen through a mutation. You can't just access the store's state directly and start changing data. All changes must go through a mutation. So we're going to add an addRobotToCart method here. This addRobotToCart mutation will be called from our RobotBuilder component. And the first parameter of a mutation is the state. And then the remaining parameters, if any, are the data that you want to pass in to the mutation. So we're going to add a robot parameter. And now, in our function here we can say state.cart.push(robot). So we're accessing the state through the parameter that was passed in to the mutation. And then we're accessing the cart on the state, which is an empty array initially because we defaulted it up above. And then we're just going to push onto that array the robot that's passed in to the mutation. Cool. So this is all we need in order to add data to the store. Now let's go call this mutation. So back over here in our RobotBuilder, instead of pushing our robot onto this local cart array, we're going to access our store. And we can access it like this because our global view instance is aware of our store, and so it's made available to our components. So, on the store I'm going to call commit. And then the first thing that we pass in to commit is the name of the mutation in our store. So this was the name of this function here, addRobotToCart. So we're going to paste that in here. So we're going to commit the addRobotToCart mutation, and then the next parameter that we pass in is the data that we want to pass to that mutation. And so that's our robot, which is this expression here. Alright, now we can delete this line. Okay, so this is now adding a robot to our cart in our Vuex store using a mutation. And this happens when we click the Add to Cart button on our RobotBuilder. But we can't see this at work until we display the data from the store somewhere else. So in the next clip, were going to create a simple cart page and see how to retrieve the cart data from the store. And then you'll be able to see this working.
Retreiving Items from the Vuex Store
Okay, so we've used this addRobotToCart mutation in order to add our robot to the store. And now to demonstrate how to retrieve data from the store, let's create a cart page. So first, we'll create a cart folder and then we'll create a ShoppingCart component. And let's get the basic content for this from our GitHub repo. So, in the src cart folder we have this ShoppingCart.vue file. Let's go ahead and grab that and paste that into here. Okay, so nothing too fancy going on here. Just a bit of HTML here with an HTML table. And down here on line 16 we have this v-for that we're looping over to display each of the items in the cart. This may look familiar. We did the same thing on the RobotBuilder page. And we'll delete that soon. And you can see here that our v-for is looping over a cart array. But that doesn't exist yet in our component. We want to get that off of our new Vuex store. For that, we'll use a computed property. So down here, let's create a computed section. And in here we're going to create a computed property called cart. And then, although you can't mutate a Vuex store state directly, you can access the state directly to read from it. And so, we're just going to access the cart on the store like this. So we're just going to return this.$store.state.cart. And that's it. Now we are adding items to the cart in the Vuex store from our RobotBuilder and then accessing it here in our cart component. So let's just go add a route and link to our cart page so we can see this working. So over here in our routes, down at the bottom here, let's add a new route. And the path for that will just be /cart. And we will name it cart. And that will use the ShoppingCart component. Oops, and I got this in the wrong place. Let's just cut this here. This needs to go right here. Okay, so now we just need to import our ShoppingCart. So right here we will import ShoppingCart from cart/ShoppingCart.vue. Okay, so we have a route for it. Now let's go over to our App component and add a link to it. So let's copy this existing menu item. And in addition to this nav-item class let's add a cart class. We're going to go ahead and pull this nav-item over to the right-hand side so there's a cart link in the top right-hand corner of our application. So we'll use this cart class for that. And then we're going to just go ahead and link to a simple /cart route here. And then since that's just a URL string we don't need to bind. And then right here we'll just change this to Cart. Okay, cool. So now down here in our styles, next to our other nav-item, we will add a nav-item.cart class. And we'll position this relative with a margin-left: auto and a border-right of none. Alright, we're ready to go check this all out. so just as a reminder, from our last clip we added a robot to our store using the addRobotToCart mutation. And that exists in our store right here. And that mutation is just pushing the robot onto our cart in our store. And then in our ShoppingCart we are using a computed property in order to get the cart out of the store. So let's go check that out. So, here we have our cart link. So, let's go ahead and add this robot to a cart and then we'll also add our friendly robot to our cart. And then let's go ahead and click on the cart to go to the cart page. And there we go. So you can see that we have our large cyclops and friendly bot robots that we just added from our build page, and it's accessible to us here on our cart page. So just to make it a little bit more obvious that this is being added to our cart when we click this button, let's add a little indicator up here on our cart link that shows how many items are in the cart. So, back over in our App component, let's add a computed property here to also get our cart off of the store like we did in the cart page. So add a computed section that will have a cart and that will just return this.$store.state.cart. And then up here right next to our cart link inside the list item, we're going to add a new div. And it will have a class, and we'll call that class cart-items. And then we'll go ahead and close this div. And then right in here we're exposing cart from our component. So we'll just say cart.length here. Okay great. So this will display the number of items in the cart. And then we just need to add this cart-items class, so let's just come down to the bottom here. And I'm just going to go ahead and paste this in. And if you're following along, you can go ahead and type this in. And you can see we're just positioning this absolute in the top right-hand corner next to the cart link, and we are giving it a border radius so it will be like a round pill. And then just a background color of sea green. Okay, so let's go see how that's working. Here we go, we can see that we have two items in our cart. If we go to our cart, and here's our route cart that we added earlier, you can see that we do have two things in our cart. And we'll go back to Build, and let's add another item to our cart. And you can see immediately that changed to three. I'll click it again here, it'll change to four. So now we are accessing data from our store across three different components. It's being added from our RobotBuilder component and then it's being accessed by our cart and App components. Okay, that's awesome. So, let's just go clean up this extra cart stuff here that we're not using any more on our RobotBuilder page. So, here in our RobotBuilder, down here at the bottom we have this table data, table header, and cost classes that we can delete. And then up in the HTML, we have this whole cart section here that we can delete. Okay. So, that is gone now from our RobotBuilder page, and when we add items to our cart they are showing on our cart. So accessing data from the state is pretty simple. Next, we'll take a look at how to add getter functions to the store so that we can compute derived state in the store when necessary.
Using Vuex Getters to Return Calculated Data
So far, we've been accessing state on the store directly just like this. Sometimes you want to have a computed property on the state that returns a value that is a calculation or derived value of some sort based on data that's in the state. This is what Vuex getters are for. To explore this, let's create a getter on our store that will return only items from the cart that are on sale. This is going to be based on this cart value from the state, but will basically filter the cart for just the items that are on sale. We could just do this in the component, but sometimes you want to reuse logic like this, and encapsulating logic like this in the store can be helpful. So first, let's go back over to our shopping cart and we will duplicate our cart here. So we'll grab this header and this table and we'll just duplicate it here. And then let's change this to h2. And we'll change this to say You saved money on these robots. And then let's just add a class on this h2 called saleItems. And then that class just looks like this. So it'll have a margin on the top to separate it from the normal part. And we'll give it a font size of 18px. And we'll give it a red color since it's sale items. Okay, now back up in our HTML, instead of looping over the cart we're going to loop over a new computed property and that will be called cartSaleItems. Okay. And then let's add a computed property down here for that. So this will be cartSaleItems. And then again, we could just grab the cart from the store and then filter it right here, but we want to do this in a getter in the store so it's reusable. So let's go back over to the store and we'll add a new section for getters. And then we'll add our cartSaleItems getter. And the first parameter for getters is the state. And now we can do whatever calculations from the state that we want to do here. So, we'll go ahead and return state.cart.filter. And then we'll filter that for just the items where the head is on sale. Of course, a robot would be on sale if other parts are on sale, but this is simple and you get the idea. You can go ahead and enhance this if you'd like. So now you can see that we're just filtering for any items where the robot's head is on sale. And this calculation is based on data that's in the state and then exposed as a cartSaleItems getter. And now this logic is nicely encapsulated here. So now the only question is how do we access it? Well that's pretty simple. So back over here in our cart where we created our computed property we can access it just like we do with the state, this.$store. But instead of saying .state we'll say .getters. And we'll ask for cartSaleItems. Okay, let's go see how that's working. So, here's our cart and here's our new section. Although it is not styled correctly, so let's come back over here and see what we did. Oh, here we go. This should be class. You probably saw that when I first did it and I was probably driving you nuts. But now that that's styled let's go take a look. Okay, there we go. So that's working now. So let's go ahead and add this robot that is not on sale. Now you can see we now have one item in our cart. And now let's go ahead and add this one that's on sale. And now let's go take a look at our cart. There we go. So you can see that we have two items in our cart and only one of them is on sale. So using getters is really easy and a nice way to encapsulate any calculation logic into your store. Next, we'll start exploring how to access data from a remote API. And that get a little bit more interesting due to the asynchronous nature of HTTP calls.
Using Actions to Work with APIs and Asynchronous Data
Everything that we've done so far with Vuex has been synchronous, simply modifying and reading from the local state. But of course, most web applications today deal with asynchronous HTTP calls to fetch and update data from a remote API. With Vuex we can accomplish that with actions. But first, we'll need an API to work with. To make this easy I've already created this API over here. So, let's clone this by copying the remote URL here. And then over here in our bash console I'm going to open a new tab and change directory to where I keep my projects. And then I'm going to go ahead and git clone that. And if you're using this same bash console, you paste by right-clicking. So I'll paste my remote URL in here and go ahead and clone that. Okay, so let's change directory into that new directory. And then I'm just going to do a npm install. Alright, now we can go ahead and start up this server by just saying npm start. Okay, now you can see we have a server listening at port 8081. So if I come back over to my browser and go to localhost port 8081 then I have an endpoint right here that I can hit, api/part. And there we go. So you can see now that this parts endpoint on our new API is returning all of our robot parts data that we previously have had hard decoded in our application. And now it's time to finally start getting this from the real API, so we'll do that with a Vuex action. So, back over in our store let's add an action section. And in here we'll create our first action. We'll call it getParts. And the first parameter to an action function is a context object. The context exposes several items for working with the state. So instead of passing in this we could actually deconstruct it to grab what we want. It provides things like state, getters, a commit function, and a dispatch function. But all we really need right now is the commit function so that's all I'm going to deconstruct out of the context object. The commit function is how we call mutations on the store. You may recall that we're already using the commit function in the RobotBuilder currently to call the addRobotToCart mutation. So, we'll use this commit function here in just a minute. First, we need a way to make HTTP calls. Vue doesn't provide any of its own custom Ajax or data access methods or libraries. Instead, they allow you to use whatever you want to make HTTP calls. So we're going to use Axios, which means we need to install it. So, back over in our console, on the tab that's running our Build-a-Bot app, let's go ahead and stop it. And then I'm going to npm install axios. And we'll save that. Okay, let's go ahead and start our server back up. And then back over in our app, we're going to go ahead and import Axios. And now down in our getParts function I can use Axios to get data. So I'm going to say axios.get. And then the axios.get function takes a URL for the endpoint that you want to hit. But what can we put here? Our API endpoint that we created is right here. So we could put http://localhost:8081/api/parts. But, of course, there are a couple of problems with this. First of all, we're going to get CORS issues if we do this because we're accessing a different port than what our application was running under. But also, local host is not going to work in production. And in production, this is typically solved by hosting your application and your API on the same domain and port. But we don't have that luxury in development without some effort. That's not a problem though because Vue provides a way for us to configure a built-in proxy so that we can proxy the API through our dev server that our Vue app is being served from. This will solve our issues locally and enable us to just use a relative URL like this. So, in order for this to work we need to configure the Vue proxy to proxy our API. We can do that by creating a file at the root of our project named vue.config.js. So I'm just going to right-click here and create a new file, vue.config.js. And then in here we just need to configure the proxy like this. So, we'll do a module.exports and we'll just export an object from here. And in here we'll just provide a devServer configuration. And we'll configure the proxy. And we want our proxy to be running at /api. So now we can configure this API to be proxied to where our server is running. So, the target for API is going to be http://localhost:8081. And then we just specify changeOrigin, and we'll set that to true. So this is actually webpack.config. And Vue, behind the scenes, is using the webpack-dev-server. So that's why we're using webpack.config here. So this is how you configure the webpack-dev-server. So now, we have a proxy running at /api that will proxy all requests from /api to http://localhost, spelled correctly, port 8081. So now this api/parts URL will get proxied to our API that's running on port 8081. So now that that's set up we can continue working on our action. So, when this axios call finishes we want to commit the data to our store. But before we can do that we need to add a default value to our state. So up here I'm going to set parts by default to just null. Remember, anything that you add to your state you need to add here at a default value for it to start with. And, again, when this axios call finishes we want to mutate our state. But we can't do that directly. We do that through mutations. So let's add a mutation next for updateParts. And that takes in state and our parts. And then we'll just set state.parts equal to the parts being passed in here. And I missed a parenthesis up here. Okay. And then notice something interesting with this mutation. ESLint is saying that we're violating the no-param-reassign rule. This rule says that you should never mutate a parameter that is passed in to a function. This helps avoid mutation-related bugs. However, Vue's whole change detection relies on mutations within the store. It's actually intended that you will mutate this state object when it's passed in. So for that reason, let's update our ESLint RC file to allow this. So, here we have our eslintrc.js file. And then right here in rules all we have to do is add another rule for no-param-reassign. And we'll set that to 0. And now ESLint should stop warning us. Yep, sure enough. Looks good now. Okay, so finally back to our action here. When our axios call finishes we want to then commit the data to the store. So, we'll take in our result here from our axios call and then we will call commit that we've de-structured there from our parameters. And we want to commit the updateParts mutation. And then we'll pass in the data that we get back from our API calls. So that will be results.data. So Axios returns a result object and it has a data object that has the payload that was returned. And this should just be result. Okay, cool. And then in the event that an error occurs I'll catch it and then I'll just pass that to console.error. Of course, you might want to do something more useful than this in a real application, but for now I'll just log any errors to the console. Okay, so with all of this in place we use Axios to call our API. And then we commit the returned data to our store through the updateParts mutation. Okay, cool. So now wherever we need this data, we need to first dispatch to this action and then get the parts from the store instead of from the hard-coded parts.json file we're currently using. So, let's start in the RobotBuilder. So, first up here we are importing availableParts from this parts file. So let's delete that. And then in order to kick of the axios call we need to dispatch an action. And then we want to dispatch that action as soon as this component is created. So, let's go ahead and add a created hook. And then in here I'm going to access the store with this.$store. And then there's a new function we haven't used on the store yet and that's called dispatch. And we're just going to tell it to dispatch the getParts action. So you use commit to commit mutations and you use dispatch to kick off actions. And I'm using the wrong get here. So that's getParts. Okay, so that will kick off our axios action, which is asynchronous. And then all we need to do is create a computed property that will return the parts from the store when the axios call is finished. So down here in computed, let's add a new computed property called availableParts. And this will just return this.$store.state.parts. Okay, and then this is just complaining because we have availableParts declared in two places. We don't need it in data anymore since it's now a computed prop. So our created lifecycle hook now dispatches the axios call and then once that's available this computed prop will get updated whenever that data returns. And now up here in our template, in all these places that we're calling availableParts will now get availableParts from the computed property. Okay, so this is pretty much it. We've dispatched our call and then we're getting the data off the store after the action in the store has committed the data via a mutation. But we still have one problem. So if we come over here, you can see our build page is not rendering. And if we look in here we have errors. Notice that it says here Cannot read property "heads" of null. So back over here, That's right here. So it's saying that it cannot read property heads of null. So availableParts is null. And that's going to happen everywhere below where we're using availableParts also. Well, that's because over in our store here our default value for parts is null. And so, because our action is asynchronous, when the component first loads the store returns null for availableParts or for this parts property. And we could fix this in two different ways. We could provide an empty object as the default value in our store or we could prevent the RobotBuilder page from rendering until the availableParts is populated. I'm going to go with the later solution because the first solution would have some other cascading problems with an empty object. So, let's just use a v-if on this outer div here. And we will just say v-if, and we will only show this if availableParts is truthy. So this won't render if it's null. So this should be working now. If we come back over here and refresh. Okay, and now we're getting a 404 error, but that's just because when we created our proxy, updated our proxy config, we needed to restart our server. So I'm going to stop my server and start it again. And now let's go ahead and refresh here. There we go. No more errors here. And you can see that we are getting our robot parts just fine. And now it's coming from an API call. So if I look in Network over here and refresh this page, you can see that I made this call to port 8080/api/parts and that got rewritten by our proxy server to port 8081. Alright, awesome. So we have now dispatched an action, which kicked off an asynchronous API call and then we're getting that data. Now we just need to update the other places in our app where we were importing that old parts.json file and use the action instead. So, let's start with these four files in the parts folder. So this is for the part info pages that display information about each of the parts. And these four files are very similar. And they're all going to need to dispatch the getParts action. And then they'll each need to have a computer prop to get the parts off the store. And since that will be repeated across all four of these components, let's create a mixin to do it and then we'll reuse it. So, we'll create a new file and we'll name it get-parts-mixin.js. And then this will just export a component options object. And in here we'll add a created hook. And this will dispatch the getParts action. And then we just want a computed prop. And that computed prop will be called parts. And it will return this.$store.state.parts. And this should actually be a colon here. There we go. And actually, I'd like this computed prop to do a little bit more than just this. I'd like to make it so that we don't have to do a v-if check in all of our components while we wait for the parts action to complete. So, we'll return the parts unless it's null. In which case, we'll return a default object. So, that default object will just look like this. It'll have heads, which is an empty array, arms, and torsos, and bases. Okay, so this is dispatching the getParts action and then returning parts from the store or a reasonable default object if the parts are not in the store. Okay, so let's just go use that mixin in each of these components. So, here in the robot arms we'll get rid of this import. And we can delete our data function since our mixin is going to handle that via computed props. And let's go ahead and import our mixin, getPartsMixin from ./get-parts-mixin. Okay. And then our mixin we use down here by specifying mixins and passing that in in the array. Okay, and then up here we just need our v-for to loop over parts.arms instead of just arms. Okay, so now let's do the same thing for RobotBases. Specify the mixin. And then up here this will be parts.bases. And then same thing here in the RobotHeads. And update the for loop. And then finally on RobotTorsos. Update the for loop. There we go. Okay, so now all of these parts components are updated, and you can see how helpful a mixin was in this case. It's made these four components incredibly simple. And actually, we can use that mixin in one more place. This PartInfo component also needs parts. So we can get rid of this import here and import the mixin instead and add mixins. And then down here we just need to change this to this.parts since that's a computer prop now from our mixin. Okay, and that wraps up our changes. So now our whole application is getting parts data from the API via a Vuex action. And that means that we don't need this data folder anymore. Okay, cool. This should all be working now so let's go check it out. So our RobotBuilder page is working. And if I click on one of these parts you can see that this is working looking up the part information. And if I go to the parts/browse then you can see that I'm getting the heads information, arms information, torsos, and bases. Awesome. So that was a fair amount of work to retrofit our application to use the new action and API. But the amount of work that was actually involved in creating the action on our store, which we can see again over here, was not that much work. And accessing it in each of the components wasn't that much work either. Just felt like a lot of work because we were retrofitting a bunch of places. But you can see that creating actions and making asynchronous calls is actually pretty straight forward. So, in the next clip we'll take a look at how to use actions to save data to our API.
Using Actions to Save Data to an API
We just learned how to use actions to retrieve data. Now let's look at using actions to save data to an API. The concept is fairly similar. We'll start by creating a new action in our store and we'll call it the addRobotToCart action. And again, we'll need the commit function. And then we'll also want to de-structure the state off of our contacts parameter. We'll use this here in a minute. And then unlike our getParts action, which doesn't take any other parameters, we're going to pass in a robot to our addRobotToCart action so that we can pass that along to our axios call and add it to our state. Okay, so our API takes in a fully populated cart. So, we're going to need to get the current cart off of the state, append this one, and then pass it to our API endpoint. So let's create a new cart variable, and we'll set that to an array, which contains all of the items from the old cart plus our new robot. Okay, so this is what our new cart looks like. Now let's save it to the API with an axios.post. And that hits the api/cart endpoint. And we are going to pass in our new cart. So we're posting to the api/cart endpoint and then we're passing the cart object as the body. And when this post finishes I want to update my local Vuex state to include the robot that was saved, but I don't want to do that until the axios.post is successful. And we need to add it to our local state. Otherwise, we'll save it to the server, but our app won't reflect that we have a new item in our cart. So, let's go ahead and add a .then here. So axios.post returns a promise. And then we basically want to just call our addToCart mutation to add this robot to the cart. If you remember, we do that with commit. So, I'm going to create a new function here. And this function is just going to call commit and commit an addRobotToCart mutation. And that needs the robot to be added. Okay, so now we have an action that will save our robot to the cart on the server and then also insert it into the cart on our local store after that succeeds. Now we just need to call this action. So we'll do that over here in our RobotBuilder. So we have our addToCart function here. And right now, the addToCart method is just calling the commit function on the store to commit a mutation. And we want to use our action now. So instead of commit we'll use dispatch. And we'll now dispatch the addRobotToCart action instead of calling the mutation. And the rest of this is just the same. So we're calling the addRobotToCart action and we're passing in the robot to add. So basically, the only difference between calling a mutation and an action is that you commit mutations whereas you dispatch actions. Okay, let's go check out how our app is working now. So, if you watch this indicator up here on the cart, when I click Add to Cart you'll notice that there is now a delay as it makes the call to the server. And then only when it succeeds do we add it to our cart locally. And then if I open my dev tools and on the Network tab I click the Add to Cart button again then you can see that we're calling the api/cart endpoint. And down here, you can see that for the payload we are passing along the cart array. Excellent. We're now using an action to save data to our API. In the next clip, we'll see how to tap in to the promises that are returned by actions.
Returning Promises from Actions
If we look at our addToCart method in our RobotBuilder, we're dispatching an action, but we have no idea here when it finishes. What I would like to do is redirect to the cart page after adding the robot to the cart succeeds. But how can I know when the action has completed successfully? Well, over here in the store in our addRobotToCart action, all we have to do is return this axios.post call. Axios.post returns a promise and we can return that from our action. And now that this returns a promise over here, I can add a .then to my dispatch call. And so when that action completes successfully then I'm going to call this.$router.push. And I'm just going to navigate to the cart URL. And now when I add an item to my cart you can see that there's a little bit of a delay as it goes out to the server. And then when it succeeds it navigates to my cart page. So, even though we're calling dispatch here, we're not actually calling our addRobotToCart function directly, the promise return from our action still gets returned to us from this dispatch call, making it easy to respond to the asynchronous event when it finishes or even to catch errors when it fails. So that was easy. Next, we'll take a look at how to organize our store into modules.
Organizing the Store with Modules
So far, we've been adding all of our actions, mutations, getters, and state objects to the single store file. This has been fine for this small application, but imagine a larger application with dozens or even hundreds or thousands of items in it. A single file would quickly become unmanageable. This is where modules come in. They allow us to break up a single store into multiple modules for maintainability. So let's imagine that our application had need for some other actions in here related to users, such as a sign-in action for example. We could change our store to have two modules, a robots module and a users module. So let's create a robots module, that's easy. First we'll create a modules folder inside our store folder. And then we'll create a new file for our module. I'll call it robots.js. And then I'm going to move all of this that is all related to robots, starting with state all the way down to my getters. I'm going to cut that out of here. And then over here I'll export a different object and I'll paste that in here. And we'll need our axios import. And we won't need it over here anymore so I'll move it from here and import it here. And then back in my store file I'll import my new robots module. So import robotsModule from modules/robots. And then finally, in my configuration object down here for my store, I'll just add a modules section. And then you just add a property for each of the modules. So I'm going to create a robots module and that will use my new robotsModule. Okay, we'll come back and add a users module in just a sec. But for now, let's go take a look at our site. Okay, this is not looking great. So, if we look at our console you can see that we are getting this error, Cannot read property "length" of undefined. And that's happening in our App component. So, if we come back over here and look at our App component, we come up to the template up here. That error is occurring right here where we're trying to show the number of items in the cart. So the problem is that the cart is undefined here. And if we go down to our computer property for that you can see that we're getting it from this.$store.state.cart. But we just added a robots module. And if we go look at that robots module you can see that the cart is in the robots module state, not in our root store state. And so, over here we need to access our cart with state.robots.cart. Notice that robots matches the name that we gave our module over here on line 10. Okay, so that should fix our error. And you see our site's rendering. If I refresh we have no errors here. So we're now working fine again. And we are accessing the cart through our robots module. So I want to call out something important here and that is that we still have a single state tree or a single state object. So we still have a single state object and on that state object we have a robots module. Okay, so we've fixed this here, but there are actually several places in our app that we need to fix this. And right now, everything is in the robots module. So I'm going to undo this change here, and then I'm going to do a global search and replace, which you can do by hitting Ctrl+Shift+H if you're using VS code. And I'm going to replace this.$store.state with this.$store.state.robots. And that should be good. Just for a little more safety I'm going to put an extra dot here on the end. And then I'm going to go ahead and replace everything here. Oh, you have to execute the search first and then I will replace everything. There we go. So that updated four occurrences across four files. So now everything that was accessing state is now accessing the robots state, which is where all of our state currently is. Okay, so now everywhere on our site should be working. We get our robot parts and we can add to the cart. And we're getting our cart data here. And that data's all coming through the state in our robots module. Now, you might be wondering about the fact that we have fixed everywhere where we're accessing state, but we didn't go in and add robots to the places where we're calling actions and mutations and getters. We'll talk about that more in the next module, but just realize that state is a little bit different than those other pieces of the store when it comes to modules. Okay, so just keep that in mind. Now, back over in our store we currently have a single robots module. In order to see just what it looks like if you have two modules and also to set us up for working with namespaced modules in our next clip, let's add a second module. We'll call that users. So, in our --- I'm going to hit Ctrl+Shift+E to get my explorer back here. And then in my modules folder I'm going to create a new file and that will be users.js. And then we'll get the contents of this from our GitHub repo. So over here in the repo I'm going to click on src and then store/modules and users. And let's grab the contents of that and paste it in here. Alright, now we have a users module, and notice that it has a sign-in action. And the sign-in action commits the updateCurrentUser mutation. And that mutation just puts the user returned from the axios call on the state. By the way, it's worth mentioning that with async/await it's expected that in the future mutations and actions will be combined into a single action. But for now, they're separate. Now, don't worry too much about what's happening in this users module. We just needed a couple of modules to demonstrate a few things. So let's go add this to the store. So back here in our store we will import it. We'll just copy this one and we will import the usersModule from modules/users. And then we can just add that down here. So we'll create a users module and paste in usersModule here. Okay, and this is it. So we now have a store with multiple modules in it. And in the next clip we're going to take a look at namespacing our modules, which will separate these two even further from each other.
There are a few nuances to working with modules in the store that we should cover. First of all, let's take a look at how actions are dispatched. If we take a look at the RobotBuilder component, you can see that when we add an item to the cart we dispatch this addRobotToCart method. And you can see in the app that this is working. So if I click Add to Cart here it does indeed get added to the cart. But if you look at our store we have two modules, right? And they each have their own set of actions. So robots has a set of actions here and users has a set of actions here. And if you think about this for a moment you might wonder how in the world is this dispatch working because we never told it which module to dispatch this action to. We updated all of our state calls to get data from the robots module, but we didn't update any of our dispatch calls. How in the world did Vue know that the robots module should be the one to handle this? Well, the answer is Vue didn't know. As we have it configured now, both modules will respond to this dispatched action as long as they have an action that corresponds to this addRobotToCart name. We can demonstrate this by adding an action to the users module. So right here I'm going to create an addRobotToCart method. And we'll just have that log to the console so that we can see that it's getting called. So I'll add that to console.log, and I'm just going to say Users addRobotToCart called. Alright, so now if we come back over here to our builder and open our console, now you can see that if I click this Add to Cart button it did get added to the cart. So, it had to have gone through the robots module to do that. But it also logged this Users addRobotToCart call. So you might be asking yourself what good are Vuex modules then if it doesn't keep things separated? Especially in a larger application, you could easily make the mistake of giving two actions in different modules the same name and not even realize it. Well, don't worry. Vue can keep them separated if we tell it to. That's where namespaced modules come into play. So, over here in our robots module I am going to set it to be a namespaced module by just setting here namespaced to true. So now over here in the robots module I need to add the namespace to the action name that I'm dispatching. So this will be robots/addRobotToCart. I need to change this for all of my actions throughout the app. So, let's do this in a global search and replace again. So I'll hit Ctrl+Shift+H. And again, since the robots module is the only one that we're using, that we're dispatching actions to, I can just update all of my dispatches. So this is going to replace this.$store.dispatch with an apostrophe. We'll replace that with this.$store.dispatch with an apostrophe and then we will prefix everything with robots/. Make sure you get all of the periods and apostrophes and everything here correctly. And then I'll execute that search and do that replace. And that updated this one extra so I'll undo that. Okay, and then let's just make sure we do a Save All after that. Alright, now if we go back to our app here, and now if we go add another robot to our cart you can see it got added to the cart but it did not log the message from the users module. So now we are successfully only dispatching items from the namespaced robots module. Okay, so it's important to note here, I mentioned this earlier and I'll probably mention it again, so the state on a module is always namespaced whether the module is namespaced or not. You may remember that when we first added our robots module we had to immediately go and add robots. to everywhere where we were accessing the state. And that was before we namespaced this module. So state is always namespaced even if your module is not set to namespaced. But mutations, actions, and getters are only namespaced if you set namespaced to true. That caught me up a few times when I was first learning Vue so that's important to note. Okay, so now we know how to create a namespace module and who accessed the actions on namespaced modules. Next let's take a look at how to access namespaced getters.
Accessing Namespaced Getters
If you remember on our cart page, we're displaying down here any robots that are currently on sale. But that's actually broken currently. If we go add a robot that's on sale, like this friendly robot, to our cart and then come over here, you can see that it's not showing up down here. This is because we namespaced our robots module. But we never actually updated the way that we're accessing the getter right here. And if you look at the way we're accessing state up here you might think that we would access it the same way. And we could just say robots right here. But getters.robots is not actually defined. You have to actually access getters using this notation here where we use a string to access the property and then we preface the name of the getter with robots/. So now if we go take a look at our cart you can see that we are getting our sale robot down here. And if I add this one that's not on sale then it is not down here. So our getter is working now. So, a slightly different syntax for accessing getters in namespace modules. Next, let's take a closer look at how our Vuex state is structured and used inside of our modules when using namespace modules.
Understanding Global and Namespaced State
We've discussed how we have one single state tree or state object, but to remind ourselves of how that state is structured let's add a couple of variables to our state. First, we'll add a state object on our root store config. So, we'll add state here and then we're going to put a variable in here call foo. And it will just be set to root-foo. Okay, so we now have a foo property in our store. And this is called the root state because it's not in a namespaced module. So it's part of the root state. And actually, any state that you add to non-namespaced modules is also part of the root state. Now let's add this same property to the other modules. So, we'll add a foo property here, but it will be set to robots-foo. And then we'll do the same thing in the users module. This will be called users-foo. Okay, and then let's do something similar with a getter at each level. So in our root store let's add a getter. So we need a getters section. And we'll create a foo getter. And we will return root-getter/ and then the value from the state property, state.foo. So this is just prepending root-getter to that string and returning it. Now let's do the same thing for getters in the robots module. So down here we'll add a foo-getter, but it will prepend robots-getter. And then we'll do the same thing for users. So right here in getters we'll add a foo getter and it will prepend users-getter. Okay, so now we have a foo property on the state at each level and a foo-getter at each level. This will help us demonstrate some interesting things about how state works across different modules. So, let's go print those out in our App component here, and we'll put it right here at the top. So I'm just going to print each of these out like this. So you can see that here I'm printing out each of the foo properties from the state. And then down here I'm going to print out each of the foo getters from the state in each of the modules. So we'll need some computed props for that. So down here in computed I'm going to create a rootFoo computed property. And that will return this.$state.foo. And then we'll do the same thing for each of the modules. So this will be robotsFoo, and this will return this.$state.robots.foo. And the same thing for users. Okay, and now I just need computed props for each of the getters. So I'm going to have a rootGetterFoo going to return this.$store.getters.foo. And then the same thing for robots. But it will return this.$store.getters and we need the string syntax robots/foo. And then same thing for users. And I mispelled that here. Oh, and it looks like I have some typos up here. This should be store.state. I have that in a couple places here. Okay, so let's go take a look at this, see what it looks like. Okay. So there's something interesting at the top here in these first three variables that we're printing out. So these are the items that we got directly off of state. Notice that users does indeed have its own namespaced state even though it is not a namespace module. So, in our App component we're accessing it with this.$store.state.users.foo, which is a thing. So, even though users is not a namespace module it does have its own namespaced state. So again, just a reminder that state is always namespaced in a module even if the module itself is not set to be namespaced. And then another point to make is that root state is not inherited by modules. And so if I get rid of this foo property here and save it on the users module and then come back and look at this it is empty. So, this.$store.state.users.foo is not set to anything. So it is not inherited from the root state. Now let's go ahead and put that back. Okay, so let's come back and look at this. And let's look at the getters. So first of all, notice that the users getter did not return anything. And that's because in our App module, in the computed prop, we were accessing it with this.$store.getters users/foo. And so, we're trying to access a namespaced getter on a module that is not namespaced. So that's not too surprising that that's not showing up. But what might be surprising is if we look at the console here you can see that we're getting this error, duplicate getter key: foo. This is because the users module and the root store are sharing the same namespace. And yet over here you can see that we've created, in our users module, a foo getter. And we also have, on our root store, a foo getter. And so, we're getting an error because we've created a getter with the same name in two different places that share the same namespace, specifically the root namespace. So let's see what happens if we get rid of this getter that's at the root level. And so now if we go back to here it shouldn't be too surprising that when we called the root getter we got the users getter value. And so, in our computed property in our App module, the root getter is calling this.$store.getters.foo. And in the users computed property we're calling getters users/foo. And we've already talked about that this is not a thing. This is not defined. So it's interesting that the root getter is getting the value from the users module. And that's because this is not a namespaced module and so its getters are shared with the root store. And so, the root store doesn't have a getter, but we added one here and technically we're adding that to the root namespace. And so when we requested the foo getter from the root namespace it retrieved this one even though it was defined in the users module. Okay, now let's explore something else with this getter. So, I'm going to remove this foo property off of our users state and then I'm going to come back over here. And let's see what we're getting. Okay, so now when we call the foo getter we're getting users-getter/undefined. And so that's kind of interesting because we've already established that we would get into this getter here because we don't have one defined on the root and they're shared. But why is state.foo undefined? If the users module is not namespaced why are we not getting the value here from state.foo? Well, remember, state is always namespaced. And the state that's passed in to getters and other places like actions and mutations within the module, it is always that local module's state. And so this is trying to get state.foo off of our users module and there is nothing. And so that is printing undefined. So that's the other important point is that the state that's passed in to mutations, getters, and actions is always the local state from that module. But what if we did want to access the root state from this getter? Well, that's available to us as the third parameter in our getter function. So, the second parameter is the getters and the third parameter is rootState. So, in a non-namespace module this is all the getters in the entire store including getters from namespace modules. In a namespace module, these getters are only the local getters in case you need access to them inside another getter. And then we have the rootState. So let's use that here. Okay, and now if we go take a look at this, there we go. Perfect. So we are now getting --- From the getter inside the users modules we are now getting the value from the rootState. And that remains true even if we come back over here and put a foo property on here set to users-foo. Over here, you can see we're still getting root-foo and that's because we're using the rootState here. And everything that we've said here about getters is also true of actions and mutations except that rootState is not actually available to mutations. So it is not passed in to mutation functions. But in an action, I can get access to rootState by de-structuring it just like we do everything else off of the context object. And then you can access rootState here. But we don't have a need for that so we'll remove that. So as a reminder, rootState is not actually available to mutations. There's been some discussion about whether it should be added here, but for now if you need access to the rootState in a mutation do it in an action, not the mutation. Hopefully this has helped create a little bit more clarity around some of the nuances with what data and methods exist where in a namespaced and non-namespaced modules. Next, we're going to take a look at some helper functions that Vuex provides to make it a little less verbose to access items from the store in our components.
Using the Vuex MapState Helper
If we take a look at the methods that we just added on our app component you can see that there's a lot of boilerplate here just to get these three props off of state. Vuex provides a mapState helper to simplify this. So let's import that helper. So up here I'm just going to import mapState from Vuex. And then we can just use mapState right down here in the computed section. So, the most simple form of mapState looks like this. So you spread the results of calling the mapState function and then we pass along to that an array of strings. So this is basically saying take the foo property off of the rootState and provide it as a computed prop with the same name. So it basically replaces this syntax down here except that we're actually renaming this property to rootFoo. If you want to give your computed prop a different name than what it's called in the state, like rootFoo in this case, then you need to use an object syntax instead of the simple array syntax. And if I had additional props here with the array syntax I could just add the other props here. But if I want to rename it I need to use a different syntax. So instead of an array I'm going to use an object, and I'm going to set to rootFoo on that object to foo. So this is a short-hand syntax that now is exactly equivalent to the syntax below. So we can remove this. We'll just need a comma here. Okay, but what about this robot's foo? We would like to be able to just say robotsFoo here, but what would we pass in here since this is coming from the robots module? Unfortunately, this requires a little bit more verbose syntax. So, we set this to a function and then we can pull whatever off the state we want to like this. And then we can delete this right here. So now we have our mapState function boiled down to just a single line, whereas it was six lines before. Granted this is wrapping and, frankly, we should probably clean this up by moving this down here like this. So that syntax is a little bit more verbose when you are working with modules, but you can see this is still a lot cleaner than it was when it was two separate properties and we were using six lines. But actually, there's another syntax that we can use when working with modules. So let's use this other method for this usersFoo property. So I'm just going to use a second mapState call here, but this time the first parameter is going to be the name of the module. So now this mapState function will work with anything on the users module. And since we're renaming foo to usersFoo then I can just pass in an object, usersFoo, and set that to foo. So this syntax is now equivalent to this code here. So we can delete this code. So that's a pretty clean syntax that we could use. And we could use that up here for robotsFoo also, but we'll leave this how it is so that you can see the different examples. And actually, this is plural, users. However, we have a small problem here. If we go over here and look at our site, you can see usersFoo is not rendering. And if I look at my console you can see module namespace not found in mapState, and it's referencing our users module. Well, the problem is, and I think this is kind of unfortunate, but the problem is that this syntax only works with namespaced modules. And so, we're actually going to be forced to use this syntax for the usersFoo property. So I'll use the function syntax state.users.foo. But we could use this syntax for robots since it is a namespace module. Now if we go take a look at this, now everything is rendering correctly. And if I refresh we don't have any errors here. So just keep in mind that this syntax can only be used for namespaced modules. Okay, cool. So, just like there is a mapState function there's also a mapGetters helper. So let's go take a look at that next.
Using the Vuex MapGetters Helper
Using mapGetters is identical to using mapState. So let's go ahead and import that up here also. And then we can use it in the exact same way. So down here I'm going to spread mapGetters. And then for the rootGetter I will just use the object syntax because I want to rename it to rootGetterFoo. And that is going to grab the foo getter off of the root store. And then I will use the module syntax for the robotsGetter like this. And then we can delete these two functions here. And actually, this usersGetterFoo is leftover code from an earlier demo. We're not actually using that so we can delete that there. And we can delete the binding for it up here also. Okay. And that should be working fine. There we go. The getters are now working just as we'd expect. And again, this code is a lot less verbose than it was before, and so these helpers are nice. And next, we'll do the same thing for actions. Although mapActions is a little bit different so let's take a look at that.
Using the Vuex MapActions Helper
MapActions is similar to mapState and mapGetters, but it has a little bit different syntax. So, first of all, we will import mapActions right here from Vuex. And then the first difference with mapActions is that instead of using it in the computed section we will use it in the methods section. So right here I'm going to spread mapActions. And there are a couple of actions in here that we want to map, and both of them are in the robots module. The first one is up here in this created hook. It is this dispatch right here where we're dispatching getParts. And the second one that we want to use is this addRobotToCart action down here. So, we'll map both of these actions here. And we'll start out by specifying the robots module since it's a namespaced module. And then we don't need to rename these actions at all so we'll just use the array syntax and we'll pass in getParts and addRobotToCart. So actually, this syntax is identical to the other helpers, but we're putting it in the methods section of our component. And the way we call them is interesting for actions. And this is in the methods section so that means that these are now methods on our component named getParts and addRobotToCart. So, if we go up here to where we dispatch our getParts call we can just call this.getParts. And the interesting thing about this is that notice that we're not calling dispatch anywhere anymore. Calling dispatch is taken care of for us by the mapActions helper. So that makes using it really nice. And now we can do the same thing for addRobotToCart down here in our addToCart function. So we can just call this.addRobotToCart. And then we'll delete all of this, but we'll still pass in our robot. Cool. So now we're not having to worry about any dispatch details, and this feels a lot cleaner. So let's go check this out. So, over here in our RobotBuilder, our robot builder is getting its parts. And I can still add to cart here. Awesome, this is working great. Let's go clean this up so that we don't have this extra stuff up here anymore. So, in our App.vue component I'm just going to comment this out in case you want to reference it later. And there, now our site's looking descent. Cool. So now we've talked about mapState, mapGetters, and mapActions. So the last helper that we'll talk about is the mapMutations helper.
Using the Vuex MapMutations Helper
MapMutations is not any different than all the other helpers. We don't have any mutations that we're using outside of the store, but I can show you how we'd use it if we did. So we'll just import mapMutations. And then down here in methods we'd map mutations just like everything else. So we spread mapMutations, and then add the module name if necessary, and then the array of mutations that you want to map over. Of course, we could use the object syntax here also just like we did with mapState if we wanted to rename the method locally. And that really is all there is to using mapMutations. It's just like using any of the other map helpers. But, of course, we don't have any mutations so I'm going to go ahead and delete this. So with that, we've covered all of the different Vuex map helpers. And, in fact, this concludes our discussion of Vuex.
In this module, we learned how to use Vuex to manage all of our applications state. And this included creating a Vuex store, changing state with mutations, retrieving data from the state, using getters to return calculated data from the state, using actions to work with asynchronous data, using modules to organize a store, and using built-in helpers to work with a store. In the next module, we'll learn how to create our own custom directives and how to create and use filters.
Creating Custom Directives and Filters
Welcome to this module on creating custom directives and filters. In this module we'll learn how to create and use our own directives similar to directives we've already been using, such as b if. We'll also learn about view filters and how to create and use them. We'll learn about creating and using custom directives, passing data to directives, declaring directives locally versus declaring them globally, creating and using filters, and declaring filters locally versus declaring them globally. Alright, let's check it out.
Creating a Custom Directive
View comes with a handful of built in directives that we've been using already in our application. For example, this v-if attribute is actually a directive. Same v show and v4. But what if we want to create our own directives? Let's explore how to do that. If we take a look at our robot builder, when we select a part that's on sale, it shows this sale icon down here. This is happening in our part selector. If we go take a look at that it's this span right here. And this span is being displayed in the bottom right hand corner using CSS. Imagine though if I had a common need for pinning an element down in the bottom right hand corner. Instead of having to always create a class that absolute positions the element in the bottom right hand corner, I could just create a directive that does it for me. Let's do that for this sale element. So to start with let's come down to the CSS and remove the positioning attributes out of this sale class. So we'll remove position, bottom, and right. And then I want to be able to pin an element to the bottom right hand corner just by adding a directive like this. So here I want to be able to just say v-pin. And then just because I added this directive, this element will be pinned in the bottom right hand corner. So let's go create this directive. So in the shared folder I'm going to create a new file and we'll call that pin-directive.js. Alright, and then to start with this is just going to export an object. And then a directive has a handful of methods that act as hooks. One of the most commonly used hooks is the bind hook. So this is just a function that we put on this object. And then this gets called as soon as the directive is bound to its parent component. And the first parameter that gets passed in here is the element itself. So in the case of our part selector that's going to be this span element right here. And this pin directive is going to be really simple. I just want to apply some styles to the element that is being passed in. So I can do that by just grabbing this element and then setting the style.position property to absolute. And then I'll also set the bottom and right attributes to five pixels. And then the same thing for the right attribute. And then that's it. That's all there is to creating this directive. Now in our parts selector component we need to import our new directive, so import pin directive from shared pin directive. And then we can just declare this locally in our component with a directive section here and then that takes an object where the key is the name of the directive that you're going to use in your template, and then the directive that we imported, so pin directive. And now because we've defined this as a directive here and named it pin, I can now use that up here in my directive like this. So notice that v- is pre pended to the name of the directive that we created. Okay, this should be working just fine so let's go check it out. There we go. So it's showing in the bottom right hand corner and then just to demonstrate that this is coming from the directive, let's remove the directive from this element and then refresh here. And then there you go. You can see the positioning is off. So let's go ahead and put that back. Awesome, that was really simple. Now let's go take a look at how we can pass some data into our directive.
Passing Data to Directives
Our new pin directive is kind of cool but it's not very flexible. It's always going to pin items to the bottom right corner. Let's make it so that we can tell the directive where to pin itself. There are a couple of different ways that we can pass information to a directive. So let's explore them both. The first is through the use of args. So back over here where we're using our directive, we can use args to tell our pin directive we want it to be pinned to the top right like this. So here I'm using args to say I want to position this in the top right corner. It's fairly expressive and to the point. The first part of this expression, position, is called the arg and the parts that follow it, separated by periods, are called modifiers. So position is the arg and top right are the modifiers. And then back over here in our directive we can get access to that information through the second parameter, which is binding. And the binding has a couple of properties that we're interested in, arg and modifiers. So let's just console.log those out for a second to see what those look like. So we'll console.log the arg and then the modifiers. Okay and let's go look at that. So if we open our console here there we can see that we're getting a couple of things logged out. So arg is positioned and modifiers is an object with two properties, right, which is set to true, and top that is set to true. So modifiers is an object that contains each modifier set to true if they exist. So back in our directive, we'll check the first arg to make sure that it's set to position and if not we'll just do nothing, so get rid of our console.log here and then we'll just say if binding.arg does not equal position, then we'll just return and do nothing because the data being passed in is not in the format that we expect. And then basically I want to loop over each of the modifiers and apply the style that pins the element to the corresponding position. So to do that I'm going to use object.keys, which enumerates all the keys on an object. And so we want to enumerate over all the keys on the binding modifiers object. And then for each of those we will call this function and then the key will be passed in and then for each key in the keys here are going to be top and right, I just want to set element.style with that key equal to five pixels. So this will set element.style.top to five pixels and element.style.right to five pixels if those are the modifiers that are passed in. Okay and then we can get rid of these down here. So we'll always set the position to absolute and then we will set the top and right and in this case, because we're passing in top and right here, we will set top and right to five pixels. If we send in something else other than top and right those will get set to five pixels. Okay, let's go see how this is working. So if we look at a sale item, there we go. It is pinned to the top right now. Alright, let's go see what happens if we change this to be pinned to the bottom left. And let's refresh that. And you'll notice that you saw some really weird behavior there when we came back here before we refreshed. And we'll get into that in a minute here. But notice if I do a full refresh on my page that it is now in the bottom left. So when I first came back to here before I refreshed, keep in mind that hot module replacement is at work here and so as we're making changes, hot module replacement, which is part of the web hack server, is applying those changes to our website without doing a full page refresh. And what was happening here is hot module replacement was still doing its job but our directive wasn't noticing all of the changes. And that's why it was pinned actually to the top right and the bottom left and so it made the entire image red. So in the next clip we'll cover why it wasn't noticing those changes. But first I had mentioned that there are a couple of ways to pass data into a directive, so let's look at the other way. The second way isn't quite as expressive but it's much more flexible. So instead of using args and modifiers we're going to pass everything in as an object. So I'm going to set v-pin equal to an expression and that expression is going to be an object. And we'll use that object to define the position. So let's say we want this to be pinned to the bottom with 10 pixels of spacing, and to the right with five pixels of spacing. So this object is going to be passed into the directive and you can see how this is more flexible than the args notation because I can actually pass values for each one of the arguments that I want to pass into the directive. So over in our directive we can access that with binding.value. Okay and then we're going to delete this check here for binding.arg. And then we're going to change our loop to loop over the keys in binding.value. So binding.value is set to whatever we passed into our directive here. So it's this bottom right object. And then we will set that to binding.value.position where position is the value being passed in here. So let's rename this to position just to be a little bit more clear. So for example, in this example here where we're passing in bottom 10 pixels and right 10 pixels, then position here is going to be bottom and right. So we're going to set element.style.bottom to binding.value.bottom. So this would set bottom to 10 pixels in this case. Okay, let's go check that out. There we go. Now you can see that this is showing in the bottom right but you can see there's 10 pixels from the bottom and five pixels from the right. Okay and to show that we can now configure it let's come back over here and let's change right to 50 pixels and see what that does. Okay notice that again, we changed it, but we're not seeing any change here and we won't see that change until we refresh. So there, now we can see that it's 50 pixels to the right. So the question is, why are we not seeing this unless we refresh? Because our hot module replacement is working. But this is actually a problem with our directive where our directive is not noticing the changes. So in the next clip let's take a look at the different directive hook functions. This will help us fix this problem.
Using Directive Lifecycle Hooks
You may have noticed throughout this course that we've had hot module replacement working, which causes our dev changes to be immediately rendered without having to refresh the browser. And you may have also noticed that our changes to our directive have been requiring us to hit refresh, and this is due to the life cycle hook that we're binding to in the directive. So over here in our directive you can see that we're using the bind life cycle hook. This hook gets fired when the directive is first bound to the element. So this code only gets fired once. But demonstrating this with hot module replacement is obscuring the issue, so let's create a better way to demonstrate the problem. So over here in our part selector component, instead of hard coding the padding here, let's bind to it. We'll use a new pin padding property. And we'll use that in both places here. And then to help demonstrate that let's also just display that pin padding right here. And then let's just make it so that when we click on this sale element it sets the pin padding to 30 pixels. Okay, so the attributes for our bottom and right are set to pin padding and then when you click on this element we set pin padding to 30 pixels. In order for this to work we need to add it to our data function down here. So we'll add pin padding and let's default that to 10 pixels. So when the component first loads this will be set to 10 pixels and then when we click on the sale element it will change it to 30 pixels. Okay, so let's go take a look at this. So let's refresh and then over here, so you can see that the pin padding is set to 10 pixels and actually, where we place this is not very easy to see so let's move this interpolation up here to the top. That should make it easier. There we go. So you can see that our padding is set to 10 pixels. And when I click on this element it will change it to 30 pixels. There you can see it changed it to 30 pixels up here but notice that our directive didn't get updated. And this is because we are only binding to the bind life cycle hook in our directive. So there is another life cycle hook that we can bind to and it's the update hook. So right here let's add an update life cycle hook and that takes in element and binding also. And then this update function is actually going to do the exact same thing as our bind function. So let's extract this out here. We're going to cut this out of here and we'll create a new function called apply style and that will take in element and binding. And then we'll just paste that code in here. Okay so now we have this apply style and we can actually call that from bind and from update. Okay so now our directive will also get updated whenever its parent component is updated, which includes when the directive's bindings are updated. So let's go check this out. So come back over here now and let's refresh one last time and then come over here. Now notice when I click on this that the directive is updated. So the directive is noticing changes now on the bindings of its parent element. Okay now there are three other life cycle hooks that can be tapped into including inserted, which fires whenever the bound element has been inserted into its parent node, and component updated, which like update, fires when the containing component has been updated. But with the update hook it could get fired before the containing component's children are updated. The component updating method fires only after all children have been updated. And then the last hook is unbind, which gets called when the directive is unbound from its parent component. We're not going to demonstrate these three life cycle hooks in this course because they're much less common. The bind and update hooks are by far the most common. In fact, it's actually so common to use bind and update together that there is a shorthand for creating a directive that hooks into both the bind and update hooks. And since that's what our directive is doing let's change ours to shorthand syntax. So here instead of exporting an object we're going to export a function. And that function is going to take in the element and binding parameters and then the body of this is just going to be this function here, so we'll cut this out here and paste it in here. And then we can delete the rest of this. And we can delete our reply style function. Cool, so you can see how this is much more to the point and a lot less ceremony going on in here. And we are now binding to both the bind and update life cycle hooks. So this is a really common way to write directives. And that should still be working fine. So let's refresh this. And there we go. Our directive is still working as expected. Alright, awesome. And that's how we create directives. Now let's go clean up this extra 30 pixels display here. We don't need that. There we go. Cool, so now let's go take a look at how to make directives available globally.
Making Directives Available Globally
So far we've only been making our directive available to a single component by importing it here. This is a good idea if the directive is really only related to a single component or just a couple of components. But often we'll want to create directives that we want to use throughout our app on lots of different components. When we do that it's better to declare the directive globally so we don't have to declare it in every component like we have here. So let's see how to do that with our pin directive. It's really quite easy. So we'll delete this directives section here and we'll delete the import. And now of course if we look at our site that directive is no longer working. Otherwise this would be pinned in the bottom right. And if we go look at the console, you can see that we're getting errors. Failed to resolve directive pin, so our component doesn't know about this directive. So now let's go declare it globally. We just do that in our main JS file. So if I come in here, let's import our directive here. So import pin directive from shared pin directive. Okay now we can make our view application aware of this directive by just calling view.directive and then we will pass in our pin directive but first we have to give it a name. So this is the pin directive and then we pass in our imported pin directive. Cool, and that's it. This directive should now be available for use in all components in our app. So if we go check that out over here you can see we're no longer getting an error here and if we come over here, there we go. It's pinned to the bottom right. Awesome, so directives are really quite simple to create and they can be really useful for sharing some functionality on elements around your site. Next let's take a look at using filters in view.
Creating a Custom Filter
View filters allow you to easily transform data in your templates but sometimes the name can be a little misleading. Sometimes when we think of a filter we think of something that takes a list and filters out items from the list. While view filters can certainly be used to do that, that's not all they do. When you think of the word filter, maybe it's easy to compare them to a filter on a camera, which takes a photo and transforms the way it looks in some way. View filters do something similar for data. They take data in as input and output something different. To demonstrate how to create and use a filter let's create a currency filter. You could see here on our cart page that we're displaying the cost of each robot. But it's not formatted very nicely so we'll create a currency filter to format it. To do that we'll create a currency filter here in our shared folder. So I'm going to create a new file here and we'll call this currencyfilter.js. And a filter is really just a function so we will export a function here. And a filter always has a minimum of one parameter and this is the input for the filter so we're going to specify amount here because this is a currency filter. And then our filter is just going to return a string. We'll use a string template here. And our string template is just going to pre pend a $ to the amount. So this looks a little bit weird because we have two $$ but this is just the string template syntax and then we're pre pending a dollar sign here to this amount variable. And then we're just going to format this amount with two decimal points. So we'll just say to fixed here, pass in two decimal points. Alright, now we can add this filter to our component like we did with directives. So over here in our shopping cart component we're going to import our currency filter from shared currency filter. And then we'll just add a filter section and we'll add a currency filter and set that to our currency filter. Alright now up here in our template we can use that right here inside this interpolation expression, so right after cost I'm going to add a pipe and then the name of our filter, so, currency. So as you can see here the first parameter that has passed into the filter is the value that precedes the pipe. So robot.cost is going to be passed in as the first parameter of our currency filter. And then we'll just copy this here and paste it down here where we're showing the cost also. Okay that should do it. Let's go take a look. There we go. So we have our currency format here and if we add a robot that's on sale then that's getting formatted down here also. Excellent. So that's formatting really nicely and that was really easy. But what if we wanted to be able to specify our currency symbol here? Right now we have it hard coded at the $. Well we could do that by passing a second parameter to our filter. So let's see how to do that. So back over here in our currency filter let's add a second parameter called symbol. And we'll use that down here instead of this $. So we'll use another expression here, pass in symbol, and now that symbol is going to be pre pended to the amount. Alright, and if we go to take a look at that right now you can see that undefined as being pre pended to our amount and that's because we are not passing in a currency symbol yet. So back over in our template, we can just pass that in like this. So I'm going to pass in a $ here. And then we need to do the same thing up here so let's pass in the currency symbol here. Okay now if we take a look there we can see that we're getting a $ here. Or I can pass in another symbol like the British pound for example. So here on my U.S. keyboard I'm going to hold down alt and type in 0163 on the numb pad and that gives me a # and I'm just going to copy this and paste this down here. And now if I come over here, there we go. Now we have a #. Okay so we now have a currency filter that takes in additional parameters. And of course you can do any type of data transformation you'd like with a filter since it just takes data in and returns data out. And since filters are just functions we can also just import them inside our component or anywhere else in our application and use them in a component like as a computer prop or whatever. They're just functions so we can use them however we want. Next let's take a look at how to declare this filter globally.
Declaring Filters Globally
Just like we did with directives we can declare filters globally. Right now our currency filter is just imported and exposed only in our shopping cart component. But using a currency filter is pretty common and so we might want to use that elsewhere in our app. So let's declare it globally. So we'll start by removing this import here and the filter here and then over in our main JS file we will import it. This changes a little bit. And then just like we did with directives we can say view.filter and the first parameter is the filter name so currency and then we pass in our filter. And that should do it. This currency filter is now available globally. So if we go back over to our app and add something to our cart, there we go. Our currency filter is now working and it's being shared globally. So, awesome. Now we have a currency filter that we can use anywhere in our app.
In this module we learned about creating and using custom directives, passing data to directives, declaring directives locally vs. declaring them globally, creating and using filters, and declaring filters locally vs. declaring them globally. Now that we've explored all the fundamentals of building applications with view, let's explore how to build and deploy our app to production in our final module of this course.
Deploying Vue Applications to Production
Welcome to this final module on Deploying Vue Applications to Production. In this module, we'll explore everything from packaging our application for production to production configuration options, to actually deploying to a production server. The basic Vue CLI build is a zero config build with reasonable defaults that can be extended or modified whenever necessary. This means that you could very reasonably create a new project with the CLI, develop it and then use the CLI to create a production-ready build without having to do any configuration, webpack or otherwise. Of course in reality, that rarely happens. It seems that we always need to be able to tweak configurations as we make changes to support our development decisions and to support our development and production environments. Luckily, the configuration is very extensible and easy to customize with whatever webpack configuration changes you need to make. We'll take a look at how to do just that, but first let's take a look at what a zero config build includes. So just what is included in an out-of-the-box zero config deployment? Well, the answer is a lot. I'm not going to read through all of these, but this is what I found when looking through the webpack config generated by the Vue CLI at the time this course was produced. There's a lot here, and it includes all the things you typically want from a webpack build like minification, concatenation, cache busting via chunk hashing, CSS preprocessor support for the most popular options, et cetera, Everything you'd typically think about when creating a build. And so there's a good chance you'd do just fine right out of the box. But in the event you need to add or remove something to config, you can always do that. In this module, we'll cover: creating a basic Vue.js build using the CLI, using environment variables and build modes, deploying to a production web server, handling deep linking when using HTML5 history mode for routing on the client side, inspecting the built-in webpack config and customizing the webpack config. Once we're done with this last module, you will have mastered all the fundamentals you need to know to get started with creating and deploying Vue.js applications. So let's jump in and wrap it up.
Creating a Basic Vue.js Build with the CLI
Once you've finished development and you're ready to deploy your changes to production, creating a deployable build is simple. Typically, all you need to run is npm run build. So this script will tell the Vue CLI to create a deployable build utilizing all of the built-in webpack config that the CLI generated for us when we first generated our project. This will result in a production deployable distribution folder. This build will contain all of the latest best practices identified by the Vue team for generating a deployable web application, including all of the optimizations we mentioned in the previous clip, all of this without us even having to think about it. Of course if you want to dig into it and make modifications, you can. More on that later. So let's go ahead and run this. Now that this is finished, we have this deployable dist folder. All we need to do to deploy this to a production server is copy this dist folder to a place where it can be served by our web server. We'll do this with an express web server later. If we take a look inside this dist folder, you can see that we have css, js and img folders, in addition to a fave icon and an index html. The CSS, js and image folders are the webpack contents of all the code that we've written for our application. But where did this fave icon and index html come from? We never created those. Well, those came from this public directory right here in our project, and they were generated by the CLI when we first created our application. And actually, any file that we put in this folder will be included in the final dist folder. But this index html file is an important file. This is the starting point for our entire application. When we deploy this to production, our web server needs to serve up this index html file whenever anyone hits the URL for our application. That will then load up our entire Vue.js application. And that's all there is to generating a zero config deployment for production. Next let's take a look at some other options that we have when building this dist folder, and then we'll see how to deploy it all to production.
Using Environment Variables and Build Modes
When we ran npm run build to generate our deployable dist folder, we didn't pass any arguments to it which means that by default it ran in production mode. But sometimes you want to be able to generate different deploys for different environments. We can do that with the mode flag. The default build that is created when we run npm run build is actually equivalent to this command, running npm run build with the switch mode=production. So by default it runs in production mode if we don't provide this mode flag. But we can provide different modes. For example, we could create a development mode build and pass in a development flag. And actually, when you run npm run serve, as we've been doing all along throughout this course to serve up our application and development, it actually creates a development mode build. The important thing to understand is that setting mode by default also sets the node_env variable, and this is really important. The webpack build that is generated when the node environment variable is set to production is very different than when it is set to development or anything else. Only production mode builds should be used in production. But what if we want to create a staging mode build and have it use the production node environment variables so that we could test a production build in a staging environment prior to production deployment? Well, we can do that with environment variables. To demonstrate that, let's see what happens if we create a staging mode build. So I'm going to set mode equal to staging and we'll run that. All right, now take a look at what is in our dist folder. This looks quite different from our production build, which had bundled things up very nicely in a way that was appropriate for production. The staging build is basically a development build so we just have this one app.js file, for example, instead of optimized chunks. But we can change that so that we could have a staging build that runs in production mode. So over here in our project, in the root folder, I'm just going to create a new file. And we'll name that file .env.staging. And then inside here, I can set environment variables including node environment. And so I'm going to set the node environment here to production because even though I'm building a staging build, I want it to be a build that looks just like production so I can test it in staging just like it will look in production. So I'll save that and let's come back over here and let's run our npm run build with mode staging again. Okay, you can see from the output there that it's very different. So if I take a look at my dist folder again, you can see this is all optimized like it was when we did a production build. So we can actually use any word that we want here for the mode. So I use staging, but this could be foo or anything else. And then we just create a corresponding .env file with that mode name appended to the end of the file name. And it will use any environment variables that we put in here for the build, which leads to another question, what other variables might you put in your .env file. Well, you could have, for example, connection information for connecting to databases or other configuration options like that. Just be sure that if you do that, you don't ever commit secret information to GitHub or anywhere else. You'll want to encrypt those values or find some other solution. But there's another thing that we can use environment variables for. Let's create another environment file and we will call it .env.development. And in here, let's create a new environment variable. We'll just call it VUE APP TEST. Now the VUE_APP piece of this is important. More on that later. For now, I'm just going to set this to foo. And now that I have that set, let's come over to our home page and we'll add a created hook in here. And then when our homepage component is created, we're just going to console log process.env.VUE APP TEST. And we created a development.env file with the app Vue test variable in it set to foo. Now we just need to start up our server again. So let's come over to our home page now and open up our console and just refresh this. And there you go, you can see that foo got logged here because in our homepage, we console logged process.env.VUE APP TEST. And in our .env.development file, that is set to foo. Now let's see what happens if we don't prepend the Vue app to this. So we'll just call this some test bar. And then back in our home page, let's also print that variable, process.env.SOME TEST. Okay, and let's stop and restart this again since we changed our .env file. Now let's come back over here and refresh. Notice that we got foo and undefined. And so in our config file, VUE APP TEST was available to us but some test was not. This is because, for security reasons, Vue will only make environment variables available to our client side code that start with VUE_APP_, but there is one exception to this and that is NODE ENV. So if we were to set this to development or any other value, node.env would also be available to us in our client side code. And this is because it's not uncommon to want to get access to the node environment in your code. So that one is available without prefixing it with Vue app. But every other variable needs to be prefixed with that. So you can use these variables in order to provide server-specific variables to your application based on the environment that you're operating in. Okay, so now that we know how to configure our deployments with environment files, let's see how to actually deploy to production.
Deploying to a Production Webserver
We just learned that we can create a production build with npm run build, and that creates this dist folder. Now we need to serve that folder from our web server. Your deployment process will be different based on the production web server you use, but just to give you an idea of what a basic deploy looks like, we'll deploy our site to an express web server. If you remember from earlier in the course, we cloned a build-a-bot-server that we used as an API. And up till now, that's all we used it for as an API. But let's update it to also serve our newly packaged app. Then we'll just deploy our app by copying this dist folder into our server. So let's open this up in Visual Studio Code. And then we'll maximize what. And you can see here in the index.js file that we have a few routes set up for our API. Let's just add a route to also serve the app from a dist folder, and then we can just copy in the dist folder whenever we want to deploy a new version of our application. So all we need to do is right here at the bottom of our routes, add an app.use, and we'll specify the root url here. And then we'll use express.static to serve the dist directory, and we'll let express.static know that we want to serve the default index file as index.html. So this line basically says whenever anyone hits the root url and it's not handled by one of the routes above, then serve the index.html file from this dist folder. And that's it, this web server is now set up to serve our Vue app. The only thing that's missing is the dist folder. So basically, whenever you deploy a Vue app, you're basically just deploying or copying the dist folder to your web server. So let's go ahead and copy the dist folder from our build-a-bot app into our server. Oops, that should be cp. Okay, so now if we take a look here you can see that we have a dist folder in here and this is our Vue.js application. And that's it, we just deployed are app to our server. Now let's go ahead and start our server. We can do that with npm start. And just like before, when we're running our API, you can see that this is running on port 8081. So one thing that's different now from when we were running in development mode is that we were spinning up our dev server on port 8080, and that was running with webpack dev server. And we did that with the npm run serve command from our build-a-bot folder. And then we were separately starting our API server on port 8081. So we have our webpack dev server running on port 8080 and our API running on 8081. But now that we've deployed are app to this production server, both are app and our API are running on port 8081. So let's go check that out. So we'll just navigate to local host 8081. And look at that, there is our server. And that is being served out of our dist folder. And if we click on Build, you can see that it is communicating with our API, which is running on the same server. So congratulations, you just deployed your first Vue.js application to production and everything here is working great except for one problem and that is deep linking. Let's take a look at that next.
Handling Deep Linking on the Server
There is one problem with our newly deployed app. You can see that our app gets loaded just fine and I can even navigate around within my app without any problems. However, let's see what happens if we try deep linking into our app. For example, what happens if I navigate straight to this build url here? So I'm going to open a new tab and navigate to that url directly. So you can see that I got a 404 error here. Why is this? Well, by navigating directly to this build url, the browser sent a request to our web server saying please send me the document at this url. However, there is no document on our server at this url. We don't have a route on our servers for /build. That is a client side route. So this is just a manufactured url created by the Vue router that only the client side app knows about. And so our server ends up returning a 404 because it doesn't know how to serve this url. So basically, what we need to do is when someone requests any url from our server, we need to return the Vue single page application. Or in other words, the index.html file from our dist folder. This is actually only a problem because we're running in HTML5 history mode. You may recall that before we were running in HTML5 history mode, our urls had hash lines in them like this. If you don't run in HTML5 history mode, the server won't try to load this url because the server only loads this stuff before the hash. But most people don't want this hash in their url so they turn on HTML5 history mode. And when you do that, you need to handle deep linking on your server. So let's see how to make our server work with that. So if you think about how to make this work, basically, whenever a call that is made for a route that is not handled by one of these routes here we'll want to serve the index html file. That way, we'll serve up the spa and then Vue will load everything up and do client-side routing to the correct url. How you do this in different web servers varies, but in express, because this is such a common practice, there's an express middleware that we can use. So back in my terminal, I'm going to stop my server and I'm going to npm install this connect history api fallback middleware. Okay, so now that that's installed, I'm going to come over here and I'm going to go ahead and require that. So we'll set const history equal to require connect history api fallback. And then I just need to use that middleware like this. So right here at the top, before I route, I'm going to say app.use, and then I'm going to call that connect history api fallback function. And then inside here I'm going to tell it the name of my index html file, which is just /index.html. In this case, this parameter isn't necessary because by default it will use index.html. I'm just putting it here for clarity. So basically, this says if a request comes in for a file that is not handled in the routes below, then redirect the request to /index.html. And this will essentially cause it to be handled by this route down here. Okay, so let's go check this out. So we need to start up our server again. And then let's come back over here and let's go ahead and deep link into the build page. Excellent, so you can see that works just fine. And we can try it again here for the cart page. And you can see everything, all the deep linking in our application is working now. And again, the way you set this up on your web server will be different depending on the production web server that you're using. Just know that you need to set up your server to always serve the index.html file whenever your server receives a request that it doesn't recognize. Cool, now let's take a look at how to change webpack config. But before we do that, we need to understand what the built-in webpack config looks like. So we'll take a look at that next.
Inspecting the Built-in Webpack Config
We'd like to be able to edit our webpack config, but before we can do that we kind of need to know what is already configured. Without some help from the CLI, this would be challenging because the Vue CLI builds the webpack config dynamically. So there's no webpack config file to look at. But not to worry, the Vue CLI provides the inspect command which outputs the dynamic webpack config. All we need to do is in our terminal, run vue inspect, and then we can tell it which environment or build mode to output the config for. So I'm going to say generate the production webpack config for production mode. And now I could just run this and it would output the webpack config to the console here. But to make it easier to explore, let's pipe this to a file. So we'll pipe it to webpack.config.js. Okay, so this is generating the webpack for us. Remember, you don't ever need to do this. This config file that's being generated isn't going to be used. We're just generating it so we can see what the webpack config file looks like that the CLI is going to use when it creates a build. So let's go take a look at this file. So over here, we can see our webpack config js file. And you can see there's a lot of stuff here, including all the webpack rules and loaders and everything. And the nice thing is if we add any of our custom config, which we'll do in the next clip, when you run this command it merges that all together and generates the resulting file. So not only can you use this to see what the Vue CLI is generating itself but you can also see how changes that you create are going to get merged into the webpack config at build time. So this is really helpful in order to see what the Vue CLI is doing before we start making our own webpack customizations. Let's try that next.
Customizing Webpack Config
Customizing the out-of-the-box webpack config can be almost as easy as editing an existing webpack config file. All we have to do is add a section to our Vue config file. If this file doesn't exist yet in a project of yours, just create it. We created this one earlier when we wanted to add our API proxy. So in here, we're just going to add a configureWebpack section. And this object is basically your webpack options object. So you configure things inside this object in the exact same way that you would your webpack options. And then the CLI uses webpack chain and webpack merge to merge it with its own config. So anything that you add here will be added to or overwrite the default config. To demonstrate this, let's add support for CoffeeScript. We do that by adding the Coffee loader. So loaders go in the module section. And the module section has rules. All right, and then we're going to add a new rule, and this rule will target files that end in .coffee. And then we'll just tell it to use the Coffee loader. And that's it, now this config will be merged in with the default webpack config. So now let's go take a look at the new webpack config. So we're going to come over to our terminal and we're going to re-run this vue inspect command to generate a webpack config. Now this is going to emerge our config with the zero config that the CLI generated and we can go take a look at the result now. So over here in our webpack config I'm going to hit Control + K and then Control + 0 in order to collapse everything in here. And then I'm going to expand this. And then down here in the modules, I'm going to expand modules. And inside rules, you can see how we have a whole bunch of rules, and down here at the bottom is our new CoffeeScript rule. Of course if we were actually going to leave this in and actually use CoffeeScript, then we need to npm install Coffee loader. Since we're just doing this to demonstrate how to edit the config, I'm not going to bother doing this. But you can see that we can be confident that the Vue CLI is going to take this rule into account when it creates our build. But what if instead of adding a new setting we wanted to edit an existing one? For example, I'm going to come up to the top of my page here and then I'm going to search for the string right here. So this is a rule targeting image files. And let's go ahead and expand this. And you can see this is a url loader rule. So what if we wanted to change this limit here? Well, let's try it, let's copy this rule here and then let's paste it in over here in our Vue config. So right here we'll add another rule here. So this is targeting that same rule and then we'll change the limit here to 5000. Well, this is actually going to have an unintended consequence. So let's delete our current webpack config file and then we will go generate a new one. And now let's see what this looks like. So if we come back in here, and then let's search for this again, so now you can see there are actually two rules in here. And the first rule has it set to a thousand, and the second one has it set to 5000. This sort of configuration can have unintended consequences so there's a better way to edit existing rules. Instead of setting our configureWebpack to an object, we can use a function. So up here I'm going to set configureWebpack to a function. And that function takes in a config object, which is the existing config from the CLI. And then we can just mutate this config that was passed in here. So for our Coffee loader, I'm going to just append a rule to the module rule. So I'm going to say config.module.rules.push, and then I'm going to push on our CoffeeScript rules. So I'm just going to grab this here and move it up here. Okay, so you can see I've mutated that config and added our CoffeeScript rule. That one was easy. The next one's going to be a little bit trickier. Let's start by storing our new rule in a variable. So I'm going to say const newRule =, and then let's grab this rule down here and paste that up here. Okay, so now we have that in a variable available to us. Let's go ahead and delete this down here. Okay, so now we need to do something with this new rule. So first, let's find the index of the rule that we want to mutate. So I'm going to set an images rule index variable equal to config.module.rules.findIndex. And I've got a typo here, images. All right, let's bring this down on to the next line here. And then we're going to find the rule where the test expression is targeting our image files. And the test object is a regex so that has a source, and the source is a string so we can use includes to search for our expression. Okay, so that should find the index of the url loader rule that we want to modify. And now I can splice the current rules array to replace the old rule with the new rule. So I'll say config.module.rules.splice. And I'll start slicing the array at imageRuleIndex and I'll replace that item, that one item, with our new rule. So this is a little bit complex and maybe not totally safe because we could have multiple rules that have that same test expression. But this will help you get the idea of how you might use the configureWebpack function in order to mutate state. And there are other options for editing existing config using webpack chain, but using webpack chain is beyond the scope of this course. Just be aware that if you start running into issues with modifying existing config, you can use webpack chain to help with mutating the config. Okay, so this rule should now be replaced with our new rule, so let's delete our webpack config and then let's go regenerate our config. Okay, let's go check this out. So in our webpack config here, let's search for this string again. And you can see now that we are only finding it once and it does have limit changed to 5000. So now you've seen how you can edit the webpack config using the object syntax and the function syntax. And that's all there really is to editing the webpack configuration. You really have complete flexibility here, and all of this without ever having to eject from the Vue CLI so that we can continue to use the Vue CLI in the future to do upgrades and to handle most of our webpack config and other configuration for us. Cool, and that's all there is to editing webpack config.