[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.