Learn Angular A-Z: Complete Tutorial for Beginners

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[MUSIC] So you want to learn Angular, and I can't blame you. Google ushered in a new paradigm for web application development when it released the first version of Angular in 2012. And while yes, that was a very long-time ago, especially considering how fast things change in the web industry, Google continues to improve Angular with new features and performance. Angular is a larger framework than React and View, providing more features and capabilities out of the box, so it's entirely understandable if you find it. Well, a little intimidating, but you've come to the right place. I am Jeremy McPeak with Envato Tut+, and I invite you to spend the next few hours with me as I walk you through the fundamentals you need to get started writing applications with Angular. We'll start at the very beginning, you'll install the Angular command line tools, and we'll create a new project together. You'll learn how to display data with Angulars directives and templating syntax, as well as binding data to properties, attributes, and handling events. We'll then dive into components, and you'll learn how to create components that accept input and provide output. I'll also teach you how to set up two-way binding and manipulate style within your component. Then, we'll look at some of the services and concepts that Angular uses, such as dependency injection, the HTTP module, and observables. You'll learn how to create and use modules to organize your applications, handle and validate form input with reactive forms, and build single-page applications with Angular's router. Now, we have a lot of ground to cover, but before we begin, be sure to subscribe to Envato Tuts+ for more free courses and tutorials. Now, let's get started. I am a firm believer that jumping right in and creating something is one of the best ways to learn a new technology, so we are going to do just that with Angular. But first of all, we need to go over what you need for Angular development, and the first is node.js. Now, we aren't going to be actually writing a node-based application instead, we install node in order to get Node Package Manager or NPM. It's how we manage our dependencies with modern development. And in the case of Angular, we use NPM to get the Angular development tools. And so when it comes to installing node.js, you essentially have the choice between two versions, you have the LTS, which stands for long-term support, and then you have the current version. Now, for our purposes, it really doesn't matter which version you choose. I typically always reach for the LTS because it's long-term support, it is the more stable version. But as I mentioned, for our purposes, it really doesn't matter, so choose whichever one you want. The installation is very straightforward, just take the defaults, and you will be good to go. You will also need a code editor. Now, technically, we are going to be working with just normal text files, that's all code is, but logically, code is specialized text, so it makes sense to have a specialised text to editor. And when it comes to working with JavaScript, Visual Studio Code is the best editor. Now, of course, you can use whatever editor that you want. There is no requirement that you use Visual Studio Code or anything else. Use what you like and if you don't know what you like, there are many free options available to try, there's many not free options that you can try. The point is to try a lot of them and find the one that you like. Now, as far as Visual Studio Code is concerned, there are distributions for Mac OS Windows and Linux, so chances are you will be able to run it on your machine. And, of course, you need a browser and any modern browser is going to be fine, especially since most modern browsers are now based upon Chromium. However, I will say that browsers like Edge, Opera, Brave, all of those Chromium-based browsers, while they work the majority of the time, there might be some specific things that don't work quite right. So for me, I'm going to be using Chrome, but again, feel free to use whichever modern browser that you like. So after you have installed Node, you will want to open up the command line, because NPM is a command line tool, and we will use NPM to install the Angular CLI. The CLI is the command line interface. And really, it's just a tool set that we can use to create projects, work with our projects, and even run them as well. The command that we will use to install the Angular CLI is npm install @angular/cli. Now, we want to follow this up with the global flag, that's --global, and the reason is because if we install this globally, this is going to give us a command called ng, and that command is what we use to work with our projects. And we will be able to use that ng command anywhere, regardless of what directory we are currently in. If we installed the Angular CLI without the global flag, then it would just install that within whatever directory we are currently in, and we wouldn't have global access to it. When Angular is done installing, we will use the ng new command, and then we just need to create our project. Let's create a to-do application, but let's not call it to-do because that's boring, let's make this a wish-list, and then press Enter. Now, this is going to ask us some questions for creating a project. And the first question is asking if we gonna use angular routing, which we will talk about routing later in this course. But for right now, we're going to take the default and specify, no. Now, I do want to point out that Angular is rather unique when compared to React and View, because the router is built in to the Angular framework. With React and View, you have to install the router separately. In fact, Angular gives us quite a few features out of the box that other frameworks don't, but we will talk about those whenever we need to. So I'm going to choose no for the routing. And then, we are going to be asked what style sheet format we want to use for this project, and you can see that there are several options here. I'm just going to choose just normal CSS, and now that we have a project, I'm going to CD into that directory. It's simply the project name that we chose, wish-list in this case, and I'm going to fire up the code editor. Now, we can also run our application so that we can actually see it in the browser. And the developer tools will also give us the ability to develop our application and see the results in real time. So it's going to watch our files and whenever we make changes, it's going to automatically rebuild our application and load that into the browser, and so we can run our project in a couple of different ways. We can go back to the command line and simply type ng serve, and that's going to start up our application in development mode, so that we get the benefit of the hot reloading and things like that. Or we can use the built-in terminal within visual studio code, which is what I'm going to opt to do, and I'll pull that up, we'll type ng serve, and then we're going to see, The Angular CLI go through the process of building our project. It will tell us where we can point our browser so that we can then view that within the browser. We can see that our application was compiled successfully, and it is going to be at localhost port 4200. So let's go ahead and hop on over to the browser so that we can view that. Now, if there were any errors as it compiled our application, of course, we would see those errors in the output. But of course, there shouldn't be since this is a brand new project. Now, in the browser, you're going to see a full fledged web page. Of course, most of these things point to the Angular website. But let's just take a moment and see how we get what we see in the browser. So inside of our project, we have this source folder, and this is the folder where our application source code lives. So let's open this up. And we're going to see some more folders and we will see this main.ts. If we open this up, this is the main TypeScript file of our application, because Angular uses TypeScript. Now, if you're not familiar with TypeScript, it's simply just a superset of the JavaScript language. So perfectly valid JavaScript is actually perfectly valid TypeScript. And so we're not going to dive into the inner depths of TypeScript, we will be using it. And as some features pop up, I'll of course mention them, we'll talk about them at that time. But for right now, we are just going to treat this as just normal JavaScript. Well, until we don't. So here, we have main.ts. This is essentially the main file of our application. There are four import statements, but the one that I want to talk about is on line 4, where it is importing this AppModule from a file called app.module inside of the app folder. So let's take a look at that. So here, we have this app.module.ts file, and we can see that it is importing this app component. Now, Angular is very similar to React and View in that it is component-driven. We build applications by building components. A components is just a building block. It contains certain functionality for different pieces of the application. And over on the left-hand side if we look more inside of the app folder you're going to see several files that begin with app.component. So in this case, we have a component called app, and it is made up of each one of these files. There's one for the CSS, there's one for the HTML, which is essentially the template for this component. This is what is actually loaded into the browser. There's a spec file for tests, and then there is the code file, or the TypeScript file for that component. So in this particular case, it has four files. That doesn't always have to be the case, but it's what we have for this app component. Let's take a look at the HTML file, because this contains all of the markup that's actually loaded inside of the browser. It's not all of the markup, but it's quite a bit of it. The first [LAUGH], well, two-thirds of it is just CSS. But if we take a look after the style, we can start to see just normal HTML. We have a div with the class of toolbar, the role is banner. There's some content inside of there. Then we have another div with a class of content, the role is main. And once again, we have some content. But look line 344, this doesn't look like HTML. We have a pair of curly braces, and then inside of that we have title. So remember that I said that this is a template. This is the HTML template for the app component. And so what we see here is just some template syntax. It is outputting the value that is assigned to a variable called title. So then the question becomes where is this title defined? Well, that is inside of the TypeScript file, app.component.ts. Here, we can see that there's a class called AppComponent. It has a title property, it is set to wishlist. So if we take a look at this in the browser, we can see at the top that there's this wishlist app is running. That is the text that we saw inside of the HTML. Except that we see title here, and the value assigned to title as we saw is wishlist. So here is wishlist app is running. So if we change the title from wishlist to simply my wishlist, then whenever we go to the browser, it's gonna take it a second or so because it has to recompile our application. But you can see that it automatically updated, I did not have to refresh that. So that's now the title is my wish list, and we see that reflected in the browser. Now, here we see something that looks a little weird, especially whenever you just think in terms of JavaScript, and that is this AppComponent. This is what we call a decorator. And the decorator gives us some information about this particular component. This is a component called AppComponent, but notice that there is this selector, and it is app-root. Then there is a templateUrl. And then the HTML file, app.component.html is set for the template. And then in styles is set for the styles URL, which is inside of the app.component.css. So this component decorator essentially tells Angular how this component is going to be used with this selector. This is essentially a tag name, where the template for this component is and where the style is as well. And so finally, let's open up the index.html file. This is directly inside of the source folder, and it doesn't really look like much. But notice here on line 11, we have app-root, that is, the selector that was defined inside of the AppComponent. So selector is app-root. So here inside of index.html, app-root is being used. So Angular knows that this is where it is going to generate the template for the AppComponent and inject that right here on line 11. So that whenever we are in the browser, we see the result of that index file being loaded and then the content from the AppComponent being rendered inside of it. And so if that is clear, don't worry because we're going to be spending a lot of time with Angular. And in fact, in the next lesson, we will start developing our wishlist application. When I start developing a new project, I typically start with the data. Because, after all, we write software to work with data. And if we don't have data, it makes it very hard to develop our software. So in this lesson, we will begin by writing a class that represents an individual wish, because after all, this is a wish list. We're going to have a list of wishes. So the question becomes, where do we define this class? Because we do have this source folder, There's an app folder and then there's also some files just directly inside of source. Well, there really are no hard and fast rules when it comes to structuring our project. There are a lot of people that have a lot of really good ideas, but I think for this application, let's just keep things simple. A wish is something that's going to be used throughout the application. It is a simple application, but the idea is that this is a wish list. We might want to use the individual wish items anywhere. So it would be something that would be shared across the application. So inside of the source folder, I'm going to create a new folder, called shared. And then inside of shared, I'll create another folder called simply models. This really isn't keeping it simple, is it? [LAUGH] But there is something to say about keeping things organized. And I personally like this organization here. So inside of modules, we'll create a new file and this will be our class that represents an individual wish. So we can call it wishListItem but that's a mouthful, so let's just call it wishItem. And this is a TypeScript file, so we'll use the TS extension. Now, let's zoom out just a little bit, because how we define this class is very important. If we take a look inside of the app folder, let's open up app.component, and we will see, of course, we import component, which is that decorator that's used to tell Angular the meta information about this component. But then we see the class definition, but it is being exported. And that is because it needs to be imported inside of this other file, app.module, here on line four. But then, again, if we take a look at this file, there are some import statements. The NgModule decorator is used, but then there's a classified that is also exported, and the reason is because it is imported inside of main.ts. So the point that I'm trying to get across is, regardless of if it's JavaScript or TypeScript, it needs to be a module because we are essentially going to import it somewhere within some other file. So whenever we create our WishItem class, we need to export it and we'll call this class WishItem. And we are going to use a TypeScript feature here that allows us to define a constructor. And it will also automatically create the properties for this class, because we could explicitly define those properties, but we can also do so as arguments to the constructor. So the first thing we are going to mark is the accessibility of this property. We want this information to be public, so that we can access it from outside of this class. And when it comes to a WishItem, we essentially have two things, we have the wish itself, which we can call wishText. I'm not exactly sure I'd like that, but we'll run with it. And then we would need a Boolean value to determine whether or not it is complete. Now, TypeScript is called TypeScript, because it allows us to also specify the type of the data that we are working with. You don't have to use the type syntax, but it is extremely helpful when developing larger projects. So our wishText is of course text, this is a string. And in TypeScript, we specify the type after we define the variable or, in this case, the constructor parameter. So in this case, it is simply a string. The isComplete is a Boolean value, so we will mark that as Boolean. And so whenever we create a new WishItem, we will pass in the text for the wish, we will denote whether or not it is completed. And behind the scenes, that's going to automatically create two properties, one called wishText, which is a string, one called isComplete, which is Boolean. We can also set a default value for Boolean. We could say that by default, isComplete is going to be false, so that then that gives us the option of passing either just the wishText or the wishText and the isComplete. So let's go to our app component, and let's go ahead and let's import this because we want to use this WishItem class. And Visual Studio Code is automatically popping up with the correct file, so we can just click on that and it has imported that. And so now inside of AppComponent, we simply want some items, which we can initialize as an array. And let's pre-populate this array with a few items. So we will call the WishItem constructor. The first wish could be To Learn Angular. And I want to point out the tooltip that we see here. We can see that WishItem accepts up to two arguments. The first is the wishText, which is a string, the second Is optional, because of this question mark after isComplete, and that is Boolean. This is one of the reasons why TypeScript exists, it's for tooling and providing a better development experience, so that as you are writing your code, your code editor can provide information about the types that you are working with. In this case, that type is a WishItem. Let's add another wish, one that we could go ahead and complete, and that is to have some coffee. So let's say Get Coffee, and let's pass in true for that value. And then let's add one more item, and this is definitely going to be a wish, but Find grass that cuts itself. So let's save this file, let's pull up the console. Let's see if we get any errors with the build process, and we don't, it was compiled successfully. And then we just need to display this information in our template, because, of course, as it is right now, we haven't changed our template, it is still as it was before. It's working with the same data, which really the only data was that title. But we want to change what is reflected here, and we will do that in the next lesson. In the previous lesson, we created a data model class to represent an individual wish. We called it simply WishItem. And then inside of our AppComponent, we created an items array that contains multiple WishItem objects. So, of course, we want to display these items within our application. But, of course, right now, we have just the default templates being used, but that's easy enough to change. Let's open up our template. And if you'll read the very top, it says that this is only a placeholder. Delete the template below to get started with your project. All right, so we'll just delete everything. And let's also pull in some CSS because it would be useful to have something other than just the default browser stylesheet. Now, it's common to find Angular applications using the material UI library. If you're not familiar with material, it is Google's UI framework or UI language, whatever verbiage you want to use there. And that's all well and good, but I think that we can get started much easier. By just using Bootstrap. Cuz we can just use the CDN, paste in that link to our index.html file, that is the template of our application. So that everything within our application is going to have access to Bootstrap. Makes it very easy to do. Plus, it's a lot easier to use. Now, let me also say that Material is a pretty cool library. There are a lot of different components that have the Material UI built in. And that's why you would find it's used in a lot of Angular applications, but for our purposes, this is going to be fine. All right, so let's start our components template with a div that has a class of container. And we'll just start with a simple div element so that we can display the individual items within that array. Now if you'll remember from a few lessons ago, we saw this syntax inside of our template to where we had the double curly braces. And then inside, I think it was title that was used, yes it was, that title property for our app component. So if we wanted to, we could display the first item by using the index of 0. And then we could have the wishText. I think that's the property that we used. We'll definitely find out whenever we review this in the browser, and sure enough, that's it. So we could do the same thing for the other items, but of course, that's not scalable whatsoever. That would be a nightmare. So whenever we work with arrays, we typically iterate over them using some kind of loop. And JavaScript has several different loops, but the one that we typically always use is a for loop. And Angular gives us an easy way of iterating over an array, or let me say this, it gives us an easy way of using the for of loop. And we will use what's called a directive. A directive generally starts with an asterisk, followed by ng, short for Angular. And if you'll notice, Visual Studio Code is popped up several options here. But this set right here, this little icon indicates that this is something that we can use for this div element that we are working with. And we see ngFor, ngIf, ngPluralCase, ngSwitchCase. And the thing that sticks out to me is ngFor, because, hello, we have a for loop. So whenever you use a directive, you use it like an attribute. So this looks a little wonky. And then the value for this directive is essentially a JavaScript expression, that's item of items. So let's take a step back. Let's talk about the for loop. So the traditional for loop that we typically learn first is that for loop that has the three parts. We have the initialization, followed by the condition, and then we have the iteration. And that's all well and good, but that's a little bit more than what we need. We just need to work with the individual items. So JavaScript gives us the ability to use the for of loop, which is primarily used for iterable objects, like an array. So that we can say for, and then let item of items, and then that's going to give us the individual item that we can work with, and that's exactly what we want in our template. So let's go back to the template, and that's what we have. So we have a directive, ngFor, the value for this directive is the JavaScript expression for the for of loop. And then that gives us the ability to use that individual item object however we need to. So we could say item.wishText, and inside of the browser, we will see our list of items. Now that's all well and good, but we want something a little bit more interactive. Because this is, after all, a wish list, we should be able to check these things off as needed. Now I'm going to paste in some markup, because, well, it's quite a bit of markup and you don't want to see me type this. And if you've used Bootstrap before, you know that there can be some setup involved to make something look nicer. And that's basically the case here. The code is essentially the same, at least as far as the ngFor directive is concerned, and whenever we output the wish text. Other than that, we just have a basic ul element that has some li elements, that is using the Bootstrap markup for outputting a checkbox. So if we take a look at this in the browser, we get a list of items with checkbox that we can check. And, of course, right now, behind the scenes, the checkbox isn't doing anything. We want to be able to essentially change the values of our wish items when the user clicks on these checkboxes, but we'll get to that. The one thing that I do want to get rid of is the list dots. So we can do that by changing the CSS. And let's open up app.component.css. And we're going to add the wish-list class. So that will set the list style to just none. And that will get rid of that. So that now we just see the checkbox and the text for that checkbox. Now the ngFor directive is somewhat special, in that it is what's called a structural directive. It changes the structure of the DOM. So in the case of ngFor, it is adding an li element for every item in the items array. So it is adding elements to the DOM structure. In the next lesson, we will look at another structural directive, one that we can use to make decisions and conditionally show or hide content. In the previous lesson, we looked at the ngFor directive, and we used it to iterate over an array so that we could display the individual wishes. And that makes perfect sense. But what if we don't have anything to display? Like, for example, we've just hard-coded these wishItem objects, and for development, that's fine. But in a real application, this information would be dynamically loaded, either by fetching it from the web server or by accessing some storage, like local storage. So let's comment out those lines, and we can see that there are some issues. Now, in the terminal, we see that there is an issue. It did not compile correctly, and we see that it is an issue with the template of component AppComponent. And it, of course, tells us what that template is, but Visual Studio Code gives us some visual cues as well. The file name of our template is a reddish color. The directory that contains that file is a reddish color, and then its parent directory is a reddish color as well. And of course, the tab text for that template is also reddish, so there's definitely something wrong with that template. If we open up that file, then we can see by this red squiggly underneath wishText that that is the issue, that's the error. So if we hover our mouse over it, it says Property wishText does not exist on type never. Now, if we go to the browser, we will see the same thing, except that this is a little bit easier to read, at least in my opinion. Property wishText does not exist on type never. All right, so this can be a little confusing if you're not familiar with TypeScript. Because remember that we are using TypeScript, TypeScript is a superset of JavaScript, and any valid JavaScript is valid TypeScript. However, behind the scenes, TypeScript is doing quite a few things that's we may or may not be aware of. For example, whenever we created this items array, we hard-coded those WishItem objects. So TypeScript, behind the scenes, assumed that this items array is an array of wishItem objects. We can see that right here that items is an array of WishItem objects. And TypeScript was safe to assume that because we only have WishItem objects inside of that array. Now, what happens if we take out those objects? Let's hover over the items property, and we can see here that items is now an array of never. And the reason is very simple, because it's an empty array. And as far as TypeScript is concerned, it will never have any objects inside of it. Because we initialized it as an empty array, we don't do anything else with it, so it's an array of never objects. And so there are primarily two ways that we can get around this. The first is to say that our items array is actually of type any. If we take this approach, then this would be considered the the JavaScript approach. Because in JavaScript, we can have an array that contains anything or we can have an items property that can be anything. And in which case, the error goes away, if we look at the browser then of course, we don't see anything, but that's kind of what we would expect because there are no items to display. But what I would do is this. We know that items is only going to be an array that contains WishItem objects, so we might as well just go ahead and say that this is an array of WishItem objects. And if you're not familiar with TypeScript, the syntax for doing that is fairly simple, although it looks a little wonky in the world of JavaScript. So you start with the name of your property or variable, followed by a colon, and then the type of that variable or property. In this case, it's an array denoted by the square brackets after the WishItem class. Okay, that's great. So we save that. We still have no errors, we go to the browser, we still don't see anything. And at least at this point, that's okay because we don't see an error. But in the terms of just the application as a whole, we don't want to just display nothing, we want to at least say that there are no wishes. So we can do this by using a directive called ngIf. If the array doesn't have any items, then display our message. So we'll have a div element, we will use the ngIf directive, and this works a lot like just a normal if statement. We provide a condition, and if that condition is true, then the element is going to be added to the document, and of course, any content that it contains as well. But if the result is false, then this element is not rendered in the document at all, it's missing, it ain't there, and that's perfect for our needs. So let's do a check. If the length of items is equal to 0, then we want to display the message of There are no wishes to display. And so if we view this in the browser, then we should see that message, volla. But let's take a look at the HTML that is rendered in the document. So, there's our app root element. Let's drill down. There's the div that has a class of container. And then we have the div that has our message, There are no wishes to display. That works great, but we have this UL element. Now in the grand scheme of things, it's not that big of a deal that we have the ul element. But if we don't have any items to display, why render the UL element at all? Now unfortunately, there is not an ngElse directive. That would be perfect in this particular case, so that we could have just ngElse and we would go on our way, we don't have that. So one of the ways that we can get around that is just by using another ngIf, except that we will essentially do the opposite comparison. If we have in the items, so if the length is greater than 0, then we want to show our UL element in all of the LI elements inside of it. But if not, we don't want to render that UL element at all. And we can see that now we still have the div that has our message, but the UL element is gone, it's not there anymore. And that's all well and good, but there are alternatives to this approach. For example, we could have just a container element. So let's just start with a div. And we will still use the ngIf here, and we can still do the same comparison if the item's length is equal to 0. But then we could say that if that's true, then we want to render the content inside of a template that we will call no items. Else, if we have items, then we want to render a template called showItems. Then we can define those templates using a special component called ng-template. We define its name starting with a pound or a hash sign, and then the name of the template. So this would be for no items, in which case we would display There are no wishes to display. And then if we do have items, we would want to have our other template called showItems. So we would create that template, give it the name of showItems, and then it would contain our UL element just like that. So it's a little bit more typing, but it's also a little bit more clear as to what's going on here. So once again, if we go to the browser, we still see just that content. If we drill down into app root, drill down into container, we have our message. But notice that the div element that we put the ngIf directive on, that's not being rendered here, so that's just kind of a placeholder for the content for our template. But let's see the other work. Let's go back, and let's have our list items here. We'll go back to the browser, it will refresh, and then we can, of course, see our list of wishes. So that works, but we can make this even just a little bit cleaner. We could get rid of the then statement here and we can put our message as the content for this div that has the ngIf directive, so that if the condition is true, then our content inside of this div element and this div element itself is going to be displayed in the document. Otherwise, if we do have items, then the show items template is going to be used. So let's go back to the browser. That's what we see. We have our list. If we drill down, we have our div with a class of container. But we also have are ul element that of course, contains all of our items, but let's make sure that the other works as well as comment out those items. Once again, we'll go back to the browser, we see our message and if we take a look at the rendered html, we have our div with a class of container, then we have that other div that contains our message, there are no wishes to display. So when it comes to making a decision, we can do so in several different ways. We can use multiple ng-if directives. We can use ng if in conjunction with the then statement or we can also use the else statement. Now there's one other thing that I want to point out. NgIf is a structural directive. It changes the structure of the document based upon the condition that is evaluated. And now that we know two structural directives, it's important to know that you cannot have two structural directives on the same element. So if I add ngIf to the li element, and it doesn't matter what we use for the condition, we're going to see that there is an error, and the error is this second structural directive, the ngFor directive. You cannot have two structural directives on the same element. Now that’s going to force you about how you need to structure your mind and structure your structural directives because there is no hard and fast rules as to which directives you use on each element. That all depends upon what your needs are. Now, in the next lesson we are going to learn how we can find data. We write software to work with data, and that data can come in any way, shape or form, and it is up to us to determine how we work with that data. Of course, it depends upon our application, but sometimes we want to display that data, sometimes we just want to work with it behind the scenes. Now, in the case of our wish list, we have essentially two pieces of information that we want to display. We want the text of the wish, which we are currently doing, but we also want to show whether or not that wish has been fulfilled. Now, that is, of course, what the check box is for. And eventually we want this to be interactive so that whenever the user clicks on these check boxes, then that toggles whether or not the wish has been fulfilled. Now, in the sense of our data, we represent that fulfillment value with the is complete property. It is a Boolean value, of course, so it's going to be true or false. Right now, we only have one wish that is fulfilled. Get coffee, because I always have coffee. So at the bare minimum, we of course, want to display this in the browser. So the question becomes, how do we do that? Well, let's first of all talk about the input element. And more specifically, let's talk about the checkbox. And the same goes for the radio button as well. So in html, we denote that a checkbox is checked by just adding the checked attribute to the element. That's all we have to do. If that checked attribute is present, then the check box will be checked in the browser. And the funny thing is it doesn't matter what you assign to this attribute. It can be assigned false, which makes no sense whatsoever, but it is still going to be checked in the browser, because it is based upon whether or not that checked attribute is present. Now, that's the html side of things. On the JavaScript side of things, this input element has a checked property. That property is Boolean. So if it is true, then the check box is checked. If it's false, then it is not checked. So the JavaScript property and the html attribute are named the same, but they behave differently. And because our data is Boolean, it makes sense that we would want to bind our isComplete property to the checked JavaScript property on the Input element. And we can do that very easily right here inside of our template, we will use what's called property binding. So we will take the JavaScript property name. In this case it's checked and we surround it with square brackets and then the value that we assign to this is a JavaScript expression that will determine whether or not this check box is checked. That, of course, is the IsComplete property on our Wish Item class. So if we take a look at the browser, we have our three items here, but notice that the Get Coffee Item is checked. Let's inspect this and we will see that it is indeed type checkbox, the class is checkbox, but notice that there is no checked attribute. That's because we set this using the JavaScript property Opposed to the html attribute. So when you want to bind something to the JavaScript property of an object, all you have to do is surround that property name with square brackets, assign the JavaScript expression that will then be bound to that property, and you are good to go. Now one thing to note here, this is the JavaScript property. So if you were doing something using a td element inside of a table and you wanted to bind to the call span. Well, call span, all lowercase is the html attribute. The JavaScript property uses camel case. So if you want to bind to the JavaScript property, you would need to use the camel case version of call span because after all that is the JavaScript property. Now, not to confuse the issue Issue, we can also bind to html attributes. So for example, we have a checkbox and we are determining whether or not that checkbox is checked, using the checked property, and we are binding that to IsComplete. But let's say that we wanted a custom attribute called data-index. And this would represent in index of the item inside of the items array. Now we can get the index with the ng4 directive. All we have to do after our iteration statement is have index, as and then whatever identifier we wanted to use for the index. I'm going to use ii because that's what I typically use. And if we assign ii to just this data-index inside of the browser, whenever it is rendered, we see that data-index is there, but it is literally the string ii. We want to bind this ii variable to the attributes, and we do that almost in the same way. We surround the attribute with square brackets, but before the attribute name, we have a prefix, attr.and then the attribute name. And this is going to result in an input element, has a data-index attribute, and it is set to the index of the item in the items array. So we can bind data to an element's attribute or to its property. Using similar syntax, the only difference is that binding to attributes uses the attr.prefix followed by the attribute name. And in the next lesson you will learn how to bind events. Events are probably the most important part of an application with a graphical interface because it is the primary way that the users interact with our application. Like, for example, in our very simple UI, the user is going to click on these checkboxes in order to toggle whether or not that wish has been fulfilled. But of course, right now, we aren't really doing anything. Now, we have bound the isComplete property on our wish item to the checked property on the checkbox. However, that is a one-way binding. We are taking the value of isComplete and binding that to the checked property on the checkbox. So when the user clicks on the checkbox, it's not updating the isComplete property. So we need to write the code that's going to do that. So in order to bind an event, it's going to be very reminiscent of the DOM level 0 event handlers, which were simply HTML attributes. If we wanted to handle the click event on an element, we would use onclick, or for mouse out, it would be onmouseout. The idea being that we have the event name and it is prefixed by, simply, on. And then the value of this attribute would be the JavaScript that we wanted to execute when that event occurred. Now, over time, we learned that that didn't scale very well, and so we started to separate our markup from our JavaScript to where we set up the event listeners in our JavaScript. Well, now in the world of UI libraries like Angular and React and Vue, we do it declaratively so that it is done inside of the template, which means that, once again, we are back to using what looks like attributes. But for Angular we bind events very similar to how we bind properties or attributes, except that instead of using square brackets, we use parentheses. And right off the bat, Visual Studio Code is going to pop up with a list of possible things that we might want to use. And this is not even the full list, over here's a scroll bar, so you can see how small that is. So there's a lot of events that we can listen for here, but notice the names of these events. They are simply just the event names. So blur, click, change. If mouseout was listed here, which if we scroll down we would see that, or mouseover, it is simply the name of the event. There is no on prefix or anything like that. They are also not camel cased because the names of events are all lowercase. So in order to bind to the click event, all we have to do is use the event name surrounded by parentheses. There we go, and then we assign the JavaScript that is going to execute. Now, this doesn't have to be a function. We can have a JavaScript expression that will just execute whenever we click on it. But I think it would be useful to have a function so that we can do some extra things here. So I'm going to say that whenever we click on our checkbox, we are going to call a function called toggleItem(). Now, this is actually going to be a method on our component class. So we need to go to app-component and we need to add the toggleItem method. And for right now, let's just write a message to the console that this was clicked. So let's save that, let's go to the browser. I already have the console pulled up, so whenever we click on these checkboxes, we can see the text, clicked, in the console. So that's great. So when it comes to DOM events, one of the things we typically do is access the event object that occurs because that gives us a lot of information about the event. Now, that's not going to be given to us by default. What we have to do is pass the event object here, and we do so using a special identifier. It is simply the dollar sign followed by event. So there we go, that will pass the event object to our toggle item. Now, notice we are getting an error here. And that's because as it is right now, the toggleItem method doesn't accept an argument. But we want an argument, we're just gonna call it e, and let's type this as an any object, and then let's just write out e to the console. So now whenever we click on these checkboxes, we are going to see the event object and we can see all of the information that comes with that. So, of course, there's the client coordinates. We can see if the control key was pressed whenever I clicked. If we scroll down, we can see the type of event that occurred, which of course, is rather obvious, but sometimes you never know. We can see the targets, which this is in alphabetical order, it ought to be easy to find, the target's right there. So everything that we would need for that event, we get access to with that event object. But I think in our case we don't even need that. All we really need is the item that we want to work with. So we can pass item to toggleItem, and that is going to give us the ability to manipulate that item object because that item object comes from our items array. So we are working with all of the same objects here. So let's type this, first of all, as a WishItem. Let's call this item, as well. And before we do anything, let's go ahead and let's see what that item is in the console. And we will see that the binding that we set up for the checked property is a one-way binding. So let's click on the Get Coffee wish, which of course, unchecks that. But if we look in the console, we can see that the wishText, of course, didn't change because, well, that makes perfect sense, but isComplete is still true. And if we click on Get Coffee, once again, isComplete is still true. So property binding is just setting up a one-way binding. That means that we need to update the isComplete property ourselves. And we can do that very easily, since we have the item that we want to work with, we can simply set the isCompleted property equal to its opposite, and that way our data is going to stay in sync with the UI. So let's let this reload. Let's click on Get Coffee. We can now see that isComplete is false. If we click on it again, isComplete will be true. So setting up event listeners is very simple. We do so inside of our template using the event names surrounded by parentheses. And then we simply assign the JavaScript code that we want to execute when that event occurs. If events are the most important part of a graphical application, then forms, or at least working with forms, is a very close second because events allow the user to interact with our application. Forms allow them to provide data to our application to work with. Now, Angular lets us work with forms in two different ways. The first is called reactive forms, which is the more robust and scalable approach. There's also a lot more setup involved, and we will eventually work with reactive forms. But for right now, we're going to take the second approach, which is called template-driven forms. It basically allows us to define our forms inside of our template, and then we just rely upon directives. That allows us to manipulate the underlying properties or objects that are bound to our form. So I just pasted in the markup for our form. It's very simple. We have just a simple text field for the wish text, and then a button that the user will click on to add that wish to the list. And let's start with the easiest part, which is going to be handling the click event on our button, because we already know how to do that. So let's just start there. And the first thing that we need to do is prevent the form from being submitted, because that is the default behavior of clicking a button inside of a form. At least, as far as this is concerned, it's just basic form processing, preventDefault(). We'll use the $event object to get access to preventDefault(), and there we go. Now, the beautiful thing about this type of syntax is that we can execute multiple statements. So this is going to be a single statement, and then we could call a method to add the wish to our list. We'll call this method, addNewWish. And of course, we need to add this to our component class. So let's do that before the toggleItem. This is just a personal preference of mine, inside of a class, I'd like to group all of the like things together. I want to group the properties together, I want to group the methods together, and then I want them in alphabetical order, because I'm OCD about that stuff. So, of course, the code inside of this addNewWish is simply going to add the wish to the items array. And then, ideally, we will also clear the text box so that the user doesn't have to clear that out if they want to rapidly add new wishes. So that should work, and we're good. So now, what we need to do is find a way to bind data to this input element. And we do that with syntax that's going to look very similar, because we essentially combine both the property binding syntax and the event binding syntax together. We start with the square bracket, followed by the parentheses, and then we have a special directive called ngModel. Now, this is very similar to what you would find in React and Vue. We simply are telling Angular, hey, we have this form field that we want to bind to a property on our class, and we can just call this newWishText. So let's take this property and define it inside of our component class. So I'm gonna put this after the items. Now, of course, down in the terminal, we see that there's an error, and that's because we haven't defined this newWishText property inside of the class. But even after we do that, we will still end up with an error. So let's hop on over to the browser, and let's see what that error is. It says that it can't bind ngModel since it isn't a known property of input. Now, if you rush on over to the documentation, it's not going to give you any hint as to why you are getting this particular error, which is frustrating, but it is what it is. So instead, what we need to do is open up the app.module file. Now, we haven't talked about this file, because we haven't needed to, but now we do. This is a very important file, because it defines practically everything that our application requires in order to run. Now, the first three statements are import statements. It's importing the NgModel decorator. It's importing this browser module, which if you hover your mouse over, it says that it exports required infrastructure for all Angular apps. So it's required infrastructure for our app and every other app. But then there's also the AppComponent, which is the component that we are working with. But notice the use of the NgModel decorator. There's an object that's passed here that defines the declarations, the imports, providers, and bootstrap. So first, we have declarations. This is a set of components or directives that belong to this particular module. So in the case of our application, we have this AppComponent, which belongs to this module because it has been declared here. The imports are things that are available to the templates, inside of this module. The providers are objects that can be injected to parts of this module. And then bootstrap is essentially what is bootstrapped. This is the entry point of the module. And so in our case, once again, it's AppComponent, that's the entry point of this module. So what we need to do in order to use the NgModel directive, is import the FormsModule, and Visual Studio Code is being very helpful and allows us to import that with a single click. That comes from @angular/forms. By importing the FormsModule, we now have access to that NgModel directive. Not just inside of our AppComponent, but for any other component that we would define inside of our application as long as it is part of this AppModule, and then the error goes away. So we are now a step closer to making this work. Let's go back to our component code, and let's do something inside of addNewWish. So we have now bound this newWishText property to that input element. So whenever the value of that input element changes, it will be reflected here inside of this newWishText property. So we want to add this newWishItem to our items array. So we will simply create a new WishItem, the text is given to us, thanks to that NewWishText property, and the Boolean value signifying whether or not the wish is fulfilled as optional. And I would imagine that to any new wish would just be unfulfilled by default. So that's all that we should have to do. And then we could set the value of newWishText equal to an empty string. Essentially clearing out that value so that the user can then have a fresh form and they could type in a new wish if they wanted to do that. So let's go back to the browser, and let's test this out. We wanna wish of $100 billion. I think that's right, and [LAUGH] let's click on Add Wish. Okay, well, a wish item was added to our list, but there's no text there whatsoever. So let's pull up the developer tools, and let's take a look at the console, because it's going to give us a very descriptive error. And we can see right here, if ngModel is used within a form tag, either the name attribute must be set, or the form control must be defined as standalone in ngModel options. So this is a very simple fix. We just need to go to our template. Let's add a name attribute to our input element here. And it doesn't matter what this is, let's just give it the name of newWishText, or let's not. Well, let's still do newWishText, but let's use hyphens in between the words there, just so that we can see that there is no relationship between the name and the property that is bound to this input element. So with that simple change, we can go back to the browser. We want $100 billion. And whenever we add wish, there we have that wish item. And so, now that we have this functionality, let's go back to our component. Let's comment out those hard-coded wish items because now we have the ability to add items on the fly. And this also gives us the ability to actually test and make sure that this application is going to work how we would expect it to. So, let's go back to the browser. I do want to add another div here with a class of row. Let's also set some margin to the top here. And this div element is going to serve as the container for the content or rather for the list, I should say. So we'll have our list inside of this div element, we'll also have that message if there are no wishes to display. Just so that there's a little bit of white space between that and I like that. So, now I wish for coffee, and voilà our application is working as intended. Sometimes when you use ngModel, you want to be notified when that model values changes. For example, in the previous lesson, we used the ngModel directive on our input box so that the value of this input box is bound to a property in our component. And so, we could also set this up so that as the user types, we are notified of that change and we get that value. Now, I don't necessarily want to do that for the input box, but I do want to use this functionality to implement a filter. So that the user can filter the list so that they can see all of the wishes, only the fulfilled wishes, and also only the unfulfilled wishes. So, the first thing I want to do is uncomment these hard-coded wish items because this gives us some data to work with. And we don't have to manually, [LAUGH] add them every time the page reloads, that would be rather annoying. The next thing I want to do is add the markup for the UI. And this is going to be just a simple select element that has three possible options, all, unfulfilled, and fulfilled. Now, the values are arbitrary, we can technically use any string value that we would want to use. I opted to use numeric values because I figured that that would be a little more simple. And since we're here, let's go ahead and let's use the ngModel directive to bind this to a property on our component. Let's just call it listFilter because that is exactly what it is. Let's go ahead and define that inside of our component. I'm gonna put this after the items array, and this is a string because the value of this listFilter model comes from the options value attribute, which is always a string. And we're going to initialize it as an empty string. Now, let's go to the browser and we are, of course, going to see this combo box. Notice that there is nothing selected by default and that makes perfect sense because if we look at the markup, there was not the selected flag on any one of these options. So, of course it's not selected. However, since this is now bound to this listFilter property, we can use this value to set the default. So, I think all should be the default, we can set the default value to 0 and that is going to automatically select the all option in the combo box. If we set it to 2, then that would set it to fulfilled. I guess some people would want to see that by default, but I don't think so, I think all is a good starting point. So, now that we have that all done, we need to set up our select element so that it uses a special event called ngModelChange. This is an event that only occurs when the model changes. So you can only use this event on elements that use the ngModel directive. Otherwise, it's pointless because it is only for when the model changes. And that's not the only special thing about this. The events object for this event is not your typical event object. This contains the new value of the model. So as the user selects a different option, the value of that option is going to be passed to whatever method we choose, which I've called this filterChanged and this dollar sign event is that value. So let's go ahead and define this inside of our component, and we can write out that value to the console. So I'm gonna call the parameter value, I'm going to type it as any. And then we will simply write out that value so that whenever we go to the browser, by default, we're not going to see anything because the model value hasn't changed. However, if we change it to fulfilled, we can see the value of 2 being written to the console. If we change it to unfulfilled, we see 1. If we change it back to all, we see the value of 0. So, we can use this functionality to essentially filter the items that we see in the browser. However, we don't want to actually filter the items array because this is our single source of truth when it comes to our items. So what we could do is have a visibleItems property, which will be essentially a copy of our items array, except that this will contain our filtered items. So we will initialize it with the items array. And then inside the filterChanged, you will set a new value for visibleItems. And of course, that's going to be based upon the new value of our listFilter. So, if it is 0 then of course the visible items will be all of our items. Then we can have an else if to check if value is equal to the string of 1 in which case, we'll set visible items to a filtered list of items. And that filter is going to be based upon if the item is not completed because this is for showing unfulfilled wishes. And of course, if neither of those cases are true, then what we want to do is display only the fulfilled items. So there we go. All we need to do now is use our new visibleItems in our templates. And really we want to do this in two places whenever we check the length of the items. Because it's possible that the user will select a filter and there will not be any wishes to display, in which case we want the message to say that there are no wishes. So we will want to use visibleItems there. And then whenever we display the list of those items, we only want to show the visibleItems. So, let's go to the browser and let's check this out. Of course, we are displaying all items now, but let's change the filter to unfulfilled. We see unfulfilled. Let's change it to fulfilled and we see the fulfilled item. Now, on the surface, it looks like this is going to work for us, but there's quite a few bugs here that we would need to sort out. First of all, I have unfulfilled selected. So of course, we only see the unfulfilled items. So, we want to wish for $100 billion. And whenever we add this wish, we don't see it listed here. And the reason is very simple, because while we did add that wish to the items array, the visibleItems array, Was not updated. So if we refresh this filter and go back to unfulfilled, then we will see that item. The second bug is that if we complete or uncomplete any one of these, once again, it's not going to automatically update what we see in the browser because we have to essentially change the filter so that the visible items array is updated with the new set of items. Now those are bugs that we can pretty easily fixed. We would just need to apply the filter anytime that the main items array was changed. However, there's a better approach. We can turn our visible items array into a getter, which will allow us to react to just about any change that we make to the original items array. And we'll look at that in the next lesson. In the previous lesson, you learned about the ngModelChange event and how you can use it to react to when the ngModelValue changes. And it's a wonderful tool. It's something that you'll use quite regularly, I would imagine, in your applications. However, I don't think it was the appropriate tool for us to use to implement our filter. Because if you'll remember, I talked about two bugs. And we could get around those bugs with minimal effort because all we would need to do is be sure that the visible items array was updated any time that we made a change to the items array or to the objects inside of the array. And so instead, what we can do is use a feature not from Angular, but from JavaScript, we can repurpose the visible items to be a getter. So that it returns an array. But that array is of course, going to be dependent upon the filter. It's either going to return all or the items, only the fulfilled items and only the unfulfilled items. So we can essentially take the code from our filter change method, and we can paste that inside of visible items. But let's do this. Let's create a variable core value, we will initialize it with the value from the list filter. That way we don't have to change value all of the time, and instead of setting a value here, all we need to do is return. And the beautiful thing about this is that now we are basing everything off of the items array so that anytime that the items array is modified, or the objects inside of it are modified, it's going to automatically be reflected within the browser. We don't have to write any extra code to make this work. Now, unfortunately, this means that we don't need to use the ngModelChange event. So we can get rid of that FilterChanged method. We can also get rid of the ngModelChange event on our select element. But with that very simple change, we are going to have the functionality that we wanted, and we aren't going to have the bugs that we had in the previous lesson. So let's go to the browser. Of course, we see all of the items. Let's make sure that the filtering works, which unfulfilled works and fulfilled works. So the first bug was adding an item. So let's go to the unfulfilled items and let's add in our wish for $100 billion. And whenever we add this item, it should automatically populate here in our unfulfilled item list. And it does. So that's perfect. That worked exactly as it should have, and we didn't have to write any extra code to make sure that the visible items array is in sync with the items, because now visible items is completely dependent upon the items array. So now let's test the other bug. If we uncheck Get Coffee, that should disappear from this list and it does. We see our message saying that there are no wishes to display and if we view the unfulfilled of course, Get Coffee is there. If we check this again, it should disappear and if we view all of them, then Get Coffee will be the only one selected. So there we go. We have our filter completely implemented. We don't have to do anything extra to get the functionality that we want. Now, I think we can make our code just a little bit cleaner if we do something like this. I'm going to define a variable called filters and this is going to be an array of our filters. So basically what we want are functions that are going to be used as the callback for the filter method. So that for index 0, that is to display all of our items, a callback method is simply going to have something that's truthy so that it will display everything and that's going to work. Let's type our parameter here to wish item, and let's use this as a basis for the other filters. Now the second filter is for unfulfilled items so we want to check and make sure that is complete as false, otherwise we want is complete to be true for the fulfilled wishes. So that's inside of our visible items getter, we can essentially get rid of all of this code and we will do this. We will return items, we'll call a filter, but then we will use the filters and the index is going to be the list filter. So this should work or maybe it won't, but we might have to change list filter to any, so let's do that. Let's go back to the browser. Let's try this out. So of course, all works. Unfulfilled, appears to work. Fulfilled, appears to work. If we uncheck Get Coffee, that disappears. There it is. Let's add an item. I'll be happy with a 100,000. So let's do that. Let's add it, and voila. So there are times when using the NGModelChange event is appropriate. There are times when it's not appropriate. And for our filter feature, a simple getter was the appropriate approach. We haven't really done a whole lot with our application, but already things are becoming a little cluttered, both from the code aspect as well as the template. And we essentially have three distinct pieces. If we really think about it, we have the user input, we have the filter and then we have just displaying our items array. And the reason why I say that these are three distinct pieces is because they essentially have their own functionality. The user input is strictly for getting user input and modifying the items array. The filter is for, well, is for filtering the items array and then the list is for displaying the items array. Now, of course, they all have that items array in common, but they do different things with it. So first of all, it's very easy to get stuck in the mindset of only creating components for things that can be reused. And yes, it is perfectly fine to create components that can be reused, but we use components for a variety of different reasons. Most of all, we use components because it organizes our application Into smaller pieces, which makes it easier to develop and maintain and add more features. So starting in this lesson, we are going to create three components, one for each of the three pieces of our application. And in this lesson, we're going to start with just the list, because that's the easiest thing that we can do. So essentially what I want to do is take the list out. Now it's more than just the list. It's also the message that will display that there are no wishes to display because I think that the list should be responsible for that. So I wanna take this out so that here inside of our app component, we would use something like this. It would be simply wish-list, and then we could supply the visible items to a property on this component called items. So we would have visible items just like that, bound to a property called items. And then that simplifies this component's template. And it will also help simplify the code for this component, because the toggle item method can be extracted out and I think that is the only thing that we need as far as displaying those items. So now let's create that component. We can do it manually but there are steps involved if we are creating the component manually. Not only do we have to create code for it but then we have to import components so that we can use the component decorator so that we can define the selector and the template. We don't necessarily always need CSS, but it could be useful. But we also need to modify the module file because we need to import any new component that we add so that we can declare that so that we can then use it within our application. Because if we don't declare it, then it's not going to know that that component exists. So it's a lot of things that we have to do and thankfully the Angular CLI gives us the ability to generate a component. We use the ng command, we follow that up with generate component, and then we specify the name of that component. I'm gonna call this wish list, which is going to make things much more confusing because our application is called wishlist. But for something that displays the list of wishes, I can't really think of anything better as far as a name is concerned. Now, here in the command line, we can see that there are four files that were created. They are inside of a directory called wishlist which is a subdirectory of app. And we have a template, we have a spec file for test, we have the code file and then we have the CSS file. So all four of those files were automatically created for us and then the module was updated. So let's take a look at that and we will see that on line six we now are importing that wish list component and on line 11, it was added to the declarations. So we didn't have to do any of that, the angular CLI did that for us. So great. I love tools that make our lives easier. So now let's go to our wish list directory. Let's open up the template. I'm going to paste in that code that I took out of the app components template, because this is essentially the template, for this new wishlist component. Now, this isn't working with a property called visible Items this is going to work with a property called Items. And the reason why is because well, that's what I want to do here when I changed the apps template to just wish list and to use this items property. We can make this whatever this is completely arbitrary, but for me, personally, it makes sense to call this Items. I guess we could call it Wishes if we wanted to do that. And let's do that. That actually might be better. So let's go back to the Wishlist template, and let's change that so that we will be working with. Wishes instead of items and we also need this toggle item method. So let's go to the apps code let's take out that toggle item method, we're gonna cut that out and we're going to paste it inside of the wish lists component and we also need to import the wish Item class. Now, we still need that inside of the app component, so we're gonna leave it. We're just gonna copy that line where we import that and we will paste it inside of the wishlist component. And so now we just have errors inside of two files. One is the app components template, so let's take a look there. We're going to see that the error says that wishlist is not a known element, and that's interesting because we just created it. However, if we take a look at the wishlist component notice what the selector is. It is app-wish-list. That's a little difficult to say. The reason why we have this app prefix is because this is the app module. So some people leave that. I typically don't unless if there's some kind of naming collision because that's exactly what this is for. This is to help avoid naming collisions but we're only going to have one wishlist. So let's just call it wishlist so we'll save that we can go back to the apps template and we're going to see that that error goes away. So we're good there, but we still have another one that it can't bind to wishes since it isn't a known property of wish-list. Well, that makes perfect sense because we haven't defined a property called wishes. However, it makes us think that we could get away with doing something like this to where we define that property which is going to be typed as an array of wish items and that's going to get rid of one error because we had the same error inside of the wishlist's template. If I took out that wishes property, then we would have an error on line one where we used wishes, we would have another error on line seven, where we used wishes as well. But that didn't take away the error from the app component. It still says, can't bind to wishes. Well, here's the thing about components that have input. Now the way that this is written, we are providing input to a property called Wishes for the Wishlist component. But we have to explicitly say that wishes is input. And we do that by going back to our wishlist component. And we are going to use an input decorator. We need to import this from Angular core and then we simply decorate the properties that we want to use as input. So wishes is going to be input. So we will use the input decorator before that. And by just that simple change, the error goes away, we can see that our application compiles. And whenever we go to the browser, well, it doesn't look the same because the styling is a little bit different. And we did add some CSS, didn't we? So let's open up the CSS for the app component. Let's take out this wishlist class, and we're going to put that inside of the wishlist components CSS. Because it makes sense to put this here. This is for the wish-list itself, so we can go back to browser. And you're seeing those dots and then seeing them gone does kind of accentuate how our list is indented over. So let's get rid of that padding. So we'll set padding zero and that should scoot that a little bit over. And it does. I would like that to be flush, but well. So everything works as it did before but our application is just a little bit more organized. And in the next lesson, we are going to improve that organization even more by creating another component. The next piece of functionality that we will extract into its own component is the form at the top of our application, so the user input form. So that whenever they type into the text box, click on the Add Wish button, of course everything will still work as it did before. Now, this is going to be a little bit different than the wish list because there is no input, at least as far as The form is concerned. There is, however, output. Because when the user clicks on the Add Wish button, our component needs to tell the app component, that, hey, I have some new data for you to work with. And the way that we do that is by essentially creating an event. So let's, first of all, generate our new component, and let's just call it add-wish-form. To create an event, we have to use what's called an event emitter so that we can emit or fire that event whenever we want to emit it. So let's start with the template here. Let's cut this out of our app components template, and let's write out what we want to do. So we'll have the add-wish-form, and we want to bind an event to this. So we're going to use the event binding syntax. And whenever you think of event names, they are typically actions, first of all. They are also typically in the present tense, such as click, mouseOver, mouseOut, keyDown, things like that. So let's call this event addWish, and then we will have some code that's going to execute whenever this happens. So let's go to our Add Wish form. Let's open up the template, and then let's paste in the form. And I don't think there's really anything that we need to change as far as this template is concerned, because we still need this newWishText for the model. We still want to execute some code whenever the user clicks on this button. So we still need to handle the click event and emit our own event. So we'll leave this as is. So let's go ahead and close this file. So let's extract the code from our component. So we essentially need to cut out the newWishText and the addNewWish method. So let's just cut that out, and let's put this inside of the code for our new component. Now let's change the selector. I want this to be just simply, add-wish-form. And we need to address line 18 here because we now do not have access to the items array. In fact, as far as this component is concerned, the items array doesn't exist. So this is where we will emit our event, but, first of all, we need to import two things. We need the output decorator, so just like with an input for a component, we have to decorate output as well. And then we need to import the EventEmitter class. Now one last thing we need to do is import the WishItem class so that we can work with that inside of our file. And that should be it, so that now all we have to do is just write the code that's going to create our event and then emit that event. So we'll start by using the output decorator. We use it just like the input, except, well, it's called output. And then we have the name of our event, which was addWish. And we're going to new up the EventEmitter constructor. And we can specify the type of event object that we are going to pass to it. Now, here we can do several different things. We could say that the event object is going to be the text from the text box. We could say that we are going to pass a brand new Wishitem object. And I think that that's probably the best approach. So that whenever we emit this addWish event, we will pass it a new wish item object that contains the new wish's text. So that's whenever we call this addNew wish method, we will use this addWish event. We will emit it, and then we will pass in our Wish item object. So that should be it as far as this component is concerned. So now the only other thing that we need to do is actually do something whenever this addWish event occurs. So let's go to our app component. Now we could define a method, we could say that we want to call a method, addWishToItems, and then we can define that inside of our code. However, with something as simple as this, it would be far easier and probably more readable to just do this. We will push into our items array, the event object, which is a wishItem. So let's save this. It does look we have an error inside of the template for our wish item, but okay, that went away. So let's go to the browser. Everything looks it's working like it did before. Let's do a hard refresh just to make sure everything is loaded as it should. And then let's type in a new wish. I want $100,000, so I'm going to wish for this. We're going to add it, and there it is in our list. And, of course, the filter still works because we didn't change any of that functionality. So now let's very briefly to look at our app components code. Now of course we still have a lot of things here, but little by little, as we start breaking out that functionality into other components, this becomes much more cleaner. And as we start breaking out the functionality for the filter, the code for our app component will be very, very clean. Extracting the functionality for the filter is going to be relatively straightforward. In fact, it's going to be very much like the add wish form, in that we won't have any input, we will have an output, because the app component needs to know which filter is selected. And in order to make all of this work, we are going to lean on the ngModelChange event. Now, if you'll remember, we originally used ngModelChange to implement the filter, and we ran into some issues. And in this particular case, we will run into issues, but it will be very easy to fix. So let's create a component, we'll just call it wish-filter. And let's start with the template. So we want to extract practically everything here, except for this containing div. That isn't necessarily part of the filter, that's more for the structure of the page. So everything inside of that, we'll just cut out, and let's say that we will have that wish-filter component. And we will listen for an event, I guess we can just call it filter, and then we will do something whenever that occurs. So let's hop on over to our wish-filters templates. Let's open up that file. Let's paste this in. And for the most part, everything is going to be the same. We still need the ngModel for our select element because we need to know what the value is for that form control. But we are also going to use ngModelChange, and we can go ahead and define a method called changeFilter that will give us the new value for our model. So let's take this method name and define that inside of our code so that we have it. And this is going to be useful for us because this is where we will emit our filter event. So let's go ahead and write that code. Of course, it's not going to work because we haven't defined that filter event yet. And we don't necessarily know what we want to emit at this particular point. Well, in fact, we do. We want to emit the callback function that's used for the filter method on the array. So there's a few things we need to port over from the app component. First, we need to import wish-filter, so let's go ahead and do that. We also need these filters, so let's cut that out. We don't need that inside of app anymore. That's going to be inside of our wish. Filter component, so we'll paste those in there. And then we need this list filter. We don't need that inside of our app component, so that should be it as far as what we need to port over. Let's change the name of the component to just simply wish-filter. And we need to import the output decorator, as well as the event emitter. So that we can define our output, which we called filter, and we will new up the event emitter class. As far as the event object, let's just use type any. That way we can pass anything to it like a function and that should be good there, so that whenever the filter is changed, we will just simply emit the given filter. And that should do it for this file, at least for right now. So let's go to our app component. Now, one thing we do need to keep track of is the filter that's going to be applied for the visible items. So we can go ahead and we can define that property. We can have it set as type any, and we can initialize it as a function so that we could go ahead and we could pass that filter to VisibleItems, or not pass it but use it inside a visible item. Now I don't think we need the title property. Let's get rid of it. We should have done that a long time ago. And we do need to change the value of filter, but we can do that inside of our template. So that whenever we bind this filter event, we could just go ahead and say filter, is equal to the event object. So it's declarative, as we are reading the template we know, well, we mostly know what's going on. And it should work, but not exactly. So here we are, we get the message that there are no wishes to display and we know that there are wishes to display. And we also know that the all filter is selected. So we should be seeing something here but whenever we change the value of the filter, we can see that it's working just fine. So this is the problem that we are going to run into. And the reason why we are experiencing it is very simple. It's because of this. Line 19, we initialize the filter as just an empty function. And so when that is passed to the filter method on the items array, it's returning undefined. Therefore, none of the items are being selected for visible items. So we can get around this in a couple of different ways. We could just return true for the filter and that is going to be a very quick fix there. However, I don't necessarily like that approach. Instead, I would prefer to rely upon the filter component to automatically provide a value to the filter inside of app. And the way that we can do that is by going to the components code. Now, if you'll notice, for every component that we've created, it has implemented OnInit. And it has provided this ngOnInit callback method. And you can think of this essentially as this is the initialization for this particular component. So what we could do here is call change filter, and then pass in the string of 0. Or we could emit the filter event, passing in the first filter. Either way, it's going to get the job done, so that whenever we save this, we go back to the browser. Let's do a hard refresh in the browser, and we can see that it's working as it should. The all filter is being applied to the list of items automatically, and of course the filter is still going to change. If we modify any one of these items, we can see that the item list is still working as it did before. The same is true for whenever we add a new item, so we want that, okay, I'll take $1,200, so we'll add that wish, it's automatically added to the list. Now one other thing, let's go to the app component. We could get away without defining this visible items whatsoever. It's useful to have it here, but one of the things that we have been doing as we have created these other components is to simplify everything so that a lot of the functionality is defined declaratively here in the template as opposed to programmatically. So instead of using visible items here, we could simply filter the items based upon our filter, which means that we will be able to completely get rid of the visible items so that the code for our app component is now I'm not gonna say it's the cleanest that it could be, but it's pretty darn close. And just to prove that this works, let's hop on over back to the browser. Let's do a hard refresh and we can see that the filter is automatically applied. If we change the filter at all, we get the results that we would expect. Over the past few lessons, we have discussed inputs and outputs on our components, but we haven't necessarily talked about how data flows from one to the other. And even though it is very simple, it's also important to understand this concept, so let's take a look at the app component markup. And I want to focus on where we use the wish list component. So in this case app is the parent, wish list is the child. And wish list has a property, it's the input. And we are binding the items from the app component, the parent, to that wishes property. So in this particular case the flow of data goes from the parent to the child, and there is no limit as to how far down we can keep passing that information. Like for example later on we are going to create another component, one that represents an individual list item element inside of the unordered list in our wish list component, where we will take the individual item that we are working with and then pass that data on down to that child component. So you can keep passing data down as far as you need to for as long as you need components that need to work with that information. So let's go back to the app component and let's look at the add wish form and the wish filter. Now we created events that emit data whenever those events occur. And that is setting up the child to parent communication. So with inputs, the information flows from parent to child, for output, the information flows from child to parent. But let's open up the filter and let's look at NG model. Now, of course, we've used NG model in other places, but we are going to focus on the wish filter component in this lesson once again, because ng-model is a little different. Even the syntax suggests that it is, because it uses both the property binding as well as the event binding syntax. So that seems to imply that this is a two-way binding, and it is. We have seen that happen. So what I want to do is modify our wish filter so that it too has a two-way binding. And the reason why I want to do it here is primarily because of what we do when this filter event occurs. All we are doing here is setting the app components filter property equal to the filter that was provided through the event. It's very simple, so in this particular case it would make a lot of sense to set up some kind of two-way binding And we can do that very easily by just using both input and output within our component. So the result is going to look like this. Not only is it going to simplify our template, but it can also simplify the code in our component class. So that all that we really need to do is just define the filter property, say that it's going to be of any type, and we're good to go there. So we can actually close app component and its templates. And then we will focus on the wish filter component. In fact, let's open up the class because that is where we are going to spend most of our time. So in order to create or to set up a two-way binding for our own components, we need both the input and the output decorators. The input is going to be the property name. So in this particular case, it would make sense that we call our property, filter. I'm going to go ahead and say that this is of type any. We don't have to initialize this if we don't want to. We can, but in our particular case there's really no need to. Now, of course, setting up two-way binding means that we have an input and an output. So we need to change the name of our outputs, but there is a rule that we have to follow so that the Angular will recognize that the input and the output are linked together. And that is, the output has to be called the same name as our input, followed by, Change. And whenever we do this and save this, the error inside of the app component should go away. Because Angular should now recognize that this is a two-way binding. There is the input and the output, but that error is not going away. So let's take a look and see what this says. It did go away. I just wasn't patient enough. Now, of course, because we changed the name of this event, we're going to have to adjust our code. And I'm not so necessarily sure that I want a filterChange event and then a method called changeFilter. So let's call this updateFilter. And whenever we update the filter, we essentially want to do two things. We want to set the filter property equal to the new value that it should be. So in this particular case, it's going to be the given filter at the given index. And then we will emit the filterChange event. We will pass in the filter property as the event object, and that is going to make this work. Now we do have this OnInit, and instead of emitting the filterChange, what I want to do is call the updateFilter. I'm going to pass in the string of 0, that is the default filter that we want to display. And then we need to update the template for our WishFilter so that it will call this updateFilter method on the ngModelChange event. But with that in place, we will have the same functionality as we did before, but our code is going to be a little bit cleaner. So, here, of course, we have our UI. The default is the All filter. Let's change it to Unfulfilled. Once again, we see the unfulfilled items. And if we change any one of these check boxes, that list is going to automatically update. If we add a new wish, then that will of course update the wishlist. So let's briefly talk about how this works. So let's go back to our app component, and here we define this filter property of type any. And we are binding this to the filter property on the wish-filter. So when our application first runs, it's going to take the value of this filter property, and it's going to pass that down to the child's filter property. So initially, the input filter on the WishFilterComponent is undefined. Not necessarily because we don't define anything here, but because it is bound to that filter property in the app component, which is undefined. However, when ngOnInit executes, we update the filter. And when we do, we change the filter property to the given filter. We emit the filterChange event with that new filter, that in turn updates the filter property on the app component. Now, of course, we don't want to set up two-way binding on anything and everything, only where it makes sense to do so. Earlier, I mentioned that I want to extract the functionality for the individual items in the wishlist component into its own component. Now this is not a requirement. There's nothing that says that we need to do this. However, from my own personal preference, if I can extract something into a component, I typically do so just because that makes my application a little bit easier to maintain. And a lot of times, I end up wanting that functionality because I will need to add features or something. Now, I am a firm believer in not writing code that I don't need to write. But there's just something about extracting functionality into components that I just can't get away from. So, what I want to do is this. So that instead of using the li elements here Inside of our list, we'll have a wish-list-item. And the first thing that we need is a for-loop here. So we won't need this for the li element. So let's just lift out that ngFor directive. And really we don't need the index either. That was primarily for demonstration purposes so that we could have something to bind to the custom data-index attribute. So let's just get rid of that. We can also change this item variable to wish, just to keep things consistent from a terminology standpoint. But then we need to supply the information that we want this wish-list-item to work with. And we can take one of two ways. We could have a property called wish that we bind the individual wish to, or we can provide the different pieces of the wish. For example, we could have a wishText, which we would bind to the wish's wishText. But then we can set up a two-way binding for the isComplete property, and we can call that fulfilled, and that will be isComplete. And let's take this approach because this gives us another opportunity to practice two-way binding. And when it comes to determining how to pass data to a child, there are no hard and fast rules. It really just depends upon what your particular needs are at that given time. And so I think that this is a worthy goal to attain. So let's create that component with ng generate component. We'll call it wish-list-item. And let's go ahead and let's cut out this li element. And that's going to be the template for this component. Now, of course, we are going to need to massage this template just a little bit, just to be able to work with the properties. Because we don't have the individual wish object anymore, we have just the pieces. So we will have the wish text there, and we'll leave everything else temporarily because I'm not exactly sure how we are going to implement this on the code side. But we do need to first of all import the WishItem class. So let's pull that in. Let's also change the name Of this selector, so we will get rid of that app prefix. We also need to port over the toggleItem, although we are going to need to make some changes to that. But then inside of our WishListItem, we can go ahead and we can define our input and output. We, of course, need to import those decorators. So we will have that. We also need to import the EventEmitter for our output. So for the Inputs property, we first of all need the wishText. Now, I'm not going to initialize this with any particular value, primarily because there's no need to, because it's going to be supplied by the parent. So I'm going to use the bang or the exclamation point, or the technical term is the non-null assertion operator. But basically, this is saying that this wishText property is a non-null property. It's primarily a trick for the compiler, because if we omit this and we don't initialize a value, the compiler is going to complain at us. But we know that this is not going to be null because it's going to be supplied by the parent. So that's why we're using the bang. Then let's define the fullfilled property. This is what we're going to use for our two way binding. And we can essentially do the same thing by using the non-null assertion operator, but this is going to be a Boolean value. But then we also need the output and its name will be fullfilledChange. And this is a new EventEmitter of type Boolean. Now, whenever we click on the checkbox, we want to reverse the value of isComplete. So what we'll do here is essentially the same thing, but we are going to use the fullfilled property. So we will set fullfilled equal to its opposite. And then we will emit the fullfilledChange event, and we'll pass in the new value for fullfilled. This means that we no longer need the item parameter because this is working strictly with class information. And let's change this, instead of toggleItem, it'll be toggleFullfilled. And so now that we know how we are going to make this work, we can go to our Components template, and let's change will be called toggleItem we want to call the toggleFullfilled method. And the checked value is going to come from the fullfilled property. And I think that's gonna be it. We should be able to do everything else. Let's make sure that our wish list template is okay. We look like that we are good to go. So let's hop on over to the browser. Let's do a hard refresh to make sure everything's loaded. And hopefully, we are going to see the same functionality. So at least as far as our All filter, everything is looking okay. Let's go to Unfulfilled, we see only one item, which that is what we would expect. Let's click on that. It goes away. So it looks like we haven't broken anything, which that's always a worthy goal to strive for. And now that we have extracted all of that functionality into its own component, I want to do some things with style. Because, yes, it's nice that we have this check box, and that is a very useful visual cue to tell the user, hey, this item has been fulfilled. But I want to do a little bit more. I want to add some styling so that it's very clear that an item is fulfilled or not fulfilled. So in the next lesson, we will look at manipulating style. One of the great things about graphical applications is that we can convey information to the user without using any text whatsoever. Like for example, we have a checkbox to denote whether or not a wish is fulfilled. If it's checked, of course, it is fulfilled, if not, then it is not fulfilled. And it can't get any clearer than that. However, I want to add some extra styling for fulfilled items. I want to strike out the text and make the color of the text just a little bit different, just so that there's even more difference between the two states. Now, when it comes to manipulating style, we as an industry, and I just mean in JavaScript and client-side development in general, we have decided that manipulating classes is typically the best way of manipulating style. It's not just an angular thing. It is an industry-wide thing. So let's define a class called strikeout. This will have the text-decoration property. We will set it to line-through. And now we just need to apply this class for the label elements for fullfilled items. And the way that we do that is with a directive called ngClass. Now, this uses the square bracket syntax. So this implies that we are binding something to a property called ngClass on label. And that is exactly what we are doing. So that means that whatever we use as the value for this ngClass attribute has to be a JavaScript expression. So that means if we want to assign a single CSS class, it would need to be a string. So we surround it with a pair of quotes. Let's save that. Let's go to the browser, and we're going to see that all of the items are struck out. That's great, except that that's not what we want. We want only the items that are fulfilled. So we can accomplish this in a couple of different ways. The first thing that we could do is use a ternary statement, because remember, this is a JavaScript expression, so we could check the value of fullfilled. And if it is fullfilled, then we will apply the strikeout class. Otherwise, we won't apply any class. If we go back to the browser, we are going to see that that is indeed what we get. And whenever we change the state of any one of these items, the strikeout class is either applied or it is removed from the label. And that's great, but this is also slightly difficult to read. There is something to say about defining CSS declaratively. But when it comes to the template of a component, I prefer concise, easy to read templates as opposed to using a lot of JavaScript expressions. So what we could do is define a property in our component. Let's define a getter. And let's just call it cssClasses. And for right now, we are going to return the result of checking the fullfilled property and returning the appropriate string. So now we have this cssClasses property that we could use to bind to ngClass. And whenever we view this in the browser, we are going to get the same result. Whenever we change the state of any one of these wishes, that strikeout class is applied or it is removed, and that's great. That's phenomenal. We can also add in the text-muted class. That's coming from Bootstrap, and that's going to give us slightly different colored text. So you can see that the Get Coffee wish is muted as opposed to just the normal text. But when it comes to applying multiple classes, we have multiple options. Yes, we can take this route and use a ternary. But we can also define our classes using array so that each element in the array is a class that we want to apply to the element. Now let's be good developers here, and let's return the same type. So we will return an array for cssClasses. One will be empty, one will have the classes that we want for fulfilled items, and here we can see we have the same results. That's fantastic as well. But we can also do this. We can return an object. And this is very useful because instead of using a ternary expression, this allows us to just return an object where the property names are the CSS classes. That we want to apply. So we could have strike out, and then the value is a conditional statement that determines whether or not that CSS class will be applied to the element. So we'll say that we want to apply the strikeout class based upon the value of the fulfilled property, and then we could essentially do the same thing for text muted if we wanted to. So let's go ahead and get that set up, so that whenever we view this in the browser, we will see the same results. But we can also make that approach just a little bit easier, because we could use both or all of the CSS classes that we wanted to apply based upon that condition. So now our code is just a little bit easier to read, at least In my opinion, that we want to apply these classes based upon the value of the fulfilled property. So once again, sanity check, let's make sure everything works, and it does. So when it comes to manipulating CSS classes, There are many different ways that you can do it. You can use just simple strings, you can use arrays, or you can use objects. I think I say this a lot, but there are no hard and fast rules as to which is more appropriate than the other. It really just depends upon your use case. I want to add the functionality to remove individual wishes from the wish list. So this means that we need to add a button for each item in the list, and then whenever we click on that button,. We issue an event. Now, the issue here is that we have the wish list item component which contains the functionality for the individual wish items. That's how I wanted it. But if we need to manipulate the array of wishes we have to do that inside of the app component because that's where it is defined. So these two components are not directly related so we can't necessarily define an event here inside of the wishlist item component and then listen for that in the app components. Now we can accomplish this in two different ways, the first would be to define a remove item event inside of the wish list Item component, so that we can issue that event and listen for it inside of the wish list. But the wish list itself really doesn't care about that event because it gets its wishes from the app component. So there's really nothing that we can do here except. A remove Wish event and then just forward that on to the app component. So we would need to define that remove wish event on the wish list item, and then on the wish list so that the app component could listen to it on the wish list. That's not scalable at all, that would be a nightmare, especially for very large applications when the event occurs way, way down the component tree. So the more feasible option than is to essentially create what's called an event bus. It's a global object that basically lets you issue events from anywhere within the application, and listen for those events anywhere within the application. And the way that we can do that is by using observable objects. Now if you're not familiar with the observer pattern that's very simple, you have an object that needs to supply data to other objects. So those other objects subscribe to the observer object, and then when those changes occur it notifies the subscribers, if that sounds like an event Guess what it is. In fact, every time we have used the event emitter, we have used an observable object. Except that in this lesson we are going to use the observable type directly. So let's go to the shared directory. Let's create a new folder there we'll call it services, and then inside of that services we will create a new file called event service. Now before we do anything here, let's go back to the wishlist item component and let's modify our template here so that we have our delete button. I'm gonna put this inside of another div element and it's simply just going to be a button that uses the button close class from bootstrap. This is going to automatically add that x, let's go ahead and define the type attribute, we'll set that to button, but remember that these divs are block level elements. So if we look at this in the browser, well, let's just go ahead and do that. We will see that the buttons are on their own lines and we don't want that. We want everything together. So let's use Flexbox, so we'll add the deflex class and then we will use the justify content between, that's going to put as much white space in between those two div elements. And you can see, there we go, but the close buttons are way over there. So let's open up the app components templates. Let's add another div element here, but we're going to use one of the column classes. Let's say that for the small size screen and above, we'll use the four column width. That should make this a little bit easier on the eyes so that we could see which close button goes with which item. That's a lot better. So with that in place, we should be able to go ahead and define the click event on this button. Let's say that we'll have a method called remove wish, and let's go ahead and define that, because what I'm trying to get to is, writing the code that I want to write. Because when I do that, I find that and I end up with much cleaner code. So inside of this remove wish, let's say that we're gonna have an event service out just called simply events. And we'll have a method called emit, because that's typically what we've been using to emit an event. And we will emit an event called remove wish, but then we need to specify something identifying for the wish that we want to remove. So in hindsight, going with the option of providing the wish text and the value for fulfilled probably wasn't the best approach. We should probably just use the wish object itself as input we'll do that in the next lesson. For right now let's just get this event bus working, so that we can have something to work with, so for the remove wish event for right now. Let's just pass in the wish text as our event object, so to speak, for this remove wish. Ideally, it would be the wish that we want to remove, but we'll get to that. So the concept here is going to be very similar to what we have with just normal events, in that we are going to emit an event called remove wish. And then we are providing the information involved with that remove wish event, which in this case is going to be the wish text. And then let's go to the app component, and let's go ahead and write the code that we will want for listening for that events. So we can do this inside of the constructor, so that we would have our events. And we would listen for the remove wish event, and then we would have a callback function because that's typically what we would have. We could define a method to do this, or we could just provide the callback function inline, so that eventually we'll get the wish, and then we will remove Wish from items, but for right now we'll just write out the wish to the console, just so that we can see what that is, let's go ahead and define wish as any. So that's the API that I want to use, so now we just need to implement that inside of the EventService file. So the first thing we need to do here is import two things. The first is called Observable, the second is called Subject. Now you'll notice that this is being imported from RxJS. This is the Reactive Extensions for JavaScript, which Angular uses extensively. And in fact, it uses Observable extensively, not just for events. Whenever we talk about HTTP requests, it uses Observable then It is used throughout the framework. Now, Subject is a special type of Observable object. This is essentially going to allow us to emulate typical events, so that we can have multiple objects that subscribe to our Observable object. Meaning that we can issue a single event and then multiple objects can be listening for that same event. So let's start by defining our class, we'll call it simply EventService. We don't need to inherit anything, but we do need the Subject, this is very important. We need the Subject which is going to allow us to essentially pass messages from the Observable Object to the subscriber objects. Now I use that term messages because that's essentially what an event is, an event is a message. In fact, we use messages for every piece of software that we write, we just might not see them working behind the scenes. All right, so we have the Emit method where we would have the eventName. We'll set that as a string and then we would have the payload for that event, which will be of type any. And then we also have the Listen method that has the eventsName, which is a string, but then we have the callback function which we'll just call back function. And then we will define this function as having an event, arguments and it will return void. Now emitting an event is going to be very straightforward, we're going to use this subject and then anytime we want to emit an event we call a method called next. We pass in the object that we want the subscribers to work with. So in this particular case, we'll have the eventName and then we will have the payload. So whatever object is going to subscribe to an event is going to get an object with these two properties. But we're going to simplify it for those subscribers because inside of listen we are going to use our subject and there's a method called AsObservable. And this is going to give us access to a subscribe method. And this subscribe method by default accepts a callback function that will accept whatever we pass to the next method. So whenever we emit any event this next method, we'll execute, it will pass in this object that has the EventsName and payload. That object will be passed to the callback function for the subscribe method. So here we will have a callback that gets, let's call this nextObj. It's a little bit more telling as to what it is, and then inside of this callback for function. We are going to check if the given events name is equal to the events name that comes from the nextObj. Because if those are equal, then we want to call the provided callback function where we will simply pass in the payload from the nextObj. It's a little bit convoluted, but if we sit and think about this, then it makes perfect sense. So the object that is going to emit the event calls the subjects next method. And it passes in the event object, so to speak, that event object has the EventsName and the payload. The object that is going to listen for that event is going to use the subjects as Observable method, because this gives us access to the subscribe method. So an object that is going to subscribe, we'll receive this event object with the EventsName and payload. So we simply check if the EventsName is the same as was provided to the Listen method. And if it is, then we execute the provided callback function and then pass in the event payload. Now all we need to do is add a reference to that service. So inside of the @ component, that should be inside of the parents and then shared, then services, and then EventService. Although, we need our events domain, so let's go back to EventService, and we want to export default new EventService. So there we go, that should fix the error there inside of the app component. So let's copy that line, let's paste it inside of the WishList item component. We do need to change this import path because we are deeper in the directory structure, but with that in place, we should be able to go to the browser. Let's open up the console and we have an error expression has changed after it was checked, blah, blah, blah. That's okay for now, that doesn't really affect us right now. So whenever we click on any one of these buttons, we should see. Well we, at least see something in the console. We see undefined, but we don't see necessarily what we expected, I expected to see the text of the wish, so something somewhere is not being passed correctly. So the first thing I'm going to do is add Debugger for emits, and then we can do that inside of the callback for subscribe, so we will add debugger there. So let's go back, and let's clear this out whenever we click on one of these buttons. All right, so let's inspect here, we can see that the payload is Learn Angular, so that makes sense. Let's continue on so that we will hit the next debugger. We see that the eventName is removeWish. The payload is Learn Angular, okay, so that's great. Let's start stepping through here. So we will step, the eventName should be the same as the New Object's eventName, so let's step over. Once again, we see Next Object has eventName as removeWish. Payload is Learn Angular, so, that is okay. Let's step into this, the wish is type any. We can see that that is Learn Angular, let's step over. So all of that should be fine. Why? Well, now we see Learn Angular. Maybe I just wasn't patient enough and needed to restart, I don't know. It's working now, so let's get rid of the debuggers. Let's save it, let's go back to the browser. Let's do a hard refresh to make sure everything is loaded, okay? Looks like it is, let's clear this out, so whenever we click OK. I don't know why we were seeing undefined before, but it is clearly working now. So we have our event bus, and this is fantastic because now we can issue events from anywhere within our application. And we can listen for those events also anywhere throughout our application. The only thing that we need to do now is modify our WishList item component, so that we work with the wish object instead of the individual pieces of the wish object. In order to complete the remove wish functionality we need some way to identify the wish that we want to remove because as it is right now the wishlist item component only works with the Individual pieces of a wish, so we can accomplish this in a couple of different ways. The first would be to supply some kind of unique identifier for each Wish. Which in a real application, that's what we would have, because the wish information would be coming from some kinda data store. However, I think it would be just fine if we forego the individual pieces and then just provide. The actual Wish object to the Wishlist Item. That way we can do whatever it is that we need to inside of the WishListItem component, which is really what I wanted to begin with. So I'm going to modify this so that we no longer have that Wish Text property we won't have the two way binding for the fulfilled. I mean yes, refactoring kinda seems like that we are undoing the work that we've done but it was also good practice just as this is also good practice for us as well. So as far as the CSS classes are concerned, we're gonna base that upon the wishes is complete property. That's gonna be fine. Then for the remove wish, now we can pass in the wish object that we will get. Then when it comes to toggling the fulfilled, we can leave this method here. There's nothing that says that we have to get rid of this. I mean I think ideally what I would wanna do is remove the Toggle Fulfilled method altogether so that we could declaratively have that right here for the click event, but this is gonna be just fine. So for the text we'll have the Wish that wish.txt for the fulfilled we'll set that to the is complete and that should be fine as far as this component is concerned we need to go to the wishlist components because we are no longer going to pass the wish.text. And this fulfilled property instead we will just have the wish property and that should work there so now all we need to do is inside of the app component we need to modify this so that instead of writing something to the console We will simply modify the items array. We will use splice so that we can have the index. So let's first of all do this. We'll get the index of the item that we want to remove. That'll be easier enough to do. And then we will remove that with the splice method. We'll pass an index and we only want to remove that one item. So that should work. Now Visual Studio Code is saying that there's still an error inside of the wish list. However everything compiled okay. So let's go to the browser. Everything looks okay. So if we Click on the Delete for Get Coffee. That should remove it. [LAUGH] And it does. That worked the first time that's incredible. How many times has that ever happened? Not very often. So if we remove everything everything's gone. So okay, I want $100,000. Let's add that. Let's change our filter. And if we remove that, there we go. We have the functionality now to remove individual wishes and we accomplish that by writing our own event bus now, the only thing that I wanna change now, this is how we get access to that event bus because as it is we have created that event object or that event service objects and we are exporting that And we're using that object wherever we need to. Wouldn't it be nice if we could automatically inject that into the classes that we want to use it in? Well, we can and we will look at that in the next lesson. Dependency Injection has become the norm in modern software development. It doesn't matter if it's server-side or client-side. It doesn't even matter what client-side framework you use because all modern frameworks have some kind of dependency injection service, and angular is no different. Now, if you're not familiar with the concept of dependency injection, it's actually very simple it injects dependencies. So, first of all, what is a dependency? Well, Let's take a look at our event service. We, of course, have this event service class, and then we export an object that we create. So we are creating an actual object that we are then importing inside of the wish list item component. And then the app component. We are using that object to listen for the remove wish event and to emit that remove wish event. This event's object, this actual object that we created, is a dependency in the app and wishlist item components. And we could say that it is a hard dependency, because we have actually created that object, and we are actually using that object inside of those components. Well, hard dependencies can be a little problematic, because it can make your code a little bit more difficult to maintain and update. But it can also cause some issues whenever you need to test your components. So, we want to use the Dependency Injection service so that we don't have to create our dependencies ourselves. All we have to do is tell Angular what it is that we want to use. And then it will automatically inject our dependencies. So for our wishlist item component, we'll say that we wanna import that event service class. Now, of course, this isn't going to work now because we aren't exporting that. But let's first of all, imagine that that's what we are doing. We are exporting the class. So that in the constructor, we say that we want an object called events, and we want it to be of type event service. We're going to say that it's private events, so that it will automatically create that property for us, so that we don't have to create that object. All we have to say is I want this wishlist component to rely upon this dependency and then Angular will provide that object for us automatically we could do the same thing inside of the app component to where we would once again Import the EventService class, and then in the constructor, we would say that we want an object called Events and it needs to be of type EventService. Now, in this particular case, we don't need to create a property because we are using events right here directly inside of the constructor. But that's the idea. We don't create our dependencies, Angular would do it for us. The first thing we need to do is go to our event service. We need to get rid of this export statements to where we create that actual object. And we are gonna export the class itself. But that's not enough. As you'll see, there is a compilation error. If we take a look at the browser, well, we are going to see an error. It says, no suitable injection token for Parameter events have class app component that basically says that's it can't inject the event service because it doesn't know that it can. It's not an automatic thing even though we want it to be an automatic thing so what we need to do is mark this class with the injectable decorator this is essentially going to tell Angular that this class can be injected into other classes, so we'll add that. Now, there's a little issue here. We're gonna have to go to the other files and we're gonna have to save these again so that the compiler Will recompile everything, but now we can see that it compiled successfully. Fantastic let's go to the browser and we don't see anything. Let's do a refresh here.once again, we don't see anything and we kind of get the same error that there's this injector error for at module event service. Blabbity blah, blah, blah, blah. Well, here's the thing, it's great that we used the injectable decorator here, but that by itself isn't enough. So we can do one of two things here. We can go to our AppModule and we could import that event service, and then we could add that event service to the providers array, so let's do that. We'll go back to the browser, and voila, we have that error of expression has changed. We'll ignore that. And as you can see, our code works just fine. So that's one way of doing it. However, whenever we use the injectable decorator, we can essentially specify where we want to be able to inject that class. So all we have to do is pass an object that has a property called providedIn, and then this gives us the ability to essentially say where we want to provide this event service. So we have a few options. If we wanted it to be inside of the AppModule and only the AppModule, then we could essentially do the same thing, except we could say that it would be AppModule here. We would, of course, need to import that here, but that would give us the same functionality that we just had by using the event service as a provider here. I don't want to do that. We could, but I don't want to. We could also provide a string called root. Now this is an application-level injector, so this essentially means that the event service would be available throughout the entire application. I don't have any data to back this up, but I would think for the most part, that's what we would want to do. However, there are other options. There's one called platform, because it would be possible to have multiple Angular applications running in the same page. And it could be possible that you would want that class to be injected or injectable throughout all of those applications. In that case, you would want to use platform. And then there's an any which, well, it's any. For our case, root is going to be just fine. So with that change, we'll be able to go back to the browser. Our code is going to work as it did before, so we can clear out all of the other things. Let's add the wish for $100,000, there it is. We completed it, which would be nice. And let's get rid of it. One of the most common things that we do within our client applications is interact with the server. We issue HTTP requests to fetch data or manipulate that data. And Angular is a little bit different than other UI frameworks like React and Vue because it has a built-in HTTP client. Now, yes, you can technically say that every framework has a built-in HTTP client because, well, we have the Fetch API that's built into the browser. And yes, that is absolutely true. But if you've built applications before, you know the benefit of using an HTTP client library, something that makes it a little bit easier to work with the Fetch API. So React and Vue do not have a built-in HTTP client. You have to use a third-party client, or you can just use the Fetch API. For Angular, we can import the HTTP client module. As I mentioned, it's built-in. We just need to import it from @angular/common/http. And after we import that, we can add it as an element to the imports array. I am inside of the AppModule file. So we are adding this as an import just like we did with the FormsModule. And by doing this, we make the HttpClientModule available throughout our application, so that then we can write a service that will interact with whatever data that we need to work with. Like for example, we are working with our wishes, and currently we have the items array inside of our app component. This is hard coded to have individual wishes. Instead, I would like to fetch this information from a JSON file. Ideally, we would have some web service so that we can make GET, POST, DELETE requests, but we don't. So for right now, we will just have a JSON file that has that same information. We will fetch that information and then use that to populate the items array. So let's go to our assets folder, and let's create a new file. Let's just call it wishes.json. And then let's paste in that array. Now, of course, this is not valid JSON. So we need to make a few changes. First of all, each and individual element is going to be an object that will have a property called wishText that is going to be set to the string value that we have. In this particular case, it's Learn Angular. But then we also have that isComplete property. And we can go ahead and set that as false. But of course, the property name needs to be a string as well. So this gives us something that we can easily just copy and paste a few times and then make the necessary changes. So we have a wish to get coffee, that is always true, because, well, I always have coffee. And we have that other wish for finding grass that cuts itself. That has been a dream of mine for so many years. All right, so we have this wishes file, and we want to be able to fetch this information, parse it into an actual array of objects that we can then work with. But in a real application, we would want to abstract this out into its own service, so that we could have a service responsible for working with wishes. So we would fetch the wishes, we would add a wish, we would delete a wish, things like that, because that service would then issue those HTTP requests to the server. So let's create a service. Now we can do this in a couple of different ways. In a couple of lessons ago, we created our own service for our event bus. And that was perfectly fine. We can also turn to the CLI to create a service. So let's look at that. So the command is, ng generate service. And then we provide the name of the service. But we don't want to have the term service at the end, because what this would do [LAUGH] is create a class that says WishServiceService. So basically, we just want to define the name of the service that we want without the word service, and then this is going to create two files. The first is going to be the code file. That is, of course, going to be our class for our service, and then there's the spec for the tests. So we have that file now, let's open up wish.service.ts. This is inside of the app folder. And you can see that it automatically makes this service injectable. So we will be able to turn around and use the service inside of the app component. So let's go ahead and do that. We will import the WishService from wish-service, and then we can inject that into the constructor. So let's make this a property, we'll call it wishService, and the type is WishService. So this gives us that wishService that we can use inside of the app component. That's great, but we need to write the code that is going to fetch that JSON file. So inside of this service, we need to import the HTTP client, and we do this from the same file. So that would be @angular/common/http. And this is injectable, so we can inject that into our constructor here. So let's make this a property called http of type Http client. And then we will have a method called getWishes, or we could call it whatever we want. But the idea is that we know that this is the method that we would use to fetch our wishes. So we would use our Http object, this has a variety of methods. You can see right here there's delete, get, head, JSONP, options, patch, post. So basically, any type of request that we would want to make, we could do that. In this case, we want to make a get request and then we simply specify the URL that we want to make a request for that is our assets and then wishes.json. Now there are other options that we can add here which we will talk about at a later time, but for right now, I just want to make this work. So what we are going to do then is return the result of calling the get method. Now the get method returns an observable, and by calling the get method, the actual request has not been sent yet. Since this is an observable object, there is a subscribe method that we can use whenever you call the subscribe method, then the request is sent. So just because we call the get method or the post method or any one of the other methods, that request has not yet been set. So let's hop on back to the app component. And what I want to do is make our http request with the OnInit method. Now remember whenever we created our other components using the command line that tool gave us our component, but it also made them implement the OnInit. This was a method that executes so that we can perform other initialization processes. One of those things could be making an HTTP request. So, we want to implement this OnInit inside of our add component, so let's add that implements OnInit. We of course need to update the import statement so that we aren't just importing components, we also import OnInit. And then we can define that ng OnInit. So here, we would use our wish service, we would call the method getWishes, and remember that this returns an observable, so we would call subscribe. So right at this point, whenever we call subscribe, that is going to make that http request, and so we need to provide a callback function that will execute when that request has been completed. So we are going to get some data back. And all we really want to do is set our items equal to the data that we received. Now let's do specify that data is any but with that change, we can hop on over to the browser, and we are going to see that our code still looks like it works. Let's make a quick little change to our JSON file just so that we can prove that we are now pulling data from there. Let's just add an exclamation point to learn angular, and we will go back to the browser. Let's refresh because it needs to load that data again. But there we go. We now have that exclamation point at the end of that learn angular. So we are successfully retrieving that information from the JSON file, it's being parsed into Wishitem objects so that we can display them here inside of our application. We can filter them however we need to. And of course, we can change their status, and of course, deleting them works as well because we haven't changed any of that functionality. So making basic http requests is very simple. In the next lesson we will look at some of the options available, such as setting headers, specifying URL parameters, and eventually, we will also talk about handling errors. When it comes to HTTP requests, we very rarely just issue a simple get request like we did in the previous lesson. Instead, we usually have to supply some options involved, such as special headers or an authorization token. Of course, it varies depending upon our server application and what it needs. But a lot of the time, we are supplying some other information here. So because we don't have an actual server-side application, everything that we are going to be dealing with is a get request in this lesson. But of course, the things we talked about can be applied to any of the request types. In fact, the only real difference between a get and a post request programmatically using the Http client is that you provide the body as the second argument. So the first argument is the URL, the second is the body that you want to send with that request, and then finally, the third are the options that you may or may not want to provide, and it's the options that we are going to be talking about in this lesson. So, when it comes to my own applications, I typically have a method that will generate just the standard options that I would need for every request. So, let's do this. I'm gonna make it private, and I'm gonna call it GetStandardOptions. And this just needs to return an object. This object is, of course, going to have several different options that may or may not be needed for the request to be successful. One of those things is typically the headers. If we don't have the correct headers, sometimes our request is going to fail. It all depends upon how the server is going to Process that incoming request. So we have a class called HTTP headers, and if we are going to use that, we of course need to import that. So Visual Studio code is prompting us here to update the import statement, this is imported from Angular common HTTP. And this constructor simply accepts an object where the property names are the names of the headers and their values are, of course, the values. So if we wanna set the content type for this particular request, we could do so application/jsom or if we had some kind of authorization token that we needed to provide, we could also do that as well. But we don't in this particular case, so we will leave that alone so that whenever I make a request so what I will do is get those options by calling that method, getStandardOptions. And then for our get request, all we have to do is supply these options as the second argument. Well, let's go ahead and let's add a fake method for adding a wish. So we'll call it AddWish to where we will accept the wish object. This will be a wish item which we need to import. But this is where we would typically use a post request. So let's take this code out. Let's paste it in here. And in this particular case, we would definitely need that authorization header. So what we could do is go ahead and get our standard options. We could say that retrieving the wishes doesn't require the authorization header but adding a wish does because well, we are changing data on the server so it only makes sense that yes of course, we definitely want some kind of authorization. So in this case, what we could do Is use our headers object, it has a set method. Now one thing about the headers object, it is primarily immutable. So if we want to add a new header, we essentially have to replace the old headers. So by calling this set method, we are essentially creating a copy of our headers and then adding a new one called Authorization, and then whatever value is needed for authorization. So that updates our headers, which of course updates our options, that we can pass on to the post method in this case. So, let's get rid of these syntax errors, even though that request will definitely not work. In fact, let's do this, let's make it private so, that we can't call this method from outside of the class. Now another common option that we would use are URL parameters and this is especially true for GET requests. So for example if we weren't actually fetching a JSON file, we were hitting some kind of Web service but we were able to specify the format that we wanted the data in. We could have a format URL parameter and then we could set that to JSON or for whatever reason, XML or any other type of format. We could easily do that by just adding that to the URL or we could modify our options because it has a params object and this is of type HttpParams. And just like the HTTP headers that we used, this is imported from the Angular common HTTP. Now whenever you new up this constructor, you can pass in an object that has various options, probably the most used would be called fromObject. So basically, this fromObject says that we want to create our URL parameters from this object where the property names are the URL parameter names. And then their values are of course, their values. So by setting this HttpParams using fromObject, I want a parameter called format and its value as JSON. So that is essentially going to send a request using this URL, except that we don't have to specify the query portion of that URL. It will automatically be generated for us, thanks to this params option. So we can save that, we can actually see this in action. We can go back to the browser, let's pull up the Developer Console. And let's go to the Network tab, and we will probably need to refresh here. Now there's going to be several requests, but what we want is this right here wishes.json. We can see that the URL includes the query portion. We have a query parameter called format and its value is JSON. And just to prove that that is coming from this options object, let's change it to XML. So that once again, when that request is issued, we can see right here, format = xml. Now probably the most important thing when it comes to making HTTP requests is handling errors. And the way that we handle errors using the HTPP client in Angular is a little bit different than other clients, and we will look at that in the next lesson. Handling errors is an important part of software development because errors will happen. Things will go wrong. It might be our fault, it might not be our fault. It can just be something that happens. This is especially true with HTTP requests because there are so many things that can go wrong. It could be a client issue. It could be a network issue. It could be a server issue. So when it comes to handling errors, we should at least provide some information to the user that, hey, whatever you tried to do it didn't work. And here's why. And really in the best cases, we can sweep it under the rug if we don't have to tell the user anything. But in most cases, we want to provide the user with at least a little bit of information. Now, the first thing that we can do is provide a second callback function to the subscribe method. So here, we are inside of the app components. We're using our wishService to get our wishes and then we're calling subscribe. Now, the first callback function is handling the response on a successful request, and we have seen that work. We know what it does. However, if there is something that goes wrong, we need to handle that error. And we can do that by providing a second argument. It is another callback function that receives the error object. And then we can do whatever it is that we need to do. We can tell the user that an error occurred. And this has a message property that we could provide them. So let's do that, let's alert(error.message). And as it is right now we don't have an error. So let's go to our wish service and let's make a request for a file that does not exist. So let's just change the name of the file from wishes.json to wishes1.json. And whenever we save this, let's go to the browser, we will see the result of that HTTP request. There is an HTTP failure response for the URL 404 not found. Now, for technical people, I think that that's perfectly fine. If I were to get that error message, I would know what the problem is. However, the vast majority of people in the world are not technical, so we need to provide a message that means something to them. Now, I am noticing some other errors. Cannot read properties of undefined, reading filter. We could fix that very easily. Let's go to our app component. I want to minimize as many errors as we get so that we don't clutter up the console. So what I'm going to do is simply define items as an empty string to begin with and that should fix that particular error. At the very basic level, all we need to do is provide a second argument to the subscribe method. This argument is a callback function that works with an error object when an error occurs. But of course, we as developers want and really need more information when an error occurs. So a lot of times, we would log that error and we would try to provide as much information as possible. So let's go to our wish service. And the first thing that we are going to do is Import an operator called catchError. Now this is called an operator because well, you'll see how we will use it. This is coming from the Reactive JavaScript library, so it's rxjs/operators. And we are going to use this catchError in order to essentially catch the error as it occurs. Then we can inspect that error, try to find out what went wrong, so that we can log the appropriate message into our system. And then we can also provide the user with something that's meaningful for them as well. And the way that we do that is by essentially piping in a process to our request that is going to handle the error. But that's not really what we need to do here. We need to pipe in catchError, and then we specify something that is going to handle that. So, I am inside of the getWishes method. We are making our request, and then we are calling pipe. We are piping in a process that will process, And error in case if that occurs and we're going to write a method called handleError that is going to handle and process that error. So let's do that, we can make it private and this method is going to accept an error object. And this is an httpErrorResponse. This classes from the angular common HTTP and Visual Studio code automatically added that import statement for me. If not, then you would need to do that where you import httpClient and headers and params. So we want to know what's happened with this particular request. So the first thing that we can do is check the status. And if the status is equal to 0, then it's not a server error, it is a client or a network error. So we can write something to the console or if we had a logging service, this is where we would want to do that. To where we can say that, there is an issue with the client or network and then we could supply the error information. Now again, this would be for our purposes, this isn't necessarily for the user. But then we can add an else, because if the status is not 0, then it is an HTTP response status, we are getting something from the server. So, in which case, we would want to signify that we have a Server-side error, and then we would want to be able to inspect that particular error. And let me go back and change the code for the client or network. I don't want to output a string, well, I do, but I also want to output the error as well, so we need to do that correctly. And so we will have an error message and it will display information about the error that occurred, but then we also want to supply the user a readable message, something that is meaningful to them. So, we can return an error and really, what we are doing, we are essentially throwing an error. But by calling throwError, this essentially gives us an observable that wraps around that error. And we simply want to create a new error, and then we'll have our message, cannot fetch. Or let's do this, retrieve wishes from the server please try again. And so now, if we save this, let's take a look at the browser, we are going to see some errors. First we see this failure to get, now this is coming from the browser, this isn't necessarily coming from our code. Whenever the browser attempts to retrieve a resource, if that's a 404, it logs it within the console. So we can ignore this error here, because that is not from our code but this one certainly is. Server-side error and then it gives us the markup for that error, cannot get assets/wishes1.json. Then if we take a look at the alert box, what the user sees, cannot retrieve wishes from the server, please try again. So for the most part, handling errors is pretty straightforward and it can be as simple as simply providing a second callback function when you call the subscribe method. However, I would imagine for most applications, you would want to at least process that error so that you can log it or provide it in the console so that you can inspect that error and see what went wrong. And most of all, you can provide a meaningful message to the user. So far in this course, we've very briefly talked about modules. And when it comes to building applications, modules are one of those things that, well, we don't necessarily need them, but they are extremely useful for very large applications. Because it allows us to essentially create a container for like minded or closely related components and functionality. For example, so far in this application, we've been focusing on the wish list and displaying the wishes, manipulating them, and all of that. If we were going to build more functionality for this application that doesn't necessarily work with wishes like a contact form, well, then it would make sense for us to first of all create a module that serves as a container for all of the wish oriented stuff. So All of the components, all of the services, everything that we have done as far as working with wishes would be inside of a module, and then we could create another module for all of the contact form stuff. So guess what we are going to do in this lesson, we are going to create a module. And we can do that from the command line using ng generate module, and then we simply specify the name of the module. Now this is going to be a little bit difficult because I've use the term wishList in a variety of places. So let's just call this the wishModule. And this is going to create a directory inside of our app directory called Wish. And by default, it's just going to give us one file, and it's going to be that module file. You can see right here, it tells us what exactly it created this file. And it did so inside of a directory called Wish. So now we have this wish directory, and really we want to put everything that we have worked on thus far inside of this folder. Now, of course that has nothing to do with the AppComponent, but everything Wish related. So, the WishFilter, the list, the WishListItem, AddWishForm, even the stuff inside of shared. And putting it inside of the shared folder is questionable, because really this stuff should have been inside of a module as well. So let's see what happens, let's just start dragging and dropping things into this Wish directory. Now Visual Studio code is going to try to fix some of these paths. So we'll see what happens whenever I click on Move. If I'm prompted with anything, well, it doesn't look like it, but definitely we can see that there is a compilation error because some of the paths cannot be found, but that's okay. Let's just go ahead and grab the wish-filter, wish-list, and wish-list-item folders. Let's drag those into the wish directory as well. Yes, we want to move those. Now of course, we are going to need to modify not just the app module, because, remember, this is where we had specified that we had some components to work with such as WishListComponent, AddWishFormComponent, and so on. And so essentially what we want to do is take these import statements where we are importing those components. We'll cut them out, and we will put them inside of this new Wish module. And we will want to add them to the declarations, so we can also cut those declarations out. So that's inside of the app module, all we will really have is the AppComponent. And we won't necessarily need the FormsModule or the httpClient module because those things are being used by the components inside of our newly created Wish module. So let's also bring those in. Now, I'm noticing that there were some changes to the pas of those files. Here we can see that WishListComponent was still set to WishList, so there's not an issue there. But if we look at AddWishFormComponent and the filter It added the wish there. So that's interesting that Visual Studio Code modified the paths for some of these and didn't for the others, but that's an easy enough change. Now, let's also grab those other import statements for the HttpClientModule. And the FormsModule because we need those inside of our WishModule. So let's just cut those out, paste them in. We also want to import those. So once again, we will cut them out of the imports in AppModule. We will paste them inside of the imports for the WishModule. Now we are still seeing some errors in our output, well, I need to scroll down. But yes, we still see some errors. And that is inside of the AppComponent. Now, this makes total sense. Because we've just lifted out all of those components and put them inside of the module. So as it is right now, our application has really no idea where those are. So what we need to do inside of our wish module is it's not good enough to just import these and declare them here, but we also need to export them. So there's an option called exports. It's an array and we want to export each one of these components so that whenever we set up our application to use this WishModule, which we probably should do. Let's go ahead and do that. Everything that can be exported from the WishModule is going to be available to use within our application. So here inside of the AppModule, we've cleaned this up quite a bit, but we do want to import the WishModule. So let's go ahead and do that. We will add that to the imports array. Let's save everything and let's see what we get as far as the output in the console. Console. Looks like there's an error inside of the wish-list-item component. So let's open that up. And I can't imagine what we are missing here. Right here, the EventService. Now our EventService is something that should be shared across our application. So we have a couple of different options here. We could break this out into a shared module or we can leave it as it is and I think for right now we will opt for that. Let's get one module working first and then we'll go from there. All right, so I don't see any red in the file explorer over here. But we do still have an error inside of our, let's see that is AppModule. So let's take a look at that file. Well, there's WishModule. Let's take a look at the WishModule. Let's see what we have. I don't see any errors. So it could be that we need to stop our application. We'll run ng serve once again, because sometimes we just have to stop the process, restart it and then see if it will build or if there will still be an error. And here we can see that it compiled successfully. So it looks like that was just an issue. Cannot retrieve, yes, because we are attempting to retrieve a file that doesn't exist. So let's go to our WishService. Let's change the URL to wishes.json. Let's go back and let's see if it loads. It does. So looks like everything's working if we add a wish. Okay, so our application looks all well and good. Now I do think that the WishService should probably be inside of our WishModule as well. So we have working code, let's break it. Let's move that WishService and we do need to change the paths. But Visual Studio Code is prompting me, update the imports. Yes, let's update the imports and that should make that work just fine. However, once again, it's saying that we have an issue. So let's take a look at the browser because that's going to give us a better understanding of where we have an error. And it looks like right here, cannot find module wish service or its corresponding type declarations that is inside of AppComponent. So let's go to AppComponent, and let's see what we have there as far as that WishService is concerned. That is being injected. And the path is correct. So let's do this. Let's copy that import. Let's go to our WishModule and let's paste this in. We, of course, will need to modify this, because this is directly inside of this directory. And then we will export this module. But just saving the file again makes that compile. So it's crazy how this works sometimes. All right, so it looks like everything is once again working as it did before. In the next lesson we are going to create another component, one for our WishModule. And in fact it's going to serve as the wish list application. So we will essentially move all of the code from our AppComponent into that new WishAppComponent. This will essentially allow us to drop in our wish application anywhere within our project. I want to extract just about everything about our wish list application into the WishModule. Even going so far as to taking what we currently have inside of the AppComponent, and putting that inside of a new component called Wish inside of the WishModule. That way, all we have to do is drop that component into the app components so that we would essentially have something like this to where we would have a wish app or something along those lines. And then voila, we have our wish list application. I'm probably taking this a little bit too far. But my goal here is to transition into talking more about forms. And so I want to keep all of our wish list stuff intact and all together so that if we ever do need to talk about it again, it's there. We just need to drop that component in and we're good to go. So let's go to the command line and let's generate a new component. We'll call it simply wish and we want to put this inside of the WishModule. Now the reason why I'm calling this component just simply wish is because that's following the pattern that we have as far as the app component because we have an app module and then we also have an app component. So in this way, we will have a wish module and then we will have a wish component. And really all we need to do is take the markup from our app component. Let's cut that out, place it inside of the template for our wish component. Now of course, not everything is going to work because, we do need to address the code as well. So let's open up the code for our wish component. We'll do the same thing for the app component. And for the most parts, we want the import statements for the WishItem, the EventService and the WishService. Let's cut those out of app and we will paste those inside of the WishComponent. And then we need to do a few more cut and pastes such as the items property, the constructor and ng on init and we need filter as well. Basically everything inside of the AppComponent class. Let's cut that out and let's paste it inside of our WishComponent. Now let's do go ahead and cut out what was generated for our WishComponent because we can paste that inside of our app component and that should make the errors go away there. We do however have an error inside of the WishComponent so let's. Take a look, it looks like it's mainly pathing, so let's modify the paths for importing the EventService, the same for the WishService. Let's save that, it should compile now. And so let's close those files. Let's sum it up the WishModule, because we want to modify this. Because now, whatever is going to be using this WishModule doesn't necessarily care about the WishListComponent, the AddWishFormComponent, and all of that stuff. Instead, all we really need to export is the WishComponent, because anything that is going to use this module is going to primarily just use this WishComponent. So, we can export that. And then inside of the AppModule, well, we don't really need to do anything at all. Because in the previous lesson, we added the import for WishModule. So inside of AppComponent, all we really need to use is that app-wish element. Now, I'm going to leave this named as is, because I kind of like the idea that this is an application for the WishList. Although I say that out loud, and now it makes a little more sense for us to change that because I would want app-wish-list. But no, let's just leave it as it is cuz that could be a little bit confusing with the WishListComponent that we currently have. And so now that we have extracted that stuff, let's go to the browser, and everything should work as it did before. We can see that we are properly loading the wishes from our JSON file. Let's make sure all of the functionality works so that if we fulfill one of these, the filtered list updates appropriately. If we remove an item, that should remove it. And if we add a wish, then, of course, that is there as well. So, fantastic. We now have extracted all of the functionality for our wish-list application into this WishModule. So in the next lesson, we can get started writing another module, one for a contact form, so that we can talk about forms. We need to talk about forms. Now yes, we have very briefly talked about forms using the template driven approach. If you'll remember whenever we created the AddWishFormComponent, we used the template-driven approach. Basically, we used the NgModule directive, assigned that to a property on our components class, and then that created a two-way binding between the form field and the property behind the scenes. And it worked rather well. And I dare say that the way that we used it for this very simple form is exactly what the template-driven form approach is for, it is for simple forms. And while it works well for simple forms, we typically don't want to use it for larger forms, and especially if our application is very forms heavy. Well, a variety of reasons, for one, there is a performance hit with using the template-driven approach. There's a lot of overhead to manage the things that are going on behind the scenes. And for now that we can't really be guaranteed that the data that we are working with is actually the data that we want to work with. I know that that sounds weird, but due to the asynchronous and two, a binding nature of using NgModule, the data that we are working with inside of our component might not necessarily be what we need to work with. So instead, we are going to start looking at the reactive form. But I want to do this inside of another module so that we can swap out these modules as we need to. So let's generate a module. Let's just call it contact, and we'll also go ahead and create a component inside of the ContactModule. And we can just call it contact. The idea is going to be that it is a contact form, so let's go ahead and generate that component. We do want to make sure that it is in the ContactModule. And while the CLI is generating those things, let's go to our ContactModule and let's open up the module file. Now, if you'll remember, whenever we used the NgModule directive, we had to include the FormsModule, but we don't do that with reactive forms. Instead, we use the ReactiveRormsModule. So we want to import that into our ContactModule. And now that we have that ContactComponent, let's go ahead and let's add that to the exports array and then we can close this particular file whenever we're done with that. So let's do that. Let's go to the AppComponents template as well as the AppModule, because we do want to be sure that we import that ContactModule. So let's go ahead and do that. And that will give us the ability to use our new ContactComponent. So inside of the apps template, let's just use app-contact and that should fix that. So let's look at the browser just to make sure that we are displaying our information and we are. So, great. Let's go to the template now for our contact form. I'm going to paste in the markup because, well, you don't want to see me type all of this. But it's a very simple form. It's a contact form, we have a field for the person's name, their email address and their message and then of course, a button so that they can submit the form. And let's be sure that that's going to look okay. I think it should, it does, great. All right, so now let's go to the component. And we want to use reactive forms. And the primary way that we do that is by importing a class called FormControl. Basically what we are going to do is create FormControls and then bind that to the individual fields in our form. We need to import this from angular/forms. And then we just define our properties that are going to represent those FormControls. So we have three, we have the senderNameControl, and we want to new up the FormControlConstructor. Now, we can pass in an initial value if we wanted to. And I think initializing as an empty string is going to be fine, because these are after all form controls and empty strings make sense. So we will also have a senderEmailControl, which once again, we will new up the FormControlConstructor. And then finally, we will have the senderMessageControl. So we have these FormControls, and now we just need to bind them to the actual form fields. So we are going to do that inside of our template. We're going to add a directive, simply called FormControl. And then the value of this directive is going to be the properties that we just created. So this is the sender name. So this will be senderNameControl. For the email address, once again, we'll use the FormControl directive, but we'll bind this to the senderEmailControl. And then finally for the text area for the message, that will be the senderMessageControl. So from a setup perspective, it feels a lot like the template-driven approach. We had to create a property that we are now binding to a particular field in the form. The only difference is that, we created those properties explicitly as FormControl objects and we are binding those objects to these form fields. So then the question becomes, why use reactive forms especially since it appears that the setup is mostly the same. We create properties, we bind them to the form fields. Well, the key is I used the term objects. We created these FormControl objects and as such, this gives us an API that we can tap, Into to make our forms a lot more flexible. For example, we're going to have a method called SubmitForms. So let's just go ahead and define this. But if we take a look at just one of these, like the SenderEmailControl, let's look at what IntelliSense is going to pop up. And you can see right off the bat that there are a lot of properties and methods. Some of these are extremely useful. Like for example, there's a dirty property. Now I've always thought that the term dirty sounds rather awful within this context, but that is exactly what it is. If the dirty property is true, then the value in that form field has changed from its original value. And that is extremely valuable to know as you are processing the form. There's also a pristine property, which is essentially the opposite. Has the value of this particular form control changed? If not, then pristine is going to be true. But it also gives you the ability to mark a form field as pristine. Or there's also a term of touched, meaning that the user has given focus to the form field and then took the focus away. So there's a lot of functionality here, especially the ability to use very expressive and flexible validators. So let's do this, let's do a check. And we will check if the senderNameControl is dirty. Well, then we will simply alert a message that says, you changed the name field. Of course, that's not going to be very useful overall for this contact form, but for example purposes, it's going to be fine. So let's go to our button, let's handle the click event. The first thing we really need to do is prevent the default from occurring, so let's call event.preventDefault(). And then we can call the submitForm. So let's go to the browser, and whenever we click on Submit Message, we should not see the alert box, because the name field is clean. There has been no change to it, but whenever we change the value to something other than an empty string, clicking the Send Message button, we get the behavior that we expect. We get the alert box with our message. You changed the name field. Now, in the next lesson, we are going to look at creating a FormGroup. Because, yes, as you've seen, as it's possible to simply create a form, bind fields to form controls, we can group those controls into logical pieces. In the previous lesson, we created a contact form, and we used reactive forms to set up the ability to process this form. We, of course, haven't done any processing, but we also briefly talked about the benefits of using reactive form as opposed to the template-driven approach. One of those is that we get this API, which gives us a much more flexible way of processing a form. And that's a wonderful thing, but it also gives us the sense of data integrity. Because as the user submits a form, we get a snapshot of the data of that form at the time it was submitted. So we don't have to worry about data being changed on us like we do with the template-driven approach. So what I want to do in this lesson is take this just slightly a step further and use a group, because our form has just three form fields. These fields are related to one another, so it just kind of makes sense to use a group to group all of these together. So, to do that, we need to, first of all, import the FormGroup class. This is, of course, coming from @angular/forms. And then we want to create our FormGroup. We'll do that by creating another property, let's just call it, contactForm. And we will new up the FormGroup constructor. This accepts an object that basically allows us to define our data model. So just like we defined our model with individual properties, now we are going to have a model that is encapsulated within this FormGroup. And we can essentially take these properties that we created in the previous lesson, paste them inside of this FormGroup. We, of course, need to change the syntax because we are dealing with an object literal here, but basically it's the same thing. We create our form controls, except that now they are properties on the contactForm. I'm going to make these a little bit easier to deal with as well. So I'm going to remove control from those property names. Now, of course, our submitForm method, it doesn't work. So let's comment out that code. And then let's go to the templates, because this is where we need to bind our FormGroup to our form. We do that at the form element. So we are going to use a directive called formGroup, and then we simply assign the contactForm object that we created. And then for the individual fields, we no longer use the formControl directive. Instead, we use an attribute called formControlName. And the value is the name of the property in our FormGroup. So we now have senderName, we have senderEmail, and we have senderMessage. And all we need to do is change from using the formControl directive to the formControlName. So now we have that group, it is properly bound to our form. The only other thing that I want to change is getting rid of the click event on our button. Because instead of listening for this event, we want to listen for the submit event on the form. And we do that by binding to the ngSubmit event. Now, the reason why we want to do this is because it allows the user to submit the form, both by clicking on a button that has a type of submit, which we have, but it also lets the user use the Enter key on the keyboard. Which I certainly appreciate, because as I'm entering a form, my hands typically don't leave the keyboard. And it's very frustrating if I can't tab between form fields, and if I can't submit a form by just pressing the Enter key, so this will be nice. Let's go back to our code, and there's a few other syntax errors that I need to fix, because we are dealing with an object literal there. And now this gives us a slightly different way of accessing the different elements within our form, but also just working with the form in general. So let's write this out. We'll say, this.contactForm.value. And we'll see what we get. So let's go to the browser, let's pull up the developer console, and let's, first of all, submit the form. You can see that we get an object that has three properties, senderEmail, senderMessage, and senderName. And, of course, if we change any of the form field values, well, then we are going to see those values represented whenever the form is submitted. So once again, submit the form. We now have the email and the name. And by using a FormGroup, we also have the ability to check the validity of the entire form all at once. All we need to do is use the valid property. And since we haven't defined any validators, this is always gonna be true. But instead of having to check each individual property, all we have to do is check if the form itself is valid. If it is, then great. If not, then we can provide the user A message. And in the next lesson, we will look at how we can validate our form. JavaScript was created for the explicit purpose of processing forms, because before we had JavaScript, well, it was just very frustrating. The user had to, first of all, wait for the page to load over a dial-up connection. They had to fill out the form, submit the form over a dial-up connection, then they had to wait for the server to process it and then respond over a dial-up connection, only to find that you missed a field or you did something wrong. It was very frustrating. So JavaScript made it so that we could process forms inside of the browser. But even then, it was still a very cumbersome process, and it really wasn't until a few years ago when the validity API was introduced that it really made it easier to process forms. Now, of course, there's always been third-party libraries, because, well, it was never really fun. But thankfully, with Angular, it is extremely easy to validate a form. All we have to do is, first of all, import validators. This is, once again, coming from @angular/forms. And then we just need to apply those validators to whatever form control that we need. Like for example, we have a contact form, and we kind of need the name, the email, and the message. Otherwise, what's the point of sending a message to us? So whenever we create these form controls, we can pass in a second argument, that is, the validator that we want to use. So we want all of those fields to be required. So we will simply pass the required validator to each one of those controls. And voila, we now have a form where all of the fields are required. Now, we had that code that output the value of the valid property on our form. And in the previous lesson, it returned true because we didn't have any validators. But now, whenever we submit the form, it's false, because, well, of course it is, all of our fields are required. So, if we provide some information and then submit the form, it is now true, so great. But of course, we need to apply more validators, especially for the email, because it's not enough to just check if it is required, we also need a syntactically correct email address. So you can also pass an array of validators. So that's exactly what we will do for senderEmail, and there is a validator called, simply, email. And we can do something similar for the senderMessage, because if the message itself isn't more than ten characters, what's the point? So we can use a minLength validator. And then we can specify the minLength of the string that is supplied. So we can say that a message has to be more than 10, or at least it has to be at least 10 characters, or else it's not valid. So now we can go back to the browser and we can check this out. Of course, every field is required. So, once again, by not entering anything and submitting, it's false. But if we supply something to each one of these, it's still false because we need a valid email address, or at least, a syntactically correct email address. So we provide that, but it's still false because our message is only four characters long. So, if we add something that's more than nine characters, we submit, it is now true. All right, so that's all well and good. But we need something visually so that we can tell the user, this is not valid, because there's nothing more frustrating than clicking a button and not knowing what the heck is going on. So one thing that we can do is add a visual cue to the button. We can disable the button if the form is not valid, so we can bind to the disabled property. And we can do this in a couple of different ways because we have two properties here. We can use our contactForm object. We have the valid property, which we have been working with. So we could say that this is disabled if not valid. I hate that approach because my brain just doesn't really process that very well. But thankfully, we can see that it does indeed work. The submit button is invalid if the form is invalid, but once it is valid, it's enabled. That's great, but thankfully we don't have to use valid here, we do have an invalid property. And so I like this a whole lot better, so that if it is invalid, then it is disabled. If not, then it's not. This reads a whole lot easier. So we will get the same functionality that we did, it's just that our code is a little bit easier to read. So that's great, but it still needs some work because we need to tell the user, hey, this field is required. Hey, this field is required, and it needs to be a valid email address. And so far, everything has been easy. It hasn't been cumbersome. This is gonna get a little verbose, so let's focus on the email address, since we have two things that we need to check for here. And while we could take several different approaches for this, what I want to do is just add a message to the bottom of the field to display the error that the user needs to fix here. So we're gonna add a div element after the input element, but of course, we only want to show this div element if the email field is not valid. So we can use ngIf, here, and we'll use our contactForm. But that's not good enough because we want the individual email field. So we have a method called get, and then we pass in the name of the field that we want to get. So in this case, that would be, senderEmail. And then we can check if it is invalid. Now this is going to display an error, we have a red squiggly here. And if we save this and if we go to the browser, we're going to get the explanation as to why. It says that the object is possibly null. Well, that's all well and good, but we know that this really shouldn't ever be null. If it is null, then there's something definitely wrong here. But we need to make this work syntactically, so what we can use is the null null coalescing operator. So what we can use is essentially the null-checking operator. I don't really remember what the actual name of this operator is, but it is a question mark followed by a dot. Basically, it says that if what comes before this operator is null, then fine, it's null, don't do anything else. However, if it's not null, then it's going to go ahead and check the invalid property. So this is going to fix that. And so, if this is not valid, if it is invalid, then we can say that this is invalid. And so, let's take a look at what that's going to look like. And there it is. Now, I don't particularly want this. We are seeing the error message as the form is loaded into the browser. I would like the message to be hidden until the user does something with this field. So that let's say that they give focus to it and then they decide to not put anything in there, so they move on to the next field. Well, that would be the perfect time to display an error message. Hey, this is required. So we can do that, but it's going to require a little bit more code. So here's what we want to do. If this is invalid, And if the field is dirty or if it has been touched, which is the kinda state that I just went over, the user gave focus to it, decided to give focus to another form field. So they touched that field signifying that they at least interacted with it. So if it is dirty or if it has been touched, then we want to display that error message. Now, notice what I've done here, we've typed out or I've used ContactForm.get, passed in the name of the field, and so on. So it does get verbose taking this particular approach. What we could do is create some kind of helper method, or a helper property, or something inside of the component class so that we can get to this information a little bit easier. I'm gonna leave this as is. But now with that in place, we can go back to the browser and the form is going to load as it should. There's no error messages, because the user hasn't done anything yet, which is what we wanted. But if we touch the email address field and then give focus to something else, then we get the message. But, of course, the message, this is invalid, this isn't good enough. We want to provide something meaningful to the user, but we also need to keep in our minds that there's actually two different error messages. There's one that's going to tell the user that this is required. There's also one that's going to tell the user that they need to supply a syntactically correct email address. So we're going to use the small elements here. We're going to set the class to text-danger so that it is red. And then we are going to use ngIf, because we need to check what kind of error that we are encountering here. So let's just copy what we have here for the invalid, because we're going to use that as our basis. Because instead of using this invalid property, we have a method called hasError, and then we can pass in the name of the validator that we want to check for. So in this case, we used the required validator. And if that is the error, then we can say that this field is required. But then we also need to check the email error as well, because we have that email validator, so we will use email. And then for this message, we can say, please enter your email address. So now, whenever we go back to the browser once again, there is no error message. We touch the field and we can see that the field is required. Okay, so we start entering stuff, but now we see that the error message changed, please enter your email address. So if we supply a syntactically correct email, then the message goes away. But the minute that we have an error for the email address, we get that message. And of course, if there is no value, we once again get the required error. Well, let's essentially do the same thing for the message very quickly. So let's just copy what we did for the email, let's paste it for this text area. And of course, instead of using senderEmail, this is senderMessage. We have to change all these. Once again, we could set up some kind of helper method or something to make it a little easier to get to this information. Now for the minlength error, all we would use is the name of that validator. So whatever validator we used, we use that name. So that would be minlength, and I believe the message has to be at least 10 characters, all right? So that should be fine. Let's go back to the browser. Let's enter, well, let's not enter our name yet. We will enter a valid email address, and then our message. And we're not seeing, it is the name of our validator, but it is all lowercase, that's kind of important, isn't it? So let's go back. Our valid email address, and then our message has to be at least 10 characters. So once we reach 10 characters, then that message goes away. But, of course, the form itself is not valid because we need to supply the name. And I guess very quickly, we could add the name validator as well. So let's copy what we did for email or the message. It doesn't really matter. Let's paste it for the sender name. We, of course, need to change [LAUGH] the field for this, but we only have one validator here and that is the required. So we can leave that as it is, this field is required or we can have something a little bit more custom for this field, please enter your name or something like that. I think this field is required is fine. So now, if we touch it, error is there. If we type something, the error goes away, and we now have validation for our form. Angular has many built-in form validators and they will work for the majority of our use cases. However, there will be times when you want your own validator. And thankfully, it's very easy to write your own custom validator, because all it is is a function. So I want to add another validator to the email address field. Basically, I want to prevent people from using certain domains like gmail.com or yahoo.com. I know that that doesn't sound very useful, and in fact, it would be very frustrating, but it gives us an example to use. Now, remember that I mentioned that a validator is a function. So we could define that function here inside of our component, but I think it makes more sense to put it inside of its own file, especially after we are done adding some of the features to it. So let's call this invalidEmailDomain, and let's first of all talk about the anatomy of a validator. Well, as I mentioned, it's just a function. The function name itself really doesn't matter. So for right now, I'm going to call it invalidEmailDomain, and it accepts the control that it is bound to. It is of type AbstractControl. So what exactly does that mean? Well, whenever we set up our form controls instead of our form group, we specify the validators. And so here for our senderEmail, we are going to add that invalidEmailDomain validator. So when Angular validates this form field or this form control, it's going to pass this form control to our invalidEmailDomain function. And then we do our validation and we return one of two values. The first is an object of type ValidationErrors. So if the value of this control is not valid, it will return an object and it will specify the type of error that occurred. The other value is null. If our function returns null, then the value is considered valid. Seems kinda weird, especially whenever we see how we return this ValidationErrors object, but it is still quite simple. So let's first of all get the value of the control. So we'll use the control object, it has a value property. But remember that we are also using strings. So if we want to see if the provided email address has a given hostname, it would make sense for us to just normalize all of the string, because email addresses are not case-sensitive. So what we will do is call tolowerCase() here, but notice what I'm doing. I'm using the ?.operator so that if control.value, Has a value, then it's going to be converted into lowercase. If it is null, well, then null is going to be assigned to value. And this is important because the first thing we are going to do is check if we don't have a value. Because if we don't have a value, then technically, our form control is valid. It doesn't have a host name that we want to prevent, so we return null if we don't have a value. Now, of course, that's just for this particular validator. Other validators might need to return an error object in that case, we don't in this case. So the next thing we need is to check if the provided value has any one of the host names that we want to prevent. So let's do this. Let's create an array called hosts, and we will have gmail.com and yahoo.com, and we could do this very easily by using the some method on our array. So we will call hosts.some, and we're going to be working with each individual host. So, basically, if some of the hosts are substrings in the form control's value, then, of course, yes, we have an error that we need to return. So all we are doing here is calling some. It returns a Boolean value, so that if some of the hosts are substrings in the value, then matches will be true, otherwise it will be false. So that then we simply need to check matches whenever we return something. If we have a match, then we return an object that has a property called invalidEmailDomain. Now this, it's not arbitrary, but this property name can be whatever it is that we want it to be. This is the error name itself. So if we take a look at the template here, whenever we check to see what error occurred, such as required in this case, but we want the email address. So here we check required, here we check email. We will essentially do the same thing, but we will use whatever property name we specify here inside of our validator. So our error name is going to be invalidEmailDomain, and then we will simply change our error message, the email provider is not allowed. But let's go ahead and let's finish this return statement. So if there is a match, we return the validation error's object, otherwise we return null. And so now all we need to do is, well, first of all, we need to import this ValidationErrors. This also comes from @angular/forms, but then we need to import this invalidEmailDomain. So let's, first of all, export that. I don't think we did. No, we didn't. So we will export that function and then import that here inside of our component. And then once we save this and we go to the browser, everything should work. Visual Studio Code, for whatever reason, still thinks that there's an error in the template. I don't think that there is. Okay, so let's try this out. We know that the validation for work. So all we are gonna be testing here is the validation for the host name. So if we have something that is permitted, asdf.com, we can see that that works just fine. But if we use gmail.com, the email provider is not allowed. If we use yahoo.com, the email provider is not allowed. But if we use outlook.com, well, then we can see that that is valid. So there we go, we have our own validator. But I want to take this a step further because I don't necessarily like that the hosts are hard coded here inside of the validator. Instead, I would like to do something like this so that we have a function that we could call. And it would look something like this, so that we would have the invalidEmailDomain here. And we would call createInvalidDomainValidator. [LAUGH] That's a mouthful, but it at least is explicit as to what it does. And then we could provide the host names that we wanted to prevent. This way, we could use this function anytime that we needed to create an invalid domain validator, and it would give us a little more flexibility. So let's do that. Let me make sure that this is typed correctly, createInvalidDomainValidator. Okay, so here's what we are going to do. We're going to change our validator code so that instead of importing the invalidEmailDomain function, we are going to import a function that will create that invalid email domain. So let's save that. Let's go back to our validator file, and instead of exporting this function, what we will do then is export the createInvalidDomainValidator. So that it would accept the hosts that we wanted to work with, which is going to be just an array of strings, and this is going to return a ValidatorFn. This is the type that we need to import from @angular/forms. But from there, all we really need to do is just cut and paste. So let's take this, cut it out and paste it inside of our createInvalidDomainValidator. We can get rid of this hosts variable because now we are relying upon the hosts that were passed to createInvalidDomainValidator. And let's do this. We will simply return our function, and we will use an arrow function here. So that should give us the same functionality as we did before, but now we have a validator that is dynamic. We can create it however we need to create it with whatever hosts that we want to prevent the user from using. Let's add another one, just to make sure that everything does indeed actually work as we expect it to. So let's also prevent hotmail.com. I still have a Hotmail account. It long ago became my spam account, but still it can be useful here. So let's test Hotmail. That email provider is not allowed. Let's try Yahoo and Gmail. Now, of course, our validators can be as simple or as complex as we need. Even though ours is a little more dynamic, I would still say that it is simple. Single page applications are not a new concept, it's been around for many, many, many years. And, of course, thankfully, the way that we create and develop single page applications has changed over the years to where now it's relatively simple, whereas at the beginning it was very cumbersome. Now if you're not familiar with the concept of a single page application, it is an entire web application that is loaded into a single web page. This is very different from a traditional web application. So here we have tutsplus.com. This is a normal website. So we go to tutsplus.com, we click on a link. This link is going to send a request to the HTTP server. The server will handle that request. It will generate the output, which is the HTML that is sent to the browser, and then it will send that response back to the browser. With a single-page application, the server, for all intents and purposes, is left out. The initial page is sent from the server. But clicking on a link to go to other parts of the application doesn't necessarily mean that it will send a request to the server. Our Angular application is going to be handling those requests. And the way that that works is by using a router. And if you're not familiar with the idea of a router, it's very simple, it essentially routes a URL to a component. So for a traditional website, the server handles all of that, it accepts a request for a given URL, and then it returns a response. The result being a completely separate page within an Angular application. The Angular application receives the URL, and it loads another component that resembles a page. If that's clear as mud, hopefully getting into it will be a little bit clearer. So, the first thing we need to do is create a new project. Now, we could add the router to our existing wish list. However, if you ever plan on creating or writing a single-page application, even if you don't intend to use the routing capabilities up front, it makes sense to go ahead and just create your project using the router. So, we're going to create a new project called router app, and the very first question is if we want to use Angular router. The default is no, but in this case, yes, we do, and let's use just ordinary css. And then when our project is created, we will create a couple of components that will represent individual pages, that we will, quote-unquote, navigate to. Let's generate the first component, and we'll just call this first. Then we will do the same thing except we'll call it second, so, that this is going to give us two components that we can then then navigate to, to represent individual pages if you will. And before we start up our application, let's go ahead and load up our code editor because there are quite a few differences between an application that has the router and then just a normal application that we have been working with. And we'll start with the app module, so let's go into the source folder inside of app, we'll look at app module. And for the most part everything looks normal except that now we have this app routing module. This is a module that contains the routing information because what we have to do is essentially define our routes. Basically we say that we want this particular path to go to this particular component. And all of that is defined inside of this AppRoutingModule, and this is inside of the AppRoutingModule file. So, let's go ahead and let's open that up, and we will see our module is defined here, it is importing the router module and for route and then the routes are being passed to this and then exports the router module. But what's important here is this routes variable, this is an array of objects that essentially defines our routes. At the most basic level, there will be two properties, there will be the path Which is essentially the URL and then the component that we want that path to route to. So, we have our first component which we need to import both the first component and the second component as well, but I want you to notice the path itself. Now, there are some frameworks that want you to put a slash before this, but these paths are all relative to our application. So, the route of our application as it is currently is local host port believe 4200, so the path that we specify within a route is going to be that first segment in the URL. So, in this case it will be first or it would be second, so, let's take a look at that in the browser. So, let's hop on over there to our Angular application, and at first is going to look like a normal application. We have the boilerplate page that we would get from the app template, but now let's go to slash first. We're going to see the same thing here, but notice way down here at the bottom, there's the first works, that is the content from the first component that we created. If we go to slash second, then it will change to second works or it won't. Did I set up the route correct? No, so the path of seconds was being routed to the first component, so that's why we saw the content for this first component, so we can change that to second component, which we need to import. And then once that's done, then whenever we go to slash second, there we have the content that we wanted. So, this is the idea behind routing, you have a URL, and then our Angular application is going to handle that URL and it's going to route it to a component that is essentially a different page. But in this particular case it looks like the majority of the pages are the same and that's because the app component is still being loaded, this is essentially the template or the home of our application. So, if we wanted to, let's keep all of the css, and there's a lot of css. Let's also keep the toolbar, but right here in this div element let's get rid of all of the content inside of this div element. And then at the very bottom you see this router outlet, this is an important component because this essentially tells the angular router where to load the component that we are navigating to. So whenever we go to slash first, the content for the first component is loaded right here. The same is true for the second, and really for any route that we attempt to go to, the output is rendered here at this outlet. So, now if we go back to the browser, we have our navbar at the top, we can see that the second works because we are at the second page. Let's go to first and there we go to first, but if we go to the route of our application, it's not going to put anything there. And the reason is very simple, because the route of our application is our app component. However, we can change that by specifying a different route, so we have first and second as the paths. If we don't have anything for a path, that is going to be essentially our homepage. So, now we have set the first component to be our homepage, it still is going to load the navbar and everything else because that is still being loaded from the app component. But now you can see that we see the content for the first component, but notice the URL, we are at home, there are no segments after the host import. But what about if we go to slash third, what happen there? Well, we see nothing, but we want to display something to the user because slash third doesn't exist. And just like a typical website, we want to tell the user, hey, you tried to go to some place that doesn't exist, and we will look at how to do that in the next lesson. The concept of single page applications has been around for many, many, many years. And even so it is very easy to get into the mindset of using this new technology, this new methodology of creating a web application. And so we tend to just forget everything else that we know about typical web applications, but the one thing that we need to remember is that this is still a web application. Yes, it is very different from a traditional web application, but it is still loaded into the browser, users still expect a typical web application experience so we need to provide that experience. And one of the things that we need to do provide a page for the users when they try to go to a page that doesn't exist. Like for example, we have two pages, the first is the default page which is handled by that first component. Then we have the second page, which all we have to do to go to that second page is change the URL, To have a second segment, and that's great. But if we go to third, well, it takes us back to our default, and we don't necessarily want that. We want to tell the user, hey, you tried to go to this page it doesn't exist. It's not technically a 404, but that is basically what they expect to see. So to provide this experience, the first thing we need is a component that we can use to handle those cases when a page is not-found. So let's generate a new component. We can call it simply not-found. And let's go ahead and change the template so that it says that, you've tried to go to a page that doesn't exist. Now, I know technically we aren't dealing with pages, we are dealing with components, but remember, the end user doesn't know that. To them, this is just another web application. It might behave a little bit differently, might be a little bit more responsive, because it is a single page application, but ultimately from their mindset, they are still working with pages. So the page you tried to go to does not exist. Great, so we have that message. Now we need to set up a route so that when the user goes to something that doesn't exist, we route them to that component. So first of all, inside of our routing file, we need to import the NotFoundComponent. Then we need to define our routes, and we do that by setting the path to a wildcard, it is simply too asterisks. And then of course, we set the component to our NotFoundComponent. And that's it, that's all we had to do. So, whenever, we go back to the browser, of course, the default is going to go to the FirstComponent. Whenever we change the path, the second it, of course goes to the second. And then no matter what we do for the other URLs, it will go to our NotFoundComponent. So third of course, doesn't exist some, random text, of course, doesn't exist. And we can even have multiple segments here, it doesn't matter because it is a wild card, it is a catch-all. So any path that doesn't match the empty string for the default route or the second string for our SecondComponent is going to be handled by this wildcard. Now, since we're here, this is a perfect opportunity to talk about our routing table, because how you define your routes will change the way that your application behaves. Always remember that, Angular uses a first-match policy. So when the application receives a request, it's not technically a request, but I'm gonna call it a request, it is gonna look at that path, and then it is going to look at the routing table, and it is going to go in order. So the first route in our routing table is our default route, where the path is an empty string. So for our default page, it found a match right in that first rule. So let's go to our second page. When our application receives this request, and it's going to take that path, and it's going to look at the first route in the table, and it won't find a match because the path doesn't match. So then it looks at the second rule. Well, there it will find a match. So it will route that request to the SecondComponent. Now, if we move the wildcard route to any other spot within the routing table, then we are going to mess up our application. So in this case, the default route is still going to work, because that is the first rule, it will match that rule. However, anything that we type for the path after that, will be matched by the wildcard because that is the second rule in our routing table. So now, well, we have syntax there, so I need to save that file. So now that the wildcard route is the second route in the table, the URL for our SecondComponent is being handled by the wildcard route. So as you're defining your routes, be mindful of the route order, because it does make a difference and you can very easily break your application by defining your routes incorrectly. One of the most basic things that we want to do within a single page application is navigate to different components, and we essentially have two different ways of doing that. The first uses an a element. So it's very similar to what we would do with normal HTML, the only difference is that instead of using an href, we use a router link attribute. And then the value of this router link is the path that we want to navigate to. So I want to add a link for the home page, in fact, let's get rid of everything else here. And we also want a link to go to our second page. So the path will be simply second. And then for the text, let's have second page. So let's take a look at this. Let's go to the browser and we need our links to be visible. So, let's add some CSS here. For the a elements inside of a toolbar, we'll set the foreground color to white. Let's also add some margin to the right. Let's just set it to 15 pixels, that will be enough to separate the two different items and it is. So now, if you look in the lower left hand corner, you can see the URL for the home is our home URL. If I hover over the second page, we can see that the URL was automatically generated and it goes to the second. So if we click on any one of this links, of course, it is going to navigate us to the appropriate component. That's fantastic, but we have another way. But sometimes we can't always use an a element, sometimes we need to navigate programmatically. So let's look at how we can do that. And what I'm going to do is, lift out the ContactModule from our wish-list. So let's go there. I'm just gonna copy this entire contact folder, because remember, this is the module, everything as far as our contact form is concerned is inside of this module. So we can just copy that. And then we want to go to our router-app, so that we can just drop that in. Now of course, since we wrote our ContactModule to be a module, we can just drop it in at it to import that into our AppModule and then we will be good to go. I mean, that is the general idea behind modules. So let's go to AppModule. And we want to import the ContactModule. Let's see if Visual Studio, yep, it's going to let us import that automatically, so we are good to go there. So that now we just need to set up a route for that. Now of course, we want to define this route before the wildcard otherwise we would never get to the contact page. But we want to set the component to simply ContactComponent. And once again, Visual Studio is gonna make our lives easier and just automatically import that for us. We are good there, but of course, we want to navigate to contact programmatically as opposed to using an a element. Now, we're still gonna use an a element, but we aren't gonna use a router link. Instead we will use an href we will set that to a pound sign or hash whatever you wanna call it, and we're going to handle the click event. So the idea is that this could be a button or something. We are going to navigate to the contact page programmatically. So the first thing we should do is preventDefault, because, yes, even though the href is set to a hash or pound, whatever, we still want to prevent the browser to navigating to that pound or hash. And then let's have a method called goToContact. So that is the code that we will execute, and then the text will be simply Contact Us. So we need to define this goToContact method in our component. So let's go to the app_component and let's add it to the class. Now, we navigate by using a router object, and that can be injected into our component. So let's define a constructor that's going to have a private router, and the type is going to be router, and we import that from Angular Router. And now that we have that router object, we can use its navigate method. Now this is going to seem a little weird, but this is really a good design because what you do is pass an array to the navigate method. That might sound weird, but this allows you to build a URL. So typically, in the past what we do in just normal JavaScript and other frameworks is we build a URL by concatenating strings together, or we use a template string. Or there's a variety of different ways that we can build a URL, but it involves a lot of extra work, which I typically don't enjoy. So what the navigate method allows us to do is pass an array, where the elements in that array are the different parts of the URL, and it will build that URL for us. So in our case we want to navigate to the contact page, and so the path is going to be contact. But if our path was actually contact/us, we could pass in a second argument to that array. And then behind the scenes, Angular is going to build that into an actual URL, and we don't have to concatenate strings or anything like that. It's really a wonderful design. The most basic usage of navigate is that, passing the array that contains the different pieces of the URL, and you're good to go. So if we go back to the browser, we don't see our contact link. Why don't we? Because I didn't save it. That's kind of important. So let's save the HTML. Let's go back. Now we see Contact Us. Now, of course, if we try to navigate to home and second page, that's going to work just fine. But whenever we navigate to Contact Us, there's our form. Yeah, we used Bootstrap, didn't we? So let's go to index.html, and let's see here. I'm going to paste in the CDN for Bootstrap. So it's not going to look exactly the same, but it's going to look a little bit better at least. So here we go. Now remember, all of the functionality was inside of that contact module. So all the validation should still work. So as far as the name, if we give focus to something else, hey, that's required, fantastic. And I want to use my Gmail. No, that's not allowed. So can we try Yahoo? Nope, that's not allowed. So everything else works as it did before, except that now it is in a different app, and we navigated to this form programmatically. [SOUND] Now that we know how to create and use routes and navigate using those routes, let's create a page to display a list of products. And then in the next lesson, we will create a page to display the individual details of those products. So let's start by creating a module, because we're going to be working with several different pieces all related to products. We're going to have the components for displaying our content. We will also have a service to emulate working with the data store. So let's start by generating a module and let's just call it products. Very simple. Then we can go ahead and create the component that will display the list of products. So we will generate that component. We'll call it products/productslist. And then let's also specify that this is inside of the products' module, and then we will generate our service. And I don't remember if we've generated a service using the command line. I remember that we did so manually. So if we haven't, then, well, this is great, we will go ahead and do that. We'll use the generate command, but this time we specify our service and we want products. Let's just call this products. So this will be our products service. And once this is done, now, whenever you generate a service, there is no M flag, so we can't specify to create that service inside of a module. But by using products in front of the name of our service, it gets placed inside of that directory. So we're good to go there. Everything is going to be inside of our products' folder, and that's exactly what I want. So, let's go to our service because it makes sense to start there because we need some data before we have anything else, and I'm going to paste in an array. So, all this class is going to do is provide access to either the data array here or to the individual items inside of the data array. So, yes, this could be a JSON file that we retrieve with HTTP, just like we did with our wishes and stuff. But this gives us a little different approach to that, and this also gives us practice creating our own observables, which is actually quite simple. So for this lesson, we need just one method to get all products. We essentially want to return the data array, but we don't want to just return the array because we want to return an observable that will then provide access to our data array when it is subscribed to, and this is very easy. We're gonna use a function called of, this is from the Arc.js library, and this is going to return an observable of our data. And that's all that we need to do, so that the inside of our product list, we will, of course, need to inject this product's service. So let's do that first, let's open up the code for our component, and we want a private. Let's call this just store, and we need to import the product service. But inside of ngOnInit, we will use our store, we'll call the getAllProducts, and then we will subscribe. This is going to give us our list of products that we can then work with. So inside of this product list component, let's say that we will have an array called products, this will be of type any, and let's initialize it as an empty array. So that whenever we subscribe to getAllProducts, we will simply set the products' property here equal to the products that were provided through our data store. And so this gives us our data for our product list. So let's go to our components template. Let's get rid of the defaults. Let's have a div with a class of container. Let's have another div with a class of row. And then inside of here let's have, I guess, an h4 element, and here we will simply display the product name. But of course we need the ng4 list. I'm getting ahead of myself here. So we want to generate multiple rows based upon however many products we have. So let's go ahead and let's do that here, so that we will use. The ng4 let product of products and that should get us there. Let's go to our app component because we do need to import this or not the app component, we need to go to the app module. And so, let's import the products module and we will add that to the imports array so that then we can set up the route so let's go to our Apps, routing module. And let's set up a route to take us to that product list. So let's import the product list component. Let's add a route. We'll say that this is simply just products. And then the component will of course be products list component. So there we go. With that, we should be able to go to the browser. Let's go to /products, and we should see our list of products, guitar, piano, and drum. Now, if there's one thing that I want you to take away from this particular lesson, it's this. Whenever you start working with a router, the way that you provide data to your components changes. Because, here, our app component primarily exists to display the individual pages that we navigate to. If you remember from our wish list, the app component is where we loaded all of our data. And then we supplied that data to all of the other components that were inside of the app component. Well, now, app component is just a container for everything else. And so it doesn't make sense to load our data inside of the app component because we can't really provide that data where it is needed. So now the components that represent individual pages, are mostly, if not solely responsible for getting their own data. So you can almost think of each individual component that represents a page as its own small application. And so now the primary question we have is how do we navigate to a dynamically generated page? Because now we have a list of products that was dynamically generated. This is completely dependent upon the data store. How do we get to an individual page for showing the guitar information, or the piano, or the drums? Well, we use something called route parameters and you will learn how to use them in the next lesson. We have a page list of products. And so now, we need to page to display the individual products so that we can see the whole lot of information that we have, which is just the name and the price. So, the thing about this is that it needs to be dynamic, because what we have here is dynamic. What we see in our list is completely dependent upon our data. So when it comes to setting up our route, we need something that can handle dynamic data because yes, we could create an individual route for each individual products, but that's not feasible. Our data is going to change. So let's talk about our URL. We have our products, which of course, is our list, and typically what we would do is follow that up with another segment, and then it would be some kind of identifier for a given product. Well, we have IDs to work with, so In our case, our URL will be products/1, 2, and 3. And we can make this work by using what's called route parameters. You can think of a route parameter as a variable for your route. So our route is going to look like this, to where we still have the products part of our URL, but then we are going to follow that up with another segment. And we will begin our route parameter here. It starts with a colon, and then we give it a name. ID is going to work for us because that is what we are working with. And then we just specify the component that is going to handle this route. So let's call it product detailsComponent of course we need to create this so let's hop on over to the command line. We will generate that component inside of the products module. Product details I think we called it, and we also need to modify our service because we only have one method and that is to get all of our products we need one to get just a single product. So with that done We should be able to just import this, we can, and I think that's it as far as our route is concerned. So remember that we have this route parameter as the second segment for our products route. The parameter starts with a colon and then we have the parameter name id. Pretty easy to remember. At least the name will be. All right, so let's go to our product service and we need a method to get a product and let's just call it get product to where we will have an id. Let's make sure that it's the number and this is going to be very simple. All we will do is try to find the product with the given id. So we are going to compare the id property on each one of these product to the provided id, and instead of returning this, we are going to return an observable of the product that was found. So there's our method. We should be able to close that. It says that there's an error. I don't see an error. Where's our error? We need another parentheses there. Okay, so we can close our service because we are done there. And let's go to our product details component. Let's go to the code. And we want to inject our data store here, just like we did in the previous lesson. So that is products service but then we need some way to work with the, Activated route, because remember that this component is handling a route. And since we have a route parameter, which is just some information about the activated route, we need to be able to access the information for the activated route. And we can do that by injecting the activated route. Now, I'm gonna call this route. This is not to be confused with router that we used a couple of lessons ago. Whenever we navigated to the contact page and we used the router, it's the same idea except that we are working with the activated route. Okay, so the first thing that we need to do is get the id from the URL. And the route gives us that. It has a property called ParamMap and this is an observable. We can call subscribe and our callback function is going to work with an object of type paramMap. We need to import this as well. Both the activated route and the paramMap comes from angular router. And then inside of here, we will have access to the ID parameter. We'll call the get method on our parameters object. We'll pass in the string of id and then that's going to get us our id. So let's do that. And then we will feed that into our call to getting the individual product. So let's do this, let's have a property. Let's call this simply product type any and let's initialize it as an empty object. So that after we have the id, we will use our store. We will call get product. We will pass in the id and we need to subscribe there. But that is going to give us our product, which we will then just set to the product property on this class, and that is going to get us there. Now, notice that there's a red squiggly here for the id. That's because getProduct is expecting a number, we are passing a string. One thing to remember is that whenever you work with route parameters, it comes from the URL. And URLs are strings, they are always strings. So you will need to coerce the data coming from your route parameters into whatever type of data that you need. In our case, we need a number. So let's do this, because it could be possible that we don't have an actual value for id. So let's check if we have a value for id. Because in the case that we do, that's really the only time that we want to fetch information from our data store. So now we can convert that id into an integer, we'll pass that to getProduct. And then that is going to work, or at least it should work. There aren't any errors that I see. So let's go to the template, and let's modify our template here, we want to display the product information. So let's have a div of container, I guess we can go ahead and have a row. And then let's have an h3, which we'll start off with the product name, and then with smaller text we'll have the price. So we will simply output product.price, and that should work. Let's go to the browser, let's go to products/1, and we should see, I think that's guitar? Yes, it is, if we go to /2, we see piano. If we go to /3, we see drums. Now the next thing I want to do is add links to our products list, so that the text here will be links, so that we can easily navigate to those pages. So let's go to our products list component. All we need is the template, and we can still use an h4, but inside of the h4, we will have our a element. We will still use routerLink, but we aren't going to use just the attributes, we are going to bind data here. The reason is because now it's more than just a simple URL. We have data that we also need to supply with that route, and we do that with an array. The first element in the array is the first segment, products, in this case. The second element is going to be whatever value that we need to supply to the parameter. In this case, it's the route parameter, so this is going to be the id from our product. And that should be all that we need to do. So let's be sure that our text is wrapped with this a element. But Visual Studio Code doesn't like this, can't bind a router link since it isn't a known property of a, let's take a look at the browser, yep. Yeah, we need to import the routing module to our products module. We can easily get that from the app routing module, so it is this router module. This was automatically imported whenever we created our project. And since the app routing module is imported into the app module, then we didn't have to worry about that. But since we are trying to use the router link inside of our ProductsModule, we need to import at least the RouterModule. So let's do that, let's go to the ProductsModule, we need to import the RouterModule. And that should fix that, so whenever we go back to the browser, there we go, and notice that we have links now. However, notice in the bottom left-hand corner, the URL is products/products/1. So what we need to do is begin our URL inside of our component, so let's go back. So for products, we need to begin that with a slash, so let's save that. Let's go back to the browser, and that URL should be fixed, there we go, it is. So if we click on Guitar, that takes us to the guitar page, if we click on Piano, and of course, Drums, they all work. So route parameters allow us to handle routes based upon dynamic data. Just think of them as variables for your routes. It's not an exaggeration to say that Angular fundamentally changed the way that we build web applications. It absolutely did by making it so much easier to build interactive and responsive user interfaces. In fact, Angular provided the inspiration that can be found in every UI framework today. And now you have the knowledge and tools to use this powerful framework to start building your own applications. Throughout this course, you learned how to use the Angular CLI to create and manage your projects. You know how to use directives and templates to display your application's data. And you know how to bind data to properties and attributes, as well as set up events. You learned how to create your own custom components that handle input, output, and two-way binding. And you learned how to use Angular's dependency injection and HTTP services. And of course, now you know how to build single-page applications using Angular's built-in router. Now, I know full well how easy it is to jump into a project and almost immediately get stuck. But thankfully, Angular's community is so helpful, so don't be afraid to reach out. And of course, you can always contact me through Twitter or the Tuts+ forums if you have any questions. Thank you so much for watching this course. If you found it useful, please like the video and subscribe to Envato Tuts+ for more free courses and tutorials. We have a lot, and there are more to come. From all of us here at Tuts+, thank you, and I will see you next time.
Info
Channel: Envato Tuts+
Views: 141,139
Rating: undefined out of 5
Keywords: angular tutorial, angular tutorial for beginners, angular, learn angular, angular crash course, angular for beginners, angular course, angular full course, angular training, angular basics, angularjs
Id: JWhRMyyF7nc
Channel Id: undefined
Length: 261min 33sec (15693 seconds)
Published: Wed Aug 30 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.