Angular Fundamentals
-
Getting Started with Angular
Introduction
Hi, this is Jim Cooper, and welcome to this Angular Fundamentals module on Getting Started with Angular. In this module you'll develop a high level conceptual understanding of Angular applications, and you'll create your first Angular Hello World app. We'll also cover some of the prerequisites for this course, and give a brief introduction to some of the technologies that will be helpful to know a little bit about for this course. We'll make this as quick and instructive as possible so that we can dive right into coding. Before we get started, let's talk a little bit about some prerequisites. First of all you'll definitely need some fundamental JavaScript knowledge. We won't be doing anything too complex, so if you feel like you have a decent grasp of basic JavaScript fundamentals, you'll be just fine. If you want to brush up on your JavaScript, choose a course or two from the Beginner JavaScript Learning Path. You'll also want to have a basic understanding of HTML. As long as you can build a simple webpage, that's really all you'll need to know. If you're fairly new to HTML, you may miss some concepts, but it shouldn't distract too much from learning how to create Angular applications. There is also an HTML Learning Path if you want to learn a little bit more. Now let's take a look at a few things that will be helpful to know but not necessarily required. Don't let this list worry you, you'll be just fine without knowing them. This is just a heads up on some of the technologies that we'll be using. First up is Node and Npm. We'll be using Node and Npm to get up and running. And you'll need to get Node installed and functional if you're going to follow along. We'll show you how to do that, but having a little bit of background will make things easier. Joe Eames has a great Npm playbook course to help you along there. We'll also be using some ES2015 or ES6 syntax in this course. We won't be using a lot of this but we will be using some. And Joe Eames and Scott Allen have a great JavaScript Fundamentals with ECMAScript 6 course that you can watch if you want to dig deeper into that. And then we'll also be using TypeScript in this course. You aren't required to use TypeScript to write Angular2 apps, but it is by far the most mainstream approach, so it's what we'll be using in this course. Dan Wahlin and John Papa have a great course to help you learn TypeScript. Again if you don't know TypeScript, don't worry, most of the code we'll be using is just plain JavaScript, and the TypeScript pieces shouldn't throw you off too much. None of the items listed here are required before you watch this course. If you're not familiar with them, you could certainly wait to brush up on these things until after this course and be just fine. We'll guide you through the Node and Npm pieces in the course. And we'll actually introduce you to the key concepts of TypeScript in just a moment, but first, we would like to introduce you to the practice exercises that we provided for this course.
-
Practice Exercises
To help facilitate your learning, Joe and I have created practice exercises for a lot of the concepts taught in this course. When we learn a new technology, it's always helpful to be able to practice what we're learning. While it's certainly helpful to your learning to follow along and create the demo application that's demonstrated in this course, there is a lot of valuable learning that can come from trying to do something without having all of the answers provided beforehand. We seem to really cement our learning when we have to figure out why something we've done isn't working like we'd expect. It helps to break down misconceptions in our learning. So for many of the clips in this course, we've created related practice exercises on Plunker. These practice exercises are all available on my website at this URL. And we'll let you know at the end of each clip if that clip has a corresponding practice exercise. You can then find the practice exercise on this page which is organized by course module and then by clip. For each exercise, you'll find a link to the clip in the course, a link to the Plunker exercise, and a link to the finished example for you to compare against when you're done or for you to use to get clues from. So if I click on one of these practice exercises, notice that it takes me to Plunker, and the instructions for this exercise can be found here. Read these carefully and you should be able to finish the exercise with what you just learned in the course. And you can get back to these instructions at any time by clicking this button over here. If you're not familiar with Plunker, here's how it works. Here are all the files for this Plunk. You can just click on these files to edit them or add new files in order to complete the exercise. Notice that to put a file into a sub-folder, you just name that file with the folder name followed by a slash. Then to try out your project, you just click Run. Notice when I run this, the Plunker just displays my running site here starting with the index HTML like a normal web server would. And depending on the exercise, you'll see something different here, and the app may be broken and you'll have to fix it, or it may be working and you'll just need to change something about it to finish the exercise. And if you get stuck or just want to compare your solution to our solution, you can come back to the list of exercises and click on this Finished link to see our finished version of this exercise. We hope you'll enjoy these fun little exercises, and hopefully they'll help you cement what you're learning in this course.
-
Introduction to TypeScript
We'll be doing very little TypeScript in this course, but we will be using some, and it will be helpful for you to understand a few TypeScript concepts. Let's take a look at a high level overview of what TypeScript is. First of all, TypeScript is a superset of JavaScript. That means anything that you can do in JavaScript, you can also do in TypeScript using the exact same syntax, but it also means that there are additional features that are available for use. So how is this accomplished? It is accomplished using transpilation. So when you write your TypeScript code, you're using a mixture of plain JavaScript plus TypeScript features, then your build process runs the TypeScript compiler and transpiles all the TypeScript code into plain JavaScript code. That's an important part about TypeScript. Your TypeScript code is only a development environment construct. TypeScript doesn't give you anything in production that you couldn't have accomplished with plain old JavaScript. It simply gives you more options for how you want to code and think about your code. It is basically a syntactic sugar that is enforced via a transpilation process. So there is a TypeScript compiler that you run that handles this transpilation, and if you write any code that violates the rules of TypeScript, the compiler will emit an error and fail to do the transpilation. So it gives you some development time or build time checking that helps to prevent some runtime errors. One nice thing about TypeScript is you can use as little or as much TypeScript as you want. Really you could write all JavaScript and pass it through a TypeScript compiler and it would work just fine, but of course there's no point in using the TypeScript compiler at that point. Again we won't be using a lot of TypeScript in this course, so let's just take a quick look at the few TypeScript features that we'll be using. This includes static typing, interfaces, class properties, and accessibility levels such as public and private. So let's look first at static typing. Static typing allows us to specify data types for variables, properties, and parameters, et cetera. In plain JavaScript, we declare variables like this. Adding typing with TypeScript is as simple as adding data types to the variable declarations like this. With this in place, if you were to try to set any of these to an invalid value, for example setting age to a string value, your TypeScript compiler would throw an error and fail to transpile, catching potential bugs earlier in the process, and that's all there is to typing in TypeScript, and you can use as much or as little as you'd like. Taking typing a little bit further, you can actually define interfaces for entire objects, and that will enforce the shape of objects. So if we wanted to ensure that all cats had a string name property and a numeric age property, we could create an interface like this, and then you could declare variables of this type like this. Given this declaration, we will get compile time safety if we try to assign an object to fluffy that does not include these fields with these types. So let's take a look at some examples that would fail to compile. First, there's this example. This one would fail because age is set to a string value instead of to a number. Let's take a look at another example. This example would fail to compile because we have not provided an age property. If we want the age property to be optional, we could add a question mark to the interface declaration for the age property like this, and now this example would work just fine. And again you can choose whether to use interfaces or not when doing Angular development, but we will be using a few of them in our course. Now let's take a look at TypeScript class properties. Given a simple ES2015 class like this, we can initialize the name of a cat in the constructor like this. This is just plain ES2015, not TypeScript, but we can make it more explicit that we have a name property by declaring it here. Adding this didn't really change any functionality but it's more explicit. We can more easily see that our cat has a name property. We could also define properties that are not initialized in the constructor like this. And actually the types are not required. I could just declare these properties like this and that works just fine. We just don't get any compile time type safety on those properties. Now that we have these properties, you may wonder where these properties are accessible from. For that, let's take a look at our final TypeScript topic, public and private accessibility. Take a look at this class, we basically have three members on this class, the name property, the color property, and the speak function. Of course we can access all of these internally in the class so I could access the name property in the speak function like this. I could also access the speak function from other functions in this class. However as defined here, these three members are also accessible outside of the cat class. That's because class members are public by default in both ES2015 and TypeScript. I'm going to simplify this class a little bit just to give us a little more room to type. So let's say I create a new cat here, I can now access the class members outside of the class like this. So what if I wanted any of these members to be private, meaning I want to be able to access them from within the class but not externally? Well I can just preface them with a private keyword like this. Now I'll get compile-time errors if I try to access them here, and that's all there is to making things private. Remember class members are public by default, and you have to explicitly mark them as private if you want them to be accessible only within the class. Now let's take a look at one last use case for private members that's pretty common. Consider this class, it's pretty common to receive values as parameters in the constructor like this, and then set corresponding private member properties to the values passed in like this. Since this is so common, TypeScript has created a shortcut for all of this that looks like this. Notice how much this simplifies this common practice. There's not even any assignment code in the constructor. The TypeScript transpilation is going to just generate code for us based on the fact that we marked these parameters as private. We'd call this constructor the same way like this. So it's not that these constructor parameters are actually private, obviously we can still access them externally to pass values in, it's just a shorthand for creating private properties that are initialized via the constructor. We'll use this a number of places in the course. And that covers pretty much everything that we'll be doing with TypeScript in this course.
-
Comparing Angular to AngularJS
When the Angular team released Angular2, it was a dramatic shift from Angular1, and now they have different product names. All Angular 1.x versions are now referred to as AngularJS, and all versions starting at Angular2 and above are referred to as Angular. Let's take a quick look at a couple of high level differences between AngularJS and Angular. Angular1 was pretty much an MVC or a model view controller framework. Angular2 and above kind of departs a little bit from that, although it's not difficult to relate the concepts together. In AngularJS you had a view or a template, and the view would refer to a controller, and your controller would expose models or objects that represented your data. In Angular you have a component, and that component has an associated template, and as with Angular1, you have models that represent your data. So at a glance you can kind of relate these together. However in Angular1, the template was kind of in control. Your template would identify one or more controllers that controlled sections of your page, whereas in Angular, components and templates really are a one-to-one. In fact you can kind of consider the template to be a part of the component, and you can actually write your code like this, and have your HTML write inside your component if you choose. In this way a component in Angular is actually more comparable to a directive in Angular1. In Angular1 you could define a directive that allowed you to basically create custom HTML elements which encapsulated both display and functionality in a more cohesive unit. If you wanted a sort of a list component that let you display an array in a stylized table with sortable columns, you could just create a directive that gave you a sort of a list element, and the directive contained all the logic and HTML to make it all come together. Something about the directive bringing it all together made it feel very cohesive. That's basically what components are except it feels a lot smoother. As awesome as directives were, they often felt clunky, and you would run into weird edge cases that were quite difficult to solve. Components feel much more smooth.
-
A Conceptual Overview of Angular
Okay we're going to dive into code real quick here, but first let's take a high level look at Angular. Once we understand the conceptual model of how Angular works, it will be easier to understand each of the pieces as we start coding them. So let's take a graphical look at an Angular application. When you navigate to the URL of an Angular application, there is always a root application component that is loaded first, and then the Angular router takes a look at the URL and says, "Oh I have a component that matches that route," and it loads that component. The component's template is then displayed in the browser, and the component may then load some data from the server and give it to the template to be displayed. Of course if this page is very complex, it's likely to be composed of lots of smaller components, and those components may be composed of other components. And so this kind of ends up creating a tree-like structure. It's helpful sometimes to think about your Angular app in this way. So here's the tree view of our imaginary application. Now imagine what happens if a user navigates to another URL. Well, then basically you start a new tree except that your app component remains. The router sees the new route and loads the corresponding component and all of its sub-components, and this repeats for all of your routes. As your application gets bigger and bigger, this can become a lot of stuff to load into memory. This is where Angular modules come in. Don't confuse Angular modules or NgModule with ES2015 modules that we've been talking about before. Angular modules are meant to be containers that group all of these routes or component trees and their corresponding bits of code into modules that can be loaded independently of each other. That way if a user only ends up visiting this section of the site, only these files will get loaded, and the browser will only load these other files if the user navigates over into this section of the site. This is one of the main purposes behind Angular modules. You don't have to use multiple modules in your app, but as your app gets larger, you may consider it. One more high level concept regarding modules that is important to mention is how modules are used to make components, services, and directives available to the rest of your application. When you create a component, service, directive, or pipe, you register it within a module. That makes that object available for use by everything else in that module. And with all of these except for services, they are now only available in that module. If you want to use them in another module, you must register them with that module also. That's true for all of these except for services. Services or providers get registered in the root injector so they're available across Angular modules. So that's kind of our high level view of Angular applications. Let's take a look at what we'll actually be building in this course.
-
Here's What We'll Be Building
Okay let's take a quick look at the demo app we'll be building in this course. We'll build an application that displays and allows users to create technology events or conferences. The starting page is this page that lists all of the upcoming events. From here we'll add the ability for users to create new events using this Create Event form complete with validation. And then we'll wire things up so that users can click on one of these events to see more detailed information about that event. This page displays sessions for the event, and users can vote on the sessions that they like. You can even create a new session for the event. And we'll even add the ability to sort and filter the sessions to find what you're looking for. We'll also add in some basic authentication and edit profile features. If this app looks familiar to you, you may have watched the Angular1 Fundamentals course. This is the same application that we built in that course which gives you the ability to compare how things used to be done with AngularJS and how they're now done with Angular. So without further ado, let's start writing some code.
-
Installing Git and Node
We're going to need a few tools installed to get going. First, we'll need Git installed. I'm on Windows so I'm going to use Git for Windows which can be downloaded from here. If you're on a Mac, you can install SCM Git from here. And if you're on Linux, you can just run a command like this one from the terminal. I'll go ahead and download and install Git for Windows. And then I'll run that, and I'll just use the defaults throughout the install. And now that that's installed, I have this new Git Bash console that I can run, so we'll use that in a minute. The next thing I need is Node. I prefer to install Node using Node Version Manager just because it makes it easier to switch between versions of Node as I develop different applications. On Windows I can download that from here. For Linux and Mac, you can just run this command from the terminal. So I'm going to go ahead and install nvm for Windows. And I'll just use all the default options here too. Okay now that that's installed, I'm going to open my Git Bash console. On Windows be sure to open it as administrator. Okay now that I have nvm installed, I can just run nvm install 6.3.0. It's recommended that you use Node 6.3 for this course or 6.3 or higher to avoid any compatibility issues. So Node is downloading version 6.3, and now I can just tell nvm to use this version of Node. Okay we're good to go with Git and Node now. If you have problems using nvm to install Node, you can try installing it directly from here. Just choose the correct installer for your system. Okay let's get coding.
-
Getting Started with the Angular CLI
Okay so here's the Git Bash console that we just installed. The best way to get started with a new Angular project is to use the Angular CLI. The Angular CLI is a command line interface that we can use to create a new project complete with Webpack config and tools for packaging up your app for production, plus a host of other features. It's highly recommended that you always start your projects with the CLI. So to get started, let's install the Angular CLI. We can do that with npm like this. This will install the Angular CLI globally so that we can use it to create new projects. And in order to avoid any issues for those who are following along, we're going to install a specific version of the CLI like this. We recommend that you use this version of the CLI if you're following along just to avoid any surprises with new versions. Okay now that that's installed, we can create our new project like this. So this will create a new folder named ng-fundamentals, and it will generate a new Angular project with Webpack and everything we need to build for production. So let's go ahead and do that. Okay now that that's complete, let's go take a look at what it generated. So I've opened up that new folder in Visual Studio Code here, and here's the project the CLI created for us. So notice that it created a number of things including a package.json file and this angular-cli.json file. This file is used for configuring a number of settings for our project that the CLI will use for various things including its Webpack builds, and it created this end-to-end testing folder and installed some Npm packages for us, and then if we look at this source folder that it also created, you can see that it created this app folder. This is where you'll do most of your coding in an Angular project. And it also created this assets folder. This is where you put static assets like images and site-wide CSS files, et cetera. We'll take a look at this in a minute. For now let's go make some small changes to this package.json file. If we scroll down and take a look at our dependencies, you can see we're installing approximate versions of our dependencies. For the purposes of this course only, I want to pin these exact dependency versions so that anyone following along won't get surprised by any dependency version changes. So I've already created a package.json file with the exact pin versions that were installed by Npm at the time this course was written. In order to make this easy, we've created a little GitHub repo for this course where we can grab a few files like this throughout the course. It's available at this URL here. So you can see down here that we have this package.json file. So I'll click on that and then look at the raw version here, and then copy everything out of here, and then I'm just going to paste that in here. Okay so you can see that the versions here are now specific versions, and we're not using the carets and tildes anymore so that we'll always install these specific versions. Now we just want to do one more thing to ensure that you're definitely working with the same package versions that we are in this course. Over here you can see that we also have a package-lock.json file. Let's grab that too. So let's look at the raw version, and then copy that, and then we'll come over here and we'll create a new package-lock.json file, and we'll paste that in here. Now there's just one last thing that we need to do in order to make sure that you're using the same package versions. If we come back over to our terminal window where we created our project with the CLI, let's go ahead to move into the folder that it created. And now we're going to delete the Npm packages from our node_modules folder, and then we'll reinstall them. Again all of this that we're doing with the package.json files and reinstalling the Npm packages isn't necessary for normal development. This is just to make sure that those that are following along with this course don't get hang up if some dependency makes a breaking change. Okay so now that we've deleted those packages, let's go ahead and reinstall them. Okay excellent. So now we're back at the point where we've generated a project with the CLI and our package versions are locked in, so now all we need to do is run it. We can do that like this. Okay so you can see that this is now running at port 4200. So let's go check that out. So over here in my browser I'm just going to navigate to localhost4200, and here you can see that we have a fully functioning app that just generated this default boilerplate HTML. Now let's go take a deeper look at what the CLI did to get our new Angular app bootstrapped.
-
Bootstrapping an Angular App
As we saw in our last clip, the Angular CLI created a fully functioning Angular app including everything needed to bootstrap this app, but it's really helpful to understand how an Angular app is bootstrapped, so let's dig into that a little bit. We talked earlier about the hierarchical component tree, and at the top of that component tree is the app.component. And as you can see, the CLI created our app.component for us here. Notice that the component is composed of three files, the component, its template, and its styles. We'll talk in-depth about components in the next module. Just be aware that the template from this app.component is what's being displayed when we load our site, so let's look at how that's wired up. The bootstrapping of our app begins with this main.ts file. And if we look over here in our angular-cli.json, you can see that this main property is pointing to our main.ts file. This is used by the Webpack config for our site, and it causes our main.js file to be loaded when our app first loads. This is used by the Webpack config for our site, and it causes this main.ts file to be loaded when our app first loads. And you can see right here that we are bootstrapping our app with our AppModule. If you remember from earlier, Angular applications are grouped into modules, and every Angular application has an AppModule. That module is defined here. And you can see that the AppModule is bootstrapped with the AppComponent, so that's how Angular knows about our app component. But there's one more piece, this makes Angular aware of our component, but we haven't seen yet where we tell Angular to actually display this component. If we look at our app.component, you can see that it has this app-root selector. This selector defines the HTML tag to use in order to display this component. And if we look over here in this index.html file, you can see that we're using that app-root selector right here. This index.html file is what is first displayed when our app loads, and it is loading our AppComponent. So that's how this all comes together. Now to show that there's nothing magical about the naming of this app.component, let's just rename this to events-app.component. Remember our app that we're building is a website that will allow users to find technology events, so we'll call this the events-app.component. And instead of renaming its template and CSS files, we can just inline them. We'll talk more about this later, but let's just delete this template file and this CSS file. There wasn't anything in here anyhow. And then here in the component, let's just change this selector to events-app, and then we'll change templateURL to just template, and hard code some basic HTML here. And then since we're not applying any styles to this component, we'll just delete the styles. The CLI also created this spec.ts file, but Joe will talk about testing later in the course, and so for now we'll just delete that. And finally let's just rename the component class to EventsAppComponent. Okay now that that's done, we just need to go update a few places that reference this component. So first in the app.module, we need to fix this import. So this will be EventsAppComponent, and we'll import that from events-app.component. And then we need to declare that, and bootstrap our app with it. So notice there's nothing convention-based about the name of the app component. It's really here that we tell Angular which component is our main top level app component. And then one last place we need to update is over here in our index.html because we changed the selector. So this is going to be events-app. Okay cool. So now if we go check out our site, you can see that it's still working, and now it's using our new template. So to recap the whole bootstrap process, the main.ts file is loaded by Webpack via the angular-cli.json file. Then the main.ts file loads our app.module, and that makes Angular aware of our app component. Then our index.html file is loaded in our browser, and it loads our app component which uses this template here. And that's it. So that's the basics of how to bootstrap an Angular app. Next we'll take a look at how to pull in some static resources into our site.
-
A Brief Look at the App Module
You will get some more experience with modules as we add different components, services, directives, and pipes to our app throughout this course. But since the CLI created an app.module for us, let's take a brief look at it. We talked in an earlier clip about the purpose of modules. This is our first chance to see what one actually looks like. Specifically I just want to mention these three sections, declarations, imports, and providers. When you want to add a component pipe or directive, you must declare them here like we're doing with the EventsAppComponent. Services however are added as providers here. And this imports array is used for importing other modules. Importing a module makes all of its exported declarations and providers available to this module. So you can see we're importing Angular's BrowserModule here. This makes a number of core Angular services and directives available to us that are commonly used throughout the Angular app. Pretty much every Angular app will import this in the AppModule. If you create a service, component, pipe, or directive, you need to be sure to add it to a module before you try to use it. You'll get some experience with this and with creating other modules like feature modules as you explore this course. I also explore in-depth the different types of modules you can create in my Angular Best Practices course, so feel free to check that out if you find yourself wanting to learn more about modules after this course. For now let's delete this providers array since we don't currently have any services. We'll add this back later. And I kind of like to have imports at the top here since declarations and other things depend on those imports. There's no real technical reason for doing this. This is just a personal preference. Okay cool so that's our AppModule. Now let's go take a look at how to use static assets in our site.
-
Accessing Static Files
Now that we have a basic site set up and running, let's take a look at how to access some static files like images and site-wide CSS files, then we'll be ready to really jump into building our Angular app. Let's start by looking at how we would access an image file. The Angular CLI has created this assets folder, and this is where we put our static files, so let's create an images folder. Okay now we need some images. For those who are following along with building this project, we've created a GitHub repo where we'll grab some files from throughout this course. It's available here at this URL. So inside this source folder, you can see that we have this assets/images folder. We just need to click on each one of these, and then click download, and then save image as, and then we'll just save that in this assets images folder. And then I'll go ahead and download the rest of these files off-camera. If you're following along, go ahead and download each one of these. Okay so now I have all of these images downloaded here and put in these assets images folder. So let's go ahead and add one of those to our app component. So over here in app, in our events-app.component, let's change this to a template string so we can add a new line. And we'll just add an image tag. And we'll go ahead and set the source to the assets folder images/basic-shield.png. And if we go look at our site, there we go, we can see that that image is showing there. Now it's important to understand what exactly happened here. First of all if we take a look at our image source, what is this path relative to? Well it's relative to the index.html file, but you can't statically access any file that is relative to the index.html file. Webpack needs to know which files to bundle up when it builds the app. So the only reason why this is working is because if you look in our angular-cli.json, by default the assets folder is included in our assets array. This array is an array of paths that we'd like Webpack to include in our app bundle, and these paths are relative to the index.html file also. And that's how you make files statically available to your app. This make sense for static files like images, but for static CSS and JavaScript files, there's a different convention. Notice down here that we have styles and scripts arrays. This is a place to reference styles and scripts that you'd like loaded with your app. Notice that by default, it is loading this styles.css file. If we go take a look at that file, you can see it's empty, but we do have some site-wide styles we want. To avoid typing those in, let's go grab them from our helper repo. So in the source folder we have this styles.css file. We click on that and then click on raw. We can copy all of that out, and we'll just paste that in here. So these are some styles that we'll be using throughout the course. Okay cool so since this styles.css is referenced from our styles array in our angular-cli.json file, these styles will now be loaded globally. So notice if I change the background color to black for the body, and then go over and refresh my app, there we go, we can see that the background color is now black. Alright cool, let's go remove that. And now let's take a look at how to load some third party vendor styles and scripts. We've actually created an Npm package for use with this course that will give us some Twitter Bootstrap CSS styling. Twitter Bootstrap is a CSS library, and we've created an Npm module that has some specific styles that we want for this course. So if we go over to our terminal and stop our server here, we can install that like this. So this is an Npm package that we created. Okay cool now if we go look in our node_modules folder, in addition to that ngf-bootstrap folder, we also have a bootstrap folder, so that was a dependency of ngf-bootstrap. And we have a jQuery folder. So jQuery is also a dependency of that. I want to be clear here that jQuery is not required for Angular development. This is just part of that ngf-bootstrap package that we pulled in. So there are a few files from here that we want to use. Let's go ahead and start up our server, and come back over to Visual Studio Code. We want to load some things from those directories that we just installed. So if we go back over to our angular-cli.json file, then let's add above our styles.css, let's add node_modules/ ngf-bootstrap/, and then in the dist folder, there is a bootstrap.min.css. Okay so this will load that CSS file. And then we want a script file from bootstrap that's also in the dist folder, in the js folder, there's a bootstrap.js file. And then bootstrap uses jQuery for some of its menus so let's grab that also. And notice for these files, we're having to back up our directory to go into the node_modules directory because this is relative to the index.html file. And it looks like I am missing a slash here. Okay and since this is changed to our Webpack config which is a service I think we need to stop and restart our server, and then if we go look at our app, cool, you can see that the background color of our site has changed. This is because we're now getting our bootstrap styling. And let's add a little bit of pathing to our site. We can do that by adding a bootstrap container. So over here in our index.html, we can just on the body add a class container. So that's a bootstrap class. Okay now if we go look at our site, there we go. Now our site has some nice pathing. So now you know how to load static files in your Angular apps. Now let's get to the real fun, let's go create our first component.
-
Summary
In this module, we explored some of the prerequisites for this course, and then we took a look at a conceptual overview of how Angular apps are architected. We then looked at the Angular CLI and how to use it to create new Angular apps. We also explored the app component created by the CLI and how the CLI bootstrapped our Angular app. And finally we learned how to pull static assets into our app. All of this sets us up really well to get started building our demo app and exploring Angular. In the next module, we'll learn the fundamentals of Angular components as we start creating our own components and explore a host of concepts related to creating, styling, and communicating between components.
-
Creating and Communicating Between Angular Components
Introduction
In this module, we're really going to get into the meat of components. This is where working with Angular really starts to get interesting. We'll start by creating a component with an inline template and data-binding. And then, we'll take a look at how to optionally move that template into an external HTML file. Next we'll explore the various ways to communicate between components. And then, we'll see how easy it is to apply CSS styling to our components. And finally, we'll explore how Angular provides built in encapsulation of CSS styles to prevent them from bleeding out to other components. So let's jump right in.
-
Creating Your First Data-bound Component
Okay, so we've created our first Hello World component, but that isn't a terribly interesting component. In this module we'll really start having fun as we get into the meat of using components. In just a few minutes we will have created a component that starts to demonstrate the power of Angular. To demonstrate this, we're going to create a page in our app that displays all of the upcoming events. So let's get started. Okay, I'm going to create an events folder in my app folder. This will hold everything used to display and work with events in our app. And then let's create our events list component. Eventually this will display all of our events, but to get started, let's just get it displaying a single event. This will be a really great canvas to demonstrate some really cool basic Angular functionality. So, just like we did with our last component, we'll need a class that will eventually contain all of our business logic for this component. We'll call this our event-list component. And of course we're going to need to import our component decorator, and decorator class with it. Okay, and this will just have a selector of events-list. Now we just need to find the HTML content for this component. To get started, let's just display a title. We can do that like this. But that HTML is really hard to read, so to make our HTML easier to read, we're going to change this into a JavaScript string literal like this. We'll change these apostrophes to back ticks, and now we can have multi-line HTML. If you're not familiar with string literals, they were added to JavaScript in the ES6 specification. In addition to some other functionality, they allow you to define multi-line strings like we have here. This allows us to create a much more legible HTML in our components. Okay, so now we have this basic component that we can start adding some interesting functionality to. Let's just wire this up so that we can see it working. To do that, let's open up our events-app component, and instead of displaying Hello World, let's pull in our component. So we've created our component, and we're pulling it into this page or this other component. And it feels like that would be enough, but really we're missing one step. Let's go take a look at our page real quick. First we'll make sure that our server's running, and that will compile our TypeScript for us. Now let's go take a look at our site. So if I refresh, notice that Hello World is gone, but our new component isn't showing up. This can be a kind of frustrating state. It seems like we've done everything right, but if we look at our console, you can see that there is an error here saying that events-list is not a known element. Remember, we have to register all of our components with our module, and we haven't done that yet. So let's go do that. So over here in our module we need to add our new component to our list of declarations. And of course we'll have to import that. Okay, let's save everything here and take a look at that. So if I refresh here, there we go. Now our events-app component is referencing our events-list component, and that events-list component being displayed here. Cool, now let's go make it do something more interesting. First let's create some sample data that will represent the data that we will eventually get from an API. For now we'll just add that data as a field to our class like this. Okay, so now we have an events object that has fields like name, date, price, et cetera. Now we just need to update our HTML to access that data and we'll do that right here in our inline template. Let's start by displaying the name of our event. First I'll add a little divider after our heading. And then we'll display the event name like this. Those of you who have done development in Angular 1 will immediately recognize this doubles braces notation. This is interpolation and it represents a one-way binding. When you put something inside these double braces, Angular will look for that object on the component. So in this case, Angular is going to look for the events-list component class and expect to find an object named event, and it will find one because we've created it here. And then it's going to expect to find a property called name on that object, and it should find that here. So let's take a look at that. Awesome. You can see here that we're now binding to our event and displaying the event name. Now let's display the rest of the properties like this. So we're just going to use some Bootstrap classes here to give our event a little bit of styling, and then we're just going to start displaying each of the properties on the event. So let's move this down here, and actually we don't need two of these. And then we'll just display the rest of this data. And then we need the escape this dollar sign, because we're using a string literal, and dollar sign curly brace is syntax that's specific to string literals, and so we have to escape that dollar sign. Okay, now if we go refresh our page, you can see that we're displaying all of our event data. That was easy. Hopefully you're already getting to see how quickly you can build applications with Angular. There's a lot more to learn, but Angular really does make building applications easy. So we're starting to get a fair amount of HTML in our template here, and we had to do something weird here where we had to escape the dollar sign. In a later part of the course, Joe will talk about Angular pipes and that will take care of our currency formatting, it won't be this dollar sign here. But still, this template's starting to feel like it's getting a little bit bigger, and you may way to move this HTML out of your component. In the next clip we'll take a look at how we can have our template files separate from our component files. And there's a practice exercise for this clip, so go check that out.
-
Using External Templates
Okay, now let's see how we can move our template HTML out into its own file. For very simple components, it's really nice to have the HTML right in the component. But often there's enough HTML that it's easier to have it in its own file. So let's create an events-list component HTML file right here next to the component. Then we'll copy this HTML out of here and into our HTML file, and since this is no longer in a string literal we don't need to escape our dollar sign any more. Now in our component, instead of using template, we just need to use templateUrl, and then we just provide the URL here to our template. And notice this URL is relative to our component. And now if we go take a look, our app should look the same. There we go, it looks exactly the same, except now we're loading our HTML from a separate template file.
-
Communicating with Child Components Using @Input
Okay, so you can see how creating little components like this is pretty easy. Breaking our app up into lots of little components will make our application well organized and easy to maintain, but it won't be very useful unless we know how to communicate between them by passing data and events back and forth. In this clip, we'll learn how to pass data from one component into a child component. Before we jump into this, I want to call out a small inconsistency you might be noticing, starting with this clip. If you take a look at the file explorer over here, you may notice that the files here look a little different from the last clip, and they don't match what was generated from the CLI. For example this app directory is not nested inside a source directory. This is because when this course was first created, the Angular CLI was still in its infancy, and we've since updated the course. Since all of our development for the rest of this course will pretty much happen inside this app folder, there's no real need for us to update the rest of this file tree for the rest of the clips. Don't worry much about that inconsistency. If you're following along, stick with the file structure you've already built up from the CLI. Okay, so let's take a look at component communication. Right now our events-list component is only displaying a single event, but we'll eventually change this to display multiple events. So to make things a little more organized, let's create a new event-thumbnail component that will encapsulate all of the display logic for a single event, like this. And then our events-list component can just worry about showing multiple of these event-thumbnail components. Okay, so let's create that event-thumbnail component. And now let's add the basic component code. Okay, so there's our basic component. And since this HTML is pretty straightforward, let's just put it inline in the component. And then we'll have to escape this dollar sign again. Okay, so now we have this component, and it's all built to bind to an event object. But the question is, where is this event object going to come from? Our events list-component is going to eventually contain a list of events, and it will want to create one of these components for every event. That means that the event data is going to come from the events-list component. So how do we pass that data in? Well first of all, we need an event object in our class that our template can bind to. With TypeScript we can declare that as a public property of our component like this. This is just creating a property called event and telling TypeScript that is of type any, which is really just saying we don't care what data type it is. Later in the course, we'll start giving out variables data types so that we can take advantage of TypeScript's type safety and intelligence. But for now, any will suffice. So now we have this variable here, but there's nothing here that tells Angular to expect a value to be passed into our component. That's where the input decorator comes in. So let's import that, and that comes from Angular Core 2, and now we can just add that decorator to our event property like this. So this input decorator tells Angular that this event will be passed in from another component. And don't confuse the word event with JavaScript events. Again this is just an event like a technology event or a conference. So let's go see how we pass in this event to our component. So we'll go back over to our event-list component, and let's just take this HTML out of here and put it back inline in our component, because it's very basic. Okay, so we don't need this empty HTML file anymore, so let's delete that. And now our event-list component needs to display an event-thumbnail component. We can just do that like this, so that matches the selector that we added to our thumbnail component, and again right now this is still just displaying a single event, but we'll update this later to show multiple events. But we still aren't passing the event data into this component. Well that's super easy. All we have to do is add an attribute like this. Okay, so we have too many things called event here and it's going to be hard to talk about, so let's rename one of our events to event1. So we'll rename this here to event1, which means that we have to rename this one to event1. So these two values correspond to each other, and this event corresponds to our input property over here. So when we add brackets around an HTML element like this, what we're saying is that this event thumbnail has an input parameter named event, and we want to pass the value of event1 from this component into that component. So it's important that this matches the name of the input parameter and that this matches the name of a member in our component. You may think it's odd to have an HTML attribute that has brackets in it, but this is actually valid HTML. The HTML spec does allow for special characters like brackets in attribute names. We'll talk more about these brackets and lots of other syntax items in the next module on template syntax. But for now, that pretty much covers wiring up these two components with each other, except that we've forgotten to declare our new subcomponent as the component in our module declarations. So let's go do that now. So we need to import it, okay, and then we just need to add that to our list of declarations down here. Okay, so we're all wired up, let's go take a look. First let's go take a look at our dash console where we're running our server. So notice that our server is running in watch mode. So it is constantly watching for any changes and recompiling our TypeScript for us. If I scroll up you can see it has recompiled multiple times as I've been saving changes. So let's go take a look at our app now. If I refresh this, there we go. So it looks exactly how it did before, but now this event thumbnail is coming from a different component and it's getting its data passed into it from its parent component through an input parameter. So it's pretty easy to pass data from one component to another. You simply create an input parameter, or property, on a component, and then in your HTML you pass that in using the square bracket binding. Alright, in the next clip we'll see how to pass data out of our child component back to the parent, and there's a practice exercise for this clip, so go check that out.
-
Communicating with Parent Components Using @Output
Okay, so you've seen how to pass data into a child component, but what if you want to pass data back out to the parent? We'll demonstrate how to do that here, and then you'll get some good opportunities to use component input and output throughout the course. When you think about component input and output, it's pretty common that input parameters are used when constructing a child component in order to give that child component its data. Output on the other hand is quite often used in response to some event within a child component, so that the parent can receive some information when some event, like a click, occurs within the child component. At this point in our application there's not much for our child component to do, but we'll demonstrate here the basics of how to use output parameters, and then you'll get more practice with some examples later in the course. Okay, to demonstrate this, we'll just add a button on the thumbnail component, and we'll add a click handler to this. Okay, we haven't talked about this type of binding and we'll talk about it more in a later clip, but basically this is just wiring up to click on a button to a function called handleClickMe on a component that we haven't created yet. Okay, so let's just go add that function to our component, and for now let's just console log something. Alright, let's just go take a look at this so that you can see that that click handler works. If I open my console and click on my button, there, it says clicked. Okay, that's working, but we're still not doing anything to tell our parent component that anything happened. In order to do that we're going to have to add and output property to our child component like this. Okay, so we've decorated this event click property with the output decorator, and we're setting it to a new event emitter. This is a common pattern you'll see with output parameters since typically output parameters are used to convey some event that has occurred. Don't confuse the word event here with our events application. The word event here is about JavaScript events, and this event emitter is an Angular thing. So let's import the output decorator and the event emitter, and now let's emit and event with this event emitter each time the button is clicked. And then we can pass a bit of data along with this event too. For now, let's just emit the string foo. Alright, so this component is now outputting this data when the button is clicked. Let's go make our parent listen to that. We just do that by adding another event handler to the child component's element in our parent component like this. So this eventClick binding has to match the eventClick output property that we created on our child component here. Basically we're saying that when the eventClick event is fired on our even thumbnail component, call this handleEventClicked method on my component. This dollar sign event refers to the data emitted with our event, and remember in this case it is the string foo. You can only pass along a single value with an event emitter, so if you need to send multiple values, you can just wrap them in an object first. So let's go ahead and add this handleEventClicked method. And we'll just console log the data that we received. Okay, let's go save our files here, and then let's go take a look at it. So if I refresh and open the console, now if I click my button, there we go. Notice it says "received foo." So that value foo is what we emitted from the child component and then we console logged that out in our parent component by binding to the eventClick output property. We can make this a little bit more interesting by emitting some actual data from our child component, such as the name of the technology event that's being displayed in the event thumbnail. So let's come over here, and here we're receiving in our conference event, and that has a name property on it. So let's just log that up. Alright, let's take a look at that. So if I refresh this now, now if I click on this, notice it says "received Angular Connect," which is the name of our event that we're displaying on our event thumbnail component. Okay, so that's all there is to passing data back to the parent component to a child component. And you get some great opportunities to practice using this later in the course with some real data that makes more sense. For now we're not going to need this demo code, so let's get rid of this handleClickMe event handler and the output parameter, and we'll get rid of the button. And then over in our parent component we'll stop handling the event and get rid of the event handler down here. Okay so that's all cleaned up and again you get a chance to practice this a little bit more later in the course. And there's a practice exercise for this clip, so go check that out.
-
Using Template Variables to Interact with Child Components
Okay, so we've covered how to communicate in and out of components using input and output properties, but there's one more way that we can use to access public properties and methods of a child component, and that can be quite useful for different use cases. This approach can be used to call methods on a child component or to bind to data in a child component. And we can accomplish this using template reference variables. Template reference variables allow you to specify a variable name that points to a component, and then you can access any public properties or methods on that component using that variable. So let's check this out. Let's add a variable name to our event-thumbnail component and we'll just call it thumbnail, like this. Now this variable is available for us to use anywhere else in our template. So let's go create a public method on our child component that we can call. So over here in our thumbnail component we'll just create a method called logFoo. And of course that will log foo. Alright, now back in our parent component, let's just add a button that when we click on it it can call this new method on our child component. We'll just add that right here. And then we're going to wire up our click event to our thumbnail local variable, which is a pointer to our component, and so we can call the logFoo method on that component. Okay, let's save everything, and then let's go check that out. I'll refresh, and there is my button, and this is on my parent component, and if open up the console and then click this button, you can see that it logged the word foo. And so we were able to call thumbnail.logFoo and thumbnail is pointing to our event-thumbnail component and over in our event-thumbnail component this is where we actually logged foo. So you can see, this is actually a public method on our event thumbnail component, and you can see now we're able to access that using a reference variable. I really like how straightforward that is. In some ways this is more simple than input and output parameters. And there's another case where we can use this template variable, and that is if we want the parent to bind to a public property on a child component. So let's add a property on the thumbnail component. We'll just add that right here, and we'll call it someProperty. And let's just set that to a string. And now we can access that someProperty property over in our parent component using our template variable. So let's add an h3 here, and then we'll just bind directly to this with interpolation. So again here we're using ut thumbnail local variable and we're going to access someProperty. Okay, let's go check this out. You can see that some value is being written out here to our parent component even though that value is in our child component. And we can access that using our thumbnail template variable. Okay, so that's the thee different ways that you can handle inter-component communication. Input properties, output properties, and template variables. And we're not going to need this template variable stuff, so let's just go ahead and clean that up, and we'll clean it up over here in our child component too. Okay, that's all there is to local variables, and there's a practice exercise for this clip, so go check that out.
-
Styling Components
Let's take a look at how we can apply CSS styles to a component. Typically when you want to apply styles to a web app, you create CSS files that contain your styles, and there are lots of different approaches to doing this in a maintainable way. With Angular you can still handle CSS as you would with any other app, but Angular components have some built in functionality for applying styles to your component that can be very useful. First let's look at applying some simple styling to our even thumbnail component. If we take a look at our template, you can see here that we're using a non-breaking space to add some blank space between the address and the city. Let's use a class to style that instead. So let's delete that non-breaking space and add a pad-left class to the city span. Now in our component we'll add a styles array to the component config. The styles array takes an array of strings, but typically you'll just provide a single string with all the styles for your component, so let's add our pad-left class here. Cool, let's go take a look at that. Okay here's the current padding with the non-breaking space, and if we refresh you can see we're getting a little bit more padding, but we no longer have that non-breaking space in there so the padding is coming from our class. Alright, now let's add some styling to our text. We'll slightly change the color of the event details, but we'll leave the event name alone. So we want to style all these divs inside this well. So we'll just add this class, and let's see what that does. Okay, now the text is a little darker. So it's kind of cool that you can put your styles and your HTML and your component all together like this in one cohesive piece. It's really that easy to just add styles to your component. It's worth noting here that just like we can put our HTML in a separate file, we can also put our CSS in separate files and refer to them here. We just use styleUrls instead of styles and provide and array or URL strings for our style sheets. There's a little bit more that we're getting for free without styles here, and we'll talk about that in our next clip. There's a practice exercise for this clip, so go check that out.
-
Exploring Angular's CSS Encapsulation
Now if you've done much work with CSS you may be thinking we're headed for trouble because we just added a fairly broad CSS selector. Typically if you add broad CSS selectors like this, you run the risks of changing the style of every div within a well across your entire site. In order to avoid this, it's becoming common to use some sort of name spacing type standard such as BEM or SMACSS, where your class names have the names of components or pages appended to them, or there is some higher level class that contains all of your code. Then when you apply styles to your elements, you style them using very specific class names. This can become cumbersome and can really interfere with the semantics of your code. Fortunately this is all taken care of with Angular. Let's take a look at how this works. Just to make it a little more obvious, let's change the color of this class to red. Now let's go over to our events-list component, which contains our thumbnail component, and add some content to demonstrate this. Before we show our event thumbnail, just above our event thumbnail, let's add a div within a well like this. So this matches the CSS selector here. In a traditional HTML app, a style like this would get applied to the whole site once the style is loaded. But if we go look at our site now, I'll just refresh this, you can see the style only got applied to our child component where we added the style. It did not affect elements in the parent that match that same CSS selector. So Angular is taking care of encapsulating our CSS styles and causing them to affect only the component where they're added. You might think that this is working this way simply because we've added it to a child component. What if we add the style to a parent component instead? Will it affect the child components? Let's take a look. So let's move this style over to the parent component, so we'll need a style attribute. Okay, let's go take a look at this. Okay, notice here that it is now only affecting the parent, and it is not affecting the child. So angular really is just scoping this to the component where we've added it. So what exactly is going on here? This is Angular's built-in view encapsulation. If we take a look at this element in the HTML, you can see that Angular is applying attributes to all of these elements. Notice that our parent component has this attribute applied to it, ending in ety-2, but the child component, its elements, and have an attribute that ends in ety-3. And then if you look at the styling or the classes that are being applied, here is our selector that we created. .well div, but Angular has added this attribute, ngcontent-ety-2. This is randomly generated string, so Angular has modified the CSS selector to say, only apply it to wells that have an attribute of this ngcontent-ety-2 and divs that have that same attribute. And this is how Angular targets only the elements from that component. So this CSS was made very specific without us having to even think about it. This is great. We get safe, name spaced CSS without any thought on our own part. Now one thing to know is that we are getting global styles applied across our site. In other words, styles from our styles.CSS and our Bootstrap styles. To demonstrate this, let's go over to our styles.CSS and add a style that targets h2 elements. Okay, so now all h2's across our site should be green. Let's go take a look. Okay, sure enough. You can see that this h2 element turned green. And so you can use global styles that apply across your site, and you could even use BEM or SMACSS or something else in order to organize your global CSS. But when you want your CSS to be very specific and tied only to your component, which is actually quite often, Angular just takes care of it for you. And you can mix global styles with component specific styles, which we will be doing throughout this course. So that's a good demonstration of how CSS encapsulation works with Angular. Let's go clean up some of this stuff that we don't need anymore. So we'll get rid of this h2 styling and we'll move our style back over to our thumbnail component and change it back to its normal color. And then we can get rid of this Hello World div over here. And then one last thing that I wanted to mention that we're not really going to cover in this course is that there is a way to get around the CSS encapsulation if you want to apply styles to a child component. All you have to do is apply the deep selector. That's fairly well documented, so if you find you have a need for it, go check that out.
-
Adding a Site Header
Okay, just to demonstrate that the app component is no different from other components, and to get a little more practice creating components, let's add a site header with some navigation elements to our app component. So right now in our app component our template is just displaying this events-list component. Eventually when we add routing to our application, this component will be replaced with a router outlet component. And as we navigate around the site, each top-level page component will be displayed here. So we want to add our navigation component above this. The nice thing about this is that this navigation element will be displayed on every page, since it's in our top-level component. This will make more sense later when we add routing. For now let's just get the navigation element showing up. Okay so first let's create our component. This component will be mostly used for navigation, so let's create a new folder for it called nav. Alright, now let's create the component. Alright, and then let's create our basic shell for our component. Okay, there's our basic component, now the HTML for this component will be a little lengthy. So, let's use a separate template file. Okay, now let's go create that file. Alright, since the HTML is a little long for this component and typing it in isn't too helpful to learning Angular, we've added a GitHub repo over here, and this is where we'll share a few helper files for this course. So let's just grab that HTML from here. So it's in source, app, nav, and it's this navbar.component.html. So let's just go to raw view and we'll copy that out of here, and the we'll just go paste that into our template file. Alright cool. Now I'm not going to spend any time explaining this HTML, it's all just simple HTML, and there's no Angular code in here. And all the CSS classes currently used here come from Bootstrap. So this component is basically ready to go, so let's just go add it to our main app component, again we'll add this above the events-list component, so let's just change this to a string literal and we'll add it here. Remember nav-bar is the selector that we gave to our new component. Okay cool, now we have this nav bar that will show up at the top of our application, and then the page components will show up underneath that. Okay, let's not forget to add our new component to our module, so we'll just import it here and then add it to our declarations. Okay, that's all there is to adding this component, so let's save everything and then let's go make sure our web server's running. You might have it running already, if not go ahead and start it up. Alright, let's go refresh our app. So if I refresh here, cool, now we have this nice nav bar up here. But we have some strange font issues going on here and we'd like a little bit more spacing over here to the right of our search component. So let's go add some styles to our component. So add styles right here. So first let's adjust font size for everything in the nav bar, and then let's add some margin to the right of our search form. Okay, and then we need to hide our search form that's in our nav bar if the browser window gets too small, so let's make that happen. Okay, let's check that out. Alright, that looks a lot better. Okay so now we have this new component, and hopefully that helped you see what the flow is like to add a new component to an app. Okay, that's it for components, in our next module we'll take a look at some more Angular syntax.
-
Summary
In this module we created our first data-bound component, and we saw how to use an external template for our components. We also learned about a few ways to communicate between components, and how to style our components. And finally, we learned how Angular keeps our styles encapsulated to just the component where they're defined.
-
Exploring the Angular Template Syntax
Introduction
In this module, we're going to explore all the fundamentals of template syntax in Angular. We'll start off by looking at interpolation and expressions, which allow us to bind to and display data in our templates. We'll explore what is and what is not allowed in expressions and the types of behaviors that are discouraged in expressions. We'll then take a look at the syntax for binding to DOM events and the statements that accompany them. Then we'll look at how we display repeating data with ngFor. To do this, we'll display a list of events on the event list page, instead of just a single event. In this module, we'll also take a look at how to optionally remove elements with ngif and how to do that in more complex cases with ngSwitch. We'll also look at how to hide DOM elements by binding to their hidden property and when you might want to do this versus removing them with ngif. And finally, we're going to take a look at various ways to programmatically add and remove CSS classes and styles to and from DOM elements. So let's go take a look.
-
Interpolation, Property Bindings, and Expressions
Okay, consider this block of code. We have a component here with an inline template. And our component has a user property and a do something function. Let's take a closer look at our template. This is interpolation and this is a property binding. Both of these are used to bind data from the component to the template. Interpolation is used when you just need to display that data, such as displaying the user name in an h2 like we are here. Whereas a property binding is used when you want to bind data to the property of a DOM element. In this case, we're binding the user's image URL to the source property of this image tag. Interpolation and property bindings both use expressions to specify the data from the component to bind to. As you can see, to use interpolation, you enclose an expression in double braces. But to bind to a property, you put the property in square brackets and the expression in quotes. Expressions are interpreted by Angular and typically reference a property on the component. In both of these cases, we are referring the user property on the profile component. However, expressions aren't restricted to just component property bindings. You can actually use a JavaScript like expression. For example, instead of using user name here, we could use an expression like two plus two and Angular would evaluate this expression and display the value four. I could even call a function on my component like this. But you can't use any expression here and there are some recommendations on the types of expressions you should use. So let's take a look at those. First, let's look at the expression restrictions. Expressions cannot use assignment operators, such as equals, plus equals, plus plus, etc; They also cannot use the new keyword to create new objects. You are also restricted from chaining multiple expressions together into a singular expression using semi-colons. And finally, you can't access anything on the global namespace, such as console, window, etc; These are the most notable restrictions. Now let's take a look at some recommendations for expressions. Perhaps most importantly, expressions should have no side effects. This means that calling an expression should not change any data or the state of the application in any way. Angular applications should have a uni-directional data flow and changing the state of the application while evaluating expressions can have some nasty consequences. Next, expressions should be fast. Expressions can get called more frequently than we realize. So it's important that they be fast. Expressions also should be simple. You don't want a lot of business logic in your templates. That really belongs in your components. And finally, expressions should be idempotent. Or, in other words, each time you call an expression it should return the same result. This is largely maintained by making your functions have no side effects.
-
Event Bindings and Statements
Okay, so we've looked at interpolation, property bindings, and expressions. Now let's take a look at event bindings and statements. This event binding is binding the button's click event to the do something function on our profile component. So that will be called when this button is clicked. Notice that event bindings use parentheses around the element event to bind to versus property bindings that use square brackets. And notice that statements follow an event binding and are put in quotes. Let's take a look at the guidelines for template statements. Template statements have similar restrictions to template expressions with a couple of exceptions. Statements actually do allow you to make assignments using equals. But assignment operators other than equals are still prohibited. And you actually are allowed to chain expression statements. But these other restrictions still remain for statements. And as far as recommendations for statements, they actually can have side effects. In fact, they often do. It's pretty much the norm for statements to make changes to the state of the application. So, therefore, they are also not required to be idempotent. And while you never want any part of your application to actually be slow, statements will typically end up calling a function or something that makes an Ajax call or something that's slower. So there isn't quite the same requirement for them to be fast. So the only real recommendation for statements is that they should still be simple. Again, you shouldn't be doing a lot of business logic in your templates. They can however call a function on the component that is more complex. So really, this is the only recommendation that remains for statements.
-
Repeating Data with ngFor
Before we jump in, I just wanted to remind you that we made changes to this course to keep it up to date. This course was originally created without the CLI since it was still in beta. Because of these updates, you may notice some inconsistencies in the file explorer over here. Mainly, this app folder is now nested inside a source folder and these files at the root of the project changed. However, almost everything we will be working on will be inside of this app folder. And everything inside of that folder will be consistent other than that this assets folder has been moved out. So don't worry about the inconsistencies you may see outside of this folder. Okay, the events application that we've been writing has been desperately waiting for us to demonstrate ngFor. So far, this events list page has been listing only a single event. Let's update our application so it can display multiple events. ngFor is going to make this very easy for us. And the fact that we've encapsulated all the display logic for an event into this event thumbnail component is going to help. So first, let's update our data to have more events. Currently, we just have this one event object here. Eventually we'll have this data come from an API call. But for now we'll just leave it right here. This is going to be a lot of data so we're not going to want to type all of this in. So this is another file that we've provided in our helper repo in GitHub. So in this repo, under the miscellaneous directory, there's this event data js file. So let's look at the raw view for that. We'll copy all of this. Then, we're just going to paste it all in right here in place of this event object. Okay, so you can see this is now an array called events. And that array now contains multiple event objects. And the event objects are the same shape as they were, except that they have a little bit more data. You can see that we added sessions to the events. Don't worry about that right now. We'll use that data later. All of this data is really obnoxious in our controller. We'll move that out of here when we talk about services in the services module and eventually it'll come from an API. But for now, it'll just stay here. So our page will be broken right now because we were binding to an event object that was singular. And now we have an events, plural, array. And so up here we just need to use ngFor to bind to our new events array. So we want to put the ngFor directive on the element we want to repeat for each item in the array. So that's our event thumbnail element. So we'll add it here. Okay, and then we'll leave this binding here except it's going to bind to event instead of event one and that is the same event as this one right here. So we'll come back and dissect this statement in a minute. But for now, let's go over and take a look at our webpage. So if I refresh this page here, you can see that I'm now displaying multiple events. That was really easy. Okay, so let's take a closer look at this ngFor statement. The first thing that stands out is this asterisk. That indicates that this ngFor directive is a structural directive. Structural directives are different from other directives because structural directives actually change the shape of the DOM. They actually add or remove HTML elements from the HTML document. They don't just hide them, they actually remove them or add them. ngFor is a structural directive because it will add an HTML element for each item in the array. And so it is prefaced with an asterisk like this. Okay, next let's take a look at the expression that's being passed into the ngFor directive. Looking at the first part of this expression, it does just what you think. It is declaring a variable. So we're defining a variable called event. And then you can see we're accessing the variable in our template binding here. So this is creating a local variable just like we did with the hashtag syntax when we we're talking about component communication using local variables. And the rest of this syntax is something specific to ngFor. This just tells Angular that events is the array to loop over and for each item in the array ngFor is going to duplicate this event thumbnail element and assign the value of each array element to this local variable. We can then use that local variable anywhere inside the element that's being repeated. To demonstrate this a little bit further, let's move this ngFor statement up to a higher element. So we'll put it on this div here. So now this entire div is going to be repeated. And you'll see that event is still accessible down here. But now we're going to get a lot of extra stuff repeated too. So let's go see what this does. Okay, you can see that this heading is now being repeated also. So ngFor will repeat the element that it's put on plus all of the elements inside of it. Let's go ahead and move this back down. Okay, now let's just style this a little bit. We're going to use Bootstrap to add a row class on a containing div around this event thumbnail. And then we're going to create a column class div element and that will also contain our thumbnail element. And then let's move our ngFor up to this div element. Okay, so this is just going to take advantage of Bootstrap's grid classes in order to build a grid of these elements. And so, this div with a class of column five, is going to be repeated for each event. Let's go take a look at that. Alright, cool, so now we have each event showing as a little thumbnail on our page. And all we needed was that simple ngFor statement. Let's just go fix the styling on this so that these are all the same height. So we'll come over to our event thumbnail and we'll just add a style right here. Okay, let's see how that looks now. Alright, cool, so now they're all nice and uniform. It's really nice to be able to think about styling a component and know we'll just go to that component to add the styles instead of trying to think about a separate hierarchy of CSS files and thinking about where to go find those files. Okay, cool, so that's all there is to using ngFor. And there's a practice exercise for this clip, so go check that out.
-
Handling Null Values with the Safe-Navigation Operator
So far, we haven't had any problems with null or undefined values in our data. But we need to be aware of issues that missing data can cause us when we're binding to objects with interpolation like we are here in the event thumbnail. What if this event object we're binding to is undefined? If you've done development with Angular 1, you may expect this to work just fine. But in order to avoid hidden runtime bugs, Angular isn't quite as forgiving as it used to be. Let's just see what happens if this event is undefined. That's easy enough to do. Over here, where we're calling our event thumbnail component, we just won't pass in this event. So that will cause the event to be undefined in the event thumbnail component. So if we go look at our site. Okay, looking at this, at first glance you might first think that it's working without errors and just not displaying the data. But if we look at our console, we can see that our app is broken and displaying errors here. And if we scroll up to the top, you can see this error, cannot read property name of undefined. And up here, you can see that the error occurred in our in line template on line two. So if we go take a look at that template, you can see that on line two we tried to bind the name property of this event object that was undefined. Just like you'd expect in any JavaScript code, this throws an error. So what do you do if the event might be null? That's where the safe navigation operator comes in. We can just put a question mark after the event like this. And we'll have to add that everywhere we use the event in this template. Okay, let's go take a look at that. First, we'll clear our console and then refresh. Okay, you can see that we're not getting errors anymore because it's safely handling that undefined object. One thing that's interesting to take note of here is that we handled the case where event is null but notice down here where we have event.location.address that we didn't blow up even though location is essentially undefined because event was undefined. That's because the safe navigation operator short circuits the evaluation of the expression so that you don't have to put question marks throughout your entire expressions, if you know that, for example, location won't ever be undefined if the event is defined. So that's working. But what if our location object can be undefined even for a valid event? Let's change our data a little bit so that we have another use case for this. So down here in our data on our second event... We'll collapse these sessions here. So on our second event here, that is for ng Netherlands, what if this event had no physical address but was online only? So we'll change this like this. Okay, so now this doesn't have a location and does have an online URL. So let's put this binding back that we removed earlier. And now if we go back over to our app and refresh, you can see it's broken again because we now need a safe navigator on the location object. So event was defined but location was undefined. So let's add a safe navigation operator here. And while we're here, let's go ahead and add support for online URL. Okay, let's have a look. So we clear our console again and refresh. Okay, we're no longer getting errors. And you can see that this is working. So we're not getting a location here and we are getting an online URL. But this is kind of ugly how the location is showing up empty here and then we have these empty online URL fields on all the other events. It'd be nice if we could just hide that data when it's blank. In the next clip, we'll see how to do that using the ngif directive. And there's a practice exercise for this clip, so go check that out.
-
Hiding and Showing Content with ngIf
Alright, let's take a look at how we hide these fields when they're empty on these events. ngif is a built in structural directive that allows us to show content only when an expression evaluates to true. So hiding these fields is easy. Let's start by showing the locations section only if the location is not null or undefined. We can do that by using ngif like this. Okay, so this is just checking to see if location is truthy. So if the location is set then the ngif directive will do nothing. But if the location is not set, then this expression will evaluate to false and the ngif directive will remove this element completely from the DOM. Let's take a look at how this is working. I'm going to refresh my page. Great, now you can see that location is hidden on our ng nl event but it is visible on the others. Now let's hide all these empty online URL lines in all these other events. We'll just do the same sort of thing down here. Okay, let's check that out. Alright, this is looking a lot better now. And if we take a look at the HTML, you can see that Angular didn't just hide them with CSS. They're actually commented out here which means they're not even being rendered in the DOM. This can be a great performance saver if you know that you're not going to need this element again for a while, especially if this element is something that's costly to render or to generate. Our case is really simple. But what if this was an actual component we were hiding and that component would actually go and fetch data from the server. In that case, it's nice to not even render the component in the DOM so that it doesn't have to go do all that work. On the other hand, what if we were going to show and hide this section frequently, say based on a button click or a mouse hover that toggles its visibility? If we completely removed this and added a back each time, Angular would have to do all that work to render it every time we show it. In that case, completely removing it from the DOM can be expensive. It would be better if we could just hide those elements. Let's take a look at that next. And there's a practice exercise for this clip, so go check that out.
-
Hiding Content with the [Hidden] Binding
We just demonstrated how to remove these location and online URL elements from the DOM completely using ngif. And we talked about how that's great if you know that you're not going to need them. But if you're going to show and hide them frequently, simply hiding them is better from a performance standpoint. So how do you hide an element in Angular? Well, the same way you'd do it with HTML. You just add the hidden attribute to the element. That's not an Angular thing, that's just HTML. But Angular's ability to bind to DOM properties makes it easy for us. Remember we talked earlier about how you can bind to any DOM property on any element. Well, hidden is a DOM property. So we can just bind to that like this. Instead of using ngif here, we could bind to the hidden property and then we're going to set that to a Boolean value so that hidden will show up if this expression shows true and it will not show up if it returns false. So we want something like this. But that will hide it if the location exists. We want to hide them if they don't exist. So let's not that. Okay, cool, and let's do the same thing down here for online URL. Okay, let's go take a look at that. I'll refresh. Okay, now it's still hiding location and online URL when they're not present. But now it's actually hiding them. If we take a look at the HTML, you can see that the location object here is still in our DOM but it's hidden. So you can see that Angular applied the hidden attribute to this element. And the same is true for the other online URL elements in the other events. So you can see that these approaches are different from the standpoint of what gets rendered in the browser even though from the user standpoint it looks the same. It's good to keep these two options in mind when you're deciding to hide things in your DOM and to consider the performance benefits of each approach. In our case, these are going to stay hidden. And so we want to put this back to just ngif. Okay, that'll do. And there's a practice exercise for this clip. So go check that out.
-
Hiding and Showing Content with ngSwitch
Okay, so we've shown how to change the visibility of elements using ngif and by binding to the hidden property of a DOM element. But what if there's a part of our document that we want to change based on multiple possible values of an expression? That's where ngSwitch comes in. So let's add something to our event that will tell us whether the conference starts earlier or later based on the event time. So we're going to add a div right here. And inside that div we'll have three spans. One that says early start. One that says late start. And then, another one that says normal start. Okay, and then we want to show and hide these based upon the event time. So I'm going to add an ngSwitch up here and that'll just get bound to the event time. And notice for ngif and ngSwitch and other things, we also have to use the safe navigation operator to guard against nulls. Okay, so now I have a ngSwitch that is bound to my event time. Now, on my spans, I just need to add ngSwitch case statements. And then I set this to the value that should allow early start to be displayed. So, in this case, I'm going to bind this to the value of 8:00 a.m. Okay, so this span has an ngSwitch case directive that is bound to the string value of 8:00 a.m. Let's do the same thing for these others. So, for late start, we will bind it to the value 10:00 a.m. And then for normal start, this will be our default case so if 8:00 a.m. doesn't match and 10:00 a.m. doesn't match then this will be displayed. And we do that with ngSwitch default. Okay, so now we have this div that's bound to our event time using ngSwitch and the cases are early start, late start and normal start with normal start being the default. Okay, so let's go take a look at this. So I'm going to refresh my page here. Okay, so cool, you can see that this one that starts at 10:00 a.m. says late start. This one that starts at 9:00 a.m. says normal start. And this one that starts at 8:00 a.m. says early start. Okay, cool, that was easy. Now we would like these values to show up right after the time. So let's go over and take a look at our HTML here. So they're showing on a new line because this is a div not a span. Really we want this to show up right after the time up here on the same line. So just to demonstrate there's nothing special about this ngSwitch. It doesn't have to be on its own div. We could take this and move that up here like this. And then we could move these spans up here and delete this div. And since this will be displaying right after the time, let's add some parentheses around it. Okay, let's go take a look at that. Alright, cool, so now that's showing up on the same line. Now our ngSwitch example here is using event time which is a string. And so the ngSwitch case values are also strings. Notice that they are wrapped in apostrophes. But ngSwitch doesn't have to work with strings. It can be any data type. And in the ngSwitch case statement expressions should also return the same data type. And that's all there is to using ngSwitch. And there's a practice exercise for this clip so go check that out.
-
Styling Components with ngClass
There are a couple of ways to conditionally add CSS classes to elements with Angular and they're both pretty easy. Class bindings are good if you're wanting to toggle a single class. And the ngClass directive is better if you're wanting to toggle multiple classes. So let's take a look at class bindings first. Let's make it so that the start time of event turns green if it is an early start event, meaning it starts at 8:00 a.m. Okay, so on this div that surrounds the event time, we're going to add a binding that looks like this. Okay, so this looks a lot like a property binding. But there is no property class.green on a div. So what is this? This is a special type of binding called a class binding. And it is parsed by Angular and it's basically saying that if this expression event.time equals 8:00 a.m., returns true, then add the green class to this div. Okay, so let's add that class to our styles and then take a look at it. Okay, I'm using important here because otherwise this style will get overwritten by another one. This has nothing to do with the fact that we're using a class binding. It's just the nature of the CSS that exists in our app. And I could make this CSS binding a little more specific and not use important. But we'll just do it this way for now. So let's go take a look at this over in our app. Okay, cool, you can see that time is green only for the 8:00 a.m. event and not for the others. So this is working great. But what if we also wanted to add a second class, say a bold class to this element? That's where ngClass comes in. Let's create a bold class. And then we'll use ngClass to add both the green class and the bold class. We can do that with an ngClass expression like this. So instead of a class binding, we'll use ngClass. And then, here, we'll return an object. And that object will have two properties on it. One for each class we want to apply. So the first one will be green and then it'll have an expression that looks like this. Okay, so this ngClass expression will apply the green class if the event time equals 8:00 a.m. And then let's just apply a second class and we'll use the same expression here but we could use a different one. Okay, so the ngClass binding is going to expect an object where the object keys are the names of the classes you want to add and the values are a Boolean expression that determines whether or not that class should be shown. So this will add the green class and the bold class if the event time is 8:00 a.m.. So this should be working. Let's take a look. Okay, cool, so now both the green and the bold classes are being applied to this element. Okay, if we come back over to our code and we look at this expression that we've applied for ngClass, it's starting to be a lot of logic to exist in our template. So, instead of this, let's actually call a function on our component. Alright, now let's add that class to our component or that function. Alright, and then, rather than running the calculation twice, let's run it once and assign it to a constant. Okay, now we can just return our object. Okay, cool, this should be working the same. And it is. And then, I had said earlier that ngClass expects an object to be returned. That isn't exactly the whole truth. You can actually return an object like this or you can return a string, which is space separated list of the classes you want applied. Or you can return an array of strings, which represent the classes you want to apply. So let's see how this would look if we were going to return a string. So basically we would replace this with an if statement. And then inside here, we would return a string with the classes we want applied if this is true. And otherwise, we'll return an empty string. Alright, so this should be working too. Great. And then the last thing that we could do is instead of returning a string, we could return an array or an empty array. Okay, and that should work just fine too. Cool, so you have various different approaches that you can use here depending on the needs of your application and your particular style. And then one last thing that I want to mention here. What if on the element that you're adding ngClass to or doing a class binding on, what if this already had a class applied to it? Something like this. Well, that's okay actually. What would happen here is the well class would always be applied to this div. And then any classes that are applied conditionally with ngClass will be added in addition to this class. And that's true for both class bindings and ngClass. And so that's the two different ways you can apply classes to elements. And there's a practice exercise for this clip. So go check that out.
-
Styling Components with ngStyle
And just like you can apply a single class to an element with a class binding, you can also apply a single style to an element using a style binding. To demonstrate, let's replace this ngClass with the style binding. We'll just make the font green again if it's an early start event like this. Okay, so this is going to set the color based on this ternary statement. If the event time is 8:00 a.m., then the color will be green. Otherwise, it'll be this gray color. So this should behave the same way as the previous ngClass binding, except that we're not applying the bold font weight. So let's take a look. If I refresh, there you can see it's green but it's not bold. Okay, so if we want to apply that bold class we'll need to use ngStyle. And to do that inline, it'll look like this. Okay, so this is a lot like ngClass, where we're returning an object. Only in this case the keys of this object or the property names of the object are styles like color and font weight and the values are a ternary statement that will set those values based on whether the ternary is true or false. And this will work like this but this is really a convoluted statement to have in a template. So let's go ahead and break this out into a function like we did with ngClass. So we'll replace this with getStartTimeStyle. And that will actually help make our logic a little more simple. So let's come over here. And we're going to replace this with getStartTimeStyle. And our if statements can be the same. But what we'll return is an object like this. Okay, so this is just returning an object with the styles we want. And font weight had to be put in quotes because it had a dash in its name. And then down here, we'll just return an empty object if the start time is not 8:00 a.m.. Okay, and because these two objects we're returning are different shapes, we have to set the return type of our function to any like this. Just to satisfy type script. Okay, let's go take a look at this. Okay, there we go. So we're now getting both of our styles applied though that function. And just like with ngClass, if we were to add a style directly to this element like this, then the styles applied with ngStyle are additive. So this style will always be applied and ngStyle would optionally apply any styles from that function. So let's delete this. And since we're using ngStyle now, we don't need these classes. And of course, we could've left ngClass and used that instead of ngStyle. But either one works fine. And there's a practice exercise for this clip. So go check that out.
-
Summary
In this module, we learned about interpolation, property bindings and expressions and we learned about event bindings and statements and what is legal and recommended for each of these. We also learned how to repeat data using ngFor, how to remove elements with ngif and ngSwitch, and how to hide elements using the hidden property binding. And finally, we learned how to add classes and styles, using class bindings or ngClass and style bindings or ngStyle. In the next module, we'll take a look at creating our own reusable services.
-
Creating Reusable Angular Services
Introduction
In this module, we'll take a look at how to create reusable services and how that helps us keep our components and our app clean. First, we'll explore why services are necessary in the first place. Then we'll talk briefly about what dependency injection is and what it means in an angular app. And then we'll dive into creating our first service with Angular, which will finally get all that ugly JSON out of our EventsListComponent. And then finally we'll talk about how to wrap third party libraries in Angular services so that they can be injected and used in your Angular apps just like any other service. Cool, let's go take a look.
-
Why We Need Services and Dependency Injection
Okay let's talk for a minute about why services are necessary? Imagine an old-fashioned record player. The record player had an arm with a needle at the end, that would move across the album as the album spun around on the turn table. It was a brilliant device for its time that allowed an unlimited number of artists to produce albums, which we would then buy and play on our oh so cool turntable. But the record player had one responsibility. To run the needle over the album and translate that into sound. What if the record player had attempted to also be responsible for the music? What if the albums were embedded into the player itself? Whenever a new album came out, you'd have to sell your old player and buy a new one. Not very practical. It was good that the record player was only responsible for playing the music, not for producing or storing the music itself. We want the pieces of our application to be like a record player. And we've made a good start. Over EventsListComponent is responsible for listing events but not responsible for what each event looks like. That's the responsibility of our event thumbnail component. But our EventsListComponent is currently doing more than it should. It's not only responsible for listing the events, it's also currently responsible for defining and supplying the event data. That really should be the responsibility of some other part of our application, especially once we start adding in logic to make a request to an HTTP end point. This is where services come in. Services allow you to define business logic in a separate file, and then inject whatever service we need whenever we need it. Kind of like choosing which record to put on our turntable and this is where dependency injection comes in. Our record player doesn't get to determine which album is placed on to or injected if you will, on to its turn table. The same should be true of our EventsListComponent. So we could do something like this in our EventsListComponent. Here we've created a new eventsService. And we are using it to get our events. So this is one step better. At least now our EventsListComponent doesn't have to worry about how to get the event data but there is a problem here. The EventsListComponent is mandating that events come from an eventsservice. In fact, it's this very specific new EventsService. What if elsewhere in our application we decided in some cases, we should get the data from an Ajax call and in other cases, we should get them from the local browser storage. We'd have no way to use a different type of eventsService. It's like our record player is saying sure, I'll let you give me the albums to play, but they all have to be Elvis Presley albums. So we'd like to be able to pass in the instance of the eventsService to use. That way the EventsListComponent isn't mandating where they come from. This is dependency injection and it looks more like this. Notice here that we are injecting the eventService as a parameter into the EventsListComponents constructor. We are then calling getEvents on the service that is passed in. This code is a Little oversimplified. We really shouldn't be doing a potentially long running call like GetEvents from a component constructor. But this gives you the general idea. The point is that someone else is deciding what instance of the eventService we are using. So let's go write some code to make this a reality.
-
Creating Your First Service
Before we jump in, I just wanted to remind you that we've made changes to this course to keep it up-to-date. This course was originally created without the CLI, since it was still in beta. Because of these updates, you may notice some inconsistencies in the file explorer over here. Those inconsistencies have been explained previously. Okay so as we've seen our EventsListComponent has all of our data hard coded right here. Eventually we will be getting this data from an API and we could just add that API call right here in our component but then our component starts to take on too many responsibilities. We'd really like to just let another service take care of the details of making that HTTP call. It would be nice if we could just call a function here and not worry about the implementation here in our component. So let's create a service that will take care of that for us. Alright so inside our events folder, we will create a new folder called shared where we can put some shared elements. And then in that folder we'll create a new typescript file for our service. Okay so in here, we're going to create and export an eventService class. And it will have a method on it to retrieve the events. Alright so eventually this GetEvents method will be the thing that will make the Ajax call to fetch the events from a server. But we'll get into that later. For now, let's just hard code the events and return them directly. So let's grab them out of our EventsListComponent. We'll just copy this array out here and delete this and then over here in our service, we will just add it as a constant down here. Okay and then we'll just go ahead and collapse that so it's not distracting, and then we'll just have our getEvents method return those events. Okay so here we have our service. It's just a class and technically, this is all we really need. We could now inject this service into our component or other services. But it's always a good practice to mark services as injectable like this. Adding this injectable decorator is important for any service that you're going to inject into your components or another service. And it's important that you don't forget to put parenthesis on the end of here like this. That's an easy thing to forget. This injectable decorator isn't really required for this service. Because this decorator is only required when you inject a service which also injects other services as dependencies of its own. Just to clarify what I mean by injecting a service, I don't mean imports. I mean if you have a constructor that injects services like this. So now that this eventsService injects the HTTP service. This injectable decorator is required. And since you never really know if a service is going to take a dependency later, it's just a best practice to always add it. We don't need this HTTP dependency yet. So let's undo that but we will leave the injectable decorator even though it's not technically required yet. So now we just need to let our app know that this service exists. We do that by registering it in our app module. First let's fix this spelling error. Then we'll come over to our app module and we'll import it. And then we will add it as a provider down here. Okay now that it's registered as a provider, Angular's injector is aware of this. So whenever we request it in another component or service, Angular will know where to go to get this service. Alright so now Angular knows how to inject this. Let's go ahead and inject it into our EventsListComponent. So we just do that in our constructor like this. Okay cool, so we'll just have to go ahead and import the EventService. Okay that's all there is to injecting a service once it's registered. Remember this private syntax right here is shorthand for saying essentially that we have a property on our class like this and like we are saying this.eventService equals eventService. This is short hand for that syntax essentially. So angular will look at the constructor for this component and see that we want an eventService and it will go out and construct that or grab it from the injector and inject it in right here. So now what we have to do is use it and we'll just do it in our constructor for now, which is a bad idea but we'll come back to that and fix it later. For now, let's just add it right here. Okay we need to declare this events variable. We will just declare it as an array of any data type. Okay cool so let's go take a look at this. First, let's make sure our server is running. Okay now let's come over to our app and refresh and this should still work just fine. So this is working great. Now back in our component, remember I said it's not a good idea to put this in our constructor. It's really not a good idea to put things in your constructor that are potentially long running. And eventually this will be an ajax call. And so this will take a little while to fetch those events. So we really shouldn't do it in our constructor, and yet we need to have this happen when our component first loads. So where we can do this, if not in the constructor? Well components have lifecycle hooks that you can hook into and one of those is the ngOnInit method. So that lifecycle event is called when the component is being loaded. So let's create an ngOnInit method. And then let's move this code into there. Okay we still need our constructor even though it's not doing anything in the body because that's where our service gets injected. But then we can access the service elsewhere in our class like we are here in ngOnInit. This will work just fine if we go over and take a look and refresh here. This is still working great. Only it actually fetched that data in the ngOnInit event. And then just as a side note, we can also take advantage of some Angular typescript declarations and let typescript know that this component implements ngOnInit like this. And then we just need to import OnInit. Okay cool now if we were to remove our ngOnInit here, we would get a warning indicating that it should be implemented because we are implementing it here on our class. So we can just add that implementation and then we're getting a little bit of typescript compilation safety. Okay cool. Now we have a functioning eventService and there is a practice exercise for this script. So go check that out.
-
Wrapping Third Party Services
It's pretty common in web applications that will want to use third party libraries or components. Let's take a look at how we might take something like that and turn it into an injectable service in Angular. To demonstrate that we'll use toastr. Toastr is a JavaScript library that allows you to create popup notification messages like this. Notice this message here. So let's see how we have to use this in our application. First we'll need to MPM install toastr. So we will come over to our Bash console and stop our server and then we'll just run this MPM install command. Okay so that MPM module that we just installed gives us a style sheet and a JavaScript file that we need to import. So let's go import that. So over here in our Angular CLI JSON file, right here at the top of the styles we'll import the toastr styles. And then down here in the scripts, we'll import the toastr JavaScript file. Okay so now toastr will be loaded with our app, but the question is how do we make this consumable as an Angular service and use it in our components? So let's make it so that when you click on this event thumbnail, we display a toast message that shows the name of the event that was clicked. So first let's wire out the click event. Okay so we have this event available to us here because we're looping over all the events in this NG4. So this will appropriately wire up this click event for each event thumbnail and pass in the appropriate event.name when you click on that thumbnail. Now let's add this method to our component. Okay so that click handler is now wired up to this handle thumbnail click method. So now we just need to display the toast message inside this method. So when we load a toastr in the Angular CLI config that made it globally available. So we could just call it right here like this. Now you can see here that our IDE is complaining because it knows the typescript is not going to compile this. That's because even though toastr is available globally typescript doesn't know about it and so it thinks that this is an undeclared variable. So we just need to declare that up here like this. This just lets typescript know that this variable is in scope already declared somewhere else. So you come over here and refresh this. When I click on one of these, you can see I get a toast with a name of that event. So that's working but there are a couple of problems here. First of all, we are using a global reference for this toastr object. And using global objects is never a good idea. And then finally another problem is that this is not testable. Since we are not injecting toaster into our component, we can't mark it and so that makes it difficult to test this component. So let's take a look at how we would create an angular service around this that we can inject. Alright, so we'll create a toastr service and it really doesn't belong in this events folder. So let's create a common folder for items that will be shared commonly throughout the app. And then we'll create our service here. Alright, so in here let's create a toastr class and let's make that injectable so that Angular knows about it. And as always don't forget these parentheses here. Okay now we basically want to wrap each of toastr's methods. It has four methods that we are interested in. The first method is called success and you call that like this. So you pass in a message and title. Let's create a method on our service to wrap that like this. Okay so now we have a success method on our new toastr service. We didn't have to name this success of course. We could call it anything. The important part is that we are calling success on the toastr object. We still have a problem here. A compiler still doesn't know what this toastr object is. We're just going to tell typescript not to worry about that. And you can do that by declaring a variable like this. So this will tell our typescript compiler that toastr is an object that we know about and in this case, it's something on the global scope. But now at least accessing our global scope is limited to this class and we won't be using it all over our application. Okay now let's go ahead and wrap the other toastr methods. Okay cool, so now we have an injectable toastr service. Alright so this service is ready to use. Let's go back over to our eventsListComponent. We're going to have to import it. And now we can just simply inject it down here and then down here, we will use our injected toastr service and now the step that's easy to forget. We need to add our toastr service to our app module. So we'll declare it as a provider here and then we'll have to import it. Okay that should do it. Let's see if that's working. Let's refresh and click on our events and there we go. We're getting our toastr messages. That's working great. So now we aren't using a global variable in our code anymore. Typescript is happy and our code is much more testable than it was.
-
Summary
In this module, we learned why services are necessary and what dependency injection is and why it's important. We then saw how to create our own reusable services and even how to wrap third party libraries in reusable Angular services. There's is one more use for services that we haven't shown here and that is to use services to hold state that is accessible throughout your application. We'll do this in the forms module when we create our authentication service which will hold information about the currently logged in user. In the next module we'll explore routing and navigation.
-
Routing and Navigating Pages
Introduction
In this module on routing and navigation, we'll explore how to define URLs for pages and navigate between them in our app. We'll talk briefly in this introduction about why routing is necessary. And then, in this module we'll see how to define routes for various pages in our site. And then we'll see how to add links that activate those routes and how to navigate to those pages from code. We'll also learn how to prevent routes from being activated or deactivated using route guards, basically preventing users from navigating to or away from pages. And we'll look at how to preload the data for a page using the resolve route handler. We'll also demonstrate how to style links based on whether their corresponding route is currently active. And finally, we'll see how to lazily load different sections of our site so they're not loaded into memory until we need them. So let's just talk quickly about why routing is necessary. In a traditional website before the advent of single-page apps, each page was completely separate and distinct from others, and every page was served independently from the server. When you went to index.html, the browser would ask the server for the index.html file, which the server would then return to the browser and then display. And if you went to foo.html, it would ask the server for that file, and the file would be returned and then displayed. And the entire page would be replaced even if 60% of the page was the same as the prior page. Modern web applications, however, load an app into memory by loading just a single page, typically, index.html, and then all other pages are actually loaded via JavaScript. But they're not really full pages. Your initial index.html is the only full-page load. And then only portions of that page are replaced as you navigate from page to page around the site. To the user, it seems like new pages are being loaded. They even see the URL changing in their browser, and the back and forward buttons even work despite the fact that only one index.html page has ever actually been loaded from the server. There's a lot that goes in to making this work, but frameworks like Angular have abstracted a lot of that away and made it very simple. So let's take a look at how we can make this happen in our app.
-
Adding Multiple Pages to Your App
Before we can demonstrate routing, we need to have multiple pages in our app to route to. Currently, we're displaying a thumbnail for each of our events here. But we would like to have a page where we can show more details about each individual event. So let's create an event details page, and then we'll see how to navigate to that page when we click on one of these events. Before we jump in, I just wanted to remind you that we've made changes to this course to keep it up to date. This course was originally created without the CLI since it was still in beta. Because of these updates, you may notice some inconsistencies in the file explorer over here. Those inconsistencies have been explained previously. Okay, so there's going to be a number of things in this event details section of our site. So let's create a folder for this component. And then in here we'll create our component. Okay, and let's create the basic shell for our component. Okay, now let's add the properties for our component here. And typically, we've added a selector here so that our component can be used from within an HTML page. But this component isn't going to be used as a child component from another page. It's going to be routed to directly so we don't need a selector. But we're going to put our HTML in a separate file, so we're going to want a template URL. And that'll go here. Okay, let's go ahead and create that template file. Okay, and then we're just going to want some HTML here to show some basic information about this component. We don't want to have to type this all in, so this is also available over in our GitHub repo. So let's jump over there. And it's inside this app folder in this events, event details folder. And it's this HTML right here. So I'm going to click on Raw here. Okay, and here's our HTML. Let's just go ahead and copy that, and we'll paste it in right here. So this is just some basic HTML with some bindings to an event property on our component. Alright, we're going to want a little bit of styling here, so let's add a class on this outer div here. And then we'll add that style to our component. This will just add some padding around the whole thing. And one more style to add. We're going to want to limit the size of this image here. Okay, so let's add that style over here. Okay, so as we sign our HTML, we're binding to an event object here. But our component doesn't have that property, so where's that going to come from? Well, this page is going to be navigated to directly, and the idea of the event that we want to view will be in the URL. So when we navigate to this page, it'll be a URL like this. So that one will represent the event ID for this page. Okay, so when this page is loaded, we're going to want to make a call to the event service to fetch the event for this page. And remember, we don't want to do that in the constructor because that is going to be a longer running Ajax call. So let's create an ngOnInit method here, and we'll do it in here. Okay, and we'll pull this from our events service. If we go over and look at our events service, it only has a method right now for retrieving all of the events. We just want to retrieve a single event. So let's add a new method here called getEvent. And that will take in an ID, that will be a number, and then we'll just pull that out of the events array. And again, we'll have this make an Ajax call later, but for now we'll just pull it off of the array like this. Okay, so now let's go call that from our new event details component. So first we're going to have to import that service. Okay, now we just need to inject it. Okay, now we can just make that call from ngOnInit. Okay. And remember, we're going to be passing the ID of the event in on the URL. And we're going to want to pass that ID in here. But for now, let's just hard-code this to event 1. Okay, and we have to declare that as a property. Alright, now this component is ready for use, but we need to register it in our app module. Okay, and then we'll register it down here. Okay, so now we have another page to route to, but the question is, how do we get to it? Let's add our first route.
-
Adding Your First Route
Alright, so now we have this event details component, but how do we get to it? First, let's remind ourselves how we're currently displaying our events list component. Over here in our index.html file, you can see that we're loading our events app component. And if we go look at that component, you can see that, and it's HTML, it has two child components, a nav bar component and an events list component. So this events app component is the top-level app component for an entire app, and it gets loaded during our app's bootstrap process. And so it loads and is displayed when we first navigate to our app. And then it loads these nav bar and events list components. And so that's how our events list component is currently being displayed. And right now there's really no way to change that. What we really want to do is to always show the nav bar component, but instead of this events list component always being shown, we want to show whatever route-level component matches our URL. This is where routing comes in. So instead of putting this events list component here, we'll replace it with Angular's router outlet component. And then somehow we need to tell Angular, when a user requests a particular URL, display its corresponding component here. We do that by defining routes. So let's go create a routes file at the root of our app where we can define those. Okay, so the routes that we define for an application are going to basically be an array of route objects, will look something like this. And right now we need two routes. First we need one for our events list page. Okay, so this basically says, if the URL matches /events, then show this events list component wherever our router outlet component is. And remember, we just added that router outlet component right here in our app component. And now we need another route for our event details page. Okay, this route looks a little different because it has this :id on the end of it. That's basically a parameter placeholder where we expect a value to be passed in on the route. We'll talk more about this in the next clip. But this route will match things like /events/1 or /events/foo. And when it sees a route like that, we will display the event details component. And then we want a default route so that if the user navigates to the route of our site, it will take them also to the events list page. So that will look like this. Okay, so this path is a little bit different. It's saying, when the path is empty or when we're at the route of our site, then redirect to the route/events. And redirect routes need this pathMatch property, and there are two options for that. Either prefix or full. Prefix means redirect if the URL starts with the specified path string. And full means, redirect if it fully matches the specified path string. In our case, we just want to use full. Okay, now we just need to import our components. Okay, there's one more thing that we can do here. This actually would work as is. But Angular provides a TypeScript definition for this router config that will give us some extra IntelliSense and compile-time safety if we add it. So let's import that. And then we'll just add that type definition to our array like this. Okay, cool, now you can see, as I add a new path, if I hit ctrl+space in VS Code, you can see it gives me some extra information about properties that I can add to this route. And we'll talk more about a lot of these properties in a little bit. But it's nice that we get this IntelliSense here so that, as we add a path, we know we're doing the right thing. And if we were to misspell something, we would get some compile-time safety and warnings in our editor. Okay, so now we've defined our routes. We just need to tell Angular to load this config over in our app module. So first let's import our routes. And then we just need to add it as an import using Angular's router module like this. Okay, we need to import RouterModule. Okay, so down here, we just used RouterModule.forRoot to import our route into our app. Alright, one more thing that we need to do when we add routing to our app is we need to tell Angular where on our web server our app is hosted. For example, our app is actually hosted right at the root of our web server. But what if our app was hosted here? Angular needs to know this so that it knows what its routes are relative to so that it can parse the URL. So we do that by providing a base tag in our index.html. And you can see right here in the head, the Angular CLI has already created a base tag for us. And since our app is just hosted at the root of our website, the href is just slash. If it was hosted somewhere other than the root, we would just indicate that here. Okay, so we now have three routes defined, and our old events list page is now being routed to directly instead of hard-coded into the HTML of our app component. So that means, over in our events list component, we really don't need this selector anymore since we'll be routing directly to it. Okay, cool, we've been jumping around a little bit. So let's just recap what we actually did here related to routing. First, we added a router outlet component to our events app component here. And then we defined our routes for each of our pages, including a default route, and then we loaded our routes into our event module using the router module. And finally, we added our base tag to our index.html page. Okay, cool, that's pretty simple, really. Let's go check this out. So you can see, our events page is loading here. And the interesting thing is, notice that our URL is now /events. If I delete this and navigate directly to the root of the site, notice that it's redirecting me to /events. That's because of that redirect route that we added. And if I add a /1 on the end of this, then I get my event details page. Awesome, this is working great. There's just one exception here. If I navigate to event number two, I still get event number one regardless of what I put in here. That's because when we created our event details component, we just hard-coded the event ID 1. We really want to get that event ID off of the URL here. So let's take a look in the next clip at how we'd do that. And there's a practice exercise for this clip. So go check that out.
-
Accessing Route Parameters
Okay, so when we navigate to our event details page, this event ID is always being ignored. So no matter what event ID we put in here, it always loads Angular Connect, which is the event with ID 1, and that's because in our event details component, when we call the getEvent here, we're always passing in event ID 1. So let's see how we can pull that event ID off of the URL and use it in our component. Remember that over here in our routes, we added this ID parameter. That colon in front of the ID is a cue to Angular that this is a parameter in the URL. Angular will take whatever value is passed in to that location in the URL and will create a parameter named id for that route, and that parameter will be set to the value that's passed in at that point in the URL, and then we can access that in our component. So if we navigate to events/4, then the ID parameter for this route will be set to 4. So if we come over to our component, we can import ActivatedRoute from Angular. And then let's just inject that into our constructor, and we'll just call it route. And then down here, we can just use that to get the event ID like this. Okay, so on this ActivatedRoute service, we're calling snapshot.params That will give us the parameters off of the current route that was used to access this component. And then this is wrapping a little bit funny, but we're passing this in as a parameter to getEvent. And getEvent takes a number. So let's just cast this to a number. Okay, so now we are passing in the event ID as a number into getEvent. And notice that this ID here matches this ID over here in the route. Okay, let's check that out. So let's refresh our page here. Notice that we went to events/4 and it loaded a different event. And if we go to event 3, we get NG CONF, and that NG CONF image is a little crazy. It looks like we might have a typo in our styles. Let's go take a look at that. Okay, yeah, here. We have a colon here that shouldn't be here. Let's refresh that. Okay, that looks better. So now, as we navigate around, that image is going to be a little better. And you can see that we're loading a different event that matches the event ID that we're passing in. So that's how you pull parameters off of the URL using the ActivatedRoute service. And there's a practice exercise for this clip, so go check that out.
-
Linking to Routes
Alright, so now we have our two pages working with routing, but we have no way to navigate between them other than just changing the URL. We would like to make it so that when you click on each of these event thumbnails, that it takes you to the event details page for that event. So let's go over to our event thumbnail component. Up here in the HTML, we're going to add a router link to the main container div like this. So that's going to turn this div into a link. And when you click on it, it's going to navigate to /events and add the event ID to the route. And that's all we need to do to add a link to the new route. So let's go check that out. And if we refresh over here, now if I click on Angular Connect, notice that it loaded the Angular Connect event. So it navigated to events/1. And if we go back and click on NG Netherlands, then it loads that event, so that's working great. So that's really easy to add router links. But now, how do we get back to the events list page? We'd like to make it so this All Events link takes us back there. So let's go over to our nav component, which is in here. Okay, so here's the HTML, and here's our All Events link. You can see it's already got an anchor tag around it. And we can add the router link to an anchor tag just like we did with the div in our thumbnail component. So it'll look like this. Okay, and notice that the expression for our router link takes in an array. That array is basically a list of path segments followed by the parameters, and there's no parameters for this route, so it's just /events. The link for the event thumbnail that linked to the event details, we passed in /events and we passed in the event ID. Okay, so this should be working, let's go take a look. Better refresh over here. I can now click on All Events, and I can go back and forth between the events list and the event details. Cool. And there's a practice exercise for this clip, so go check that out.
-
Navigating from Code
Okay, we've just seen how to link to our routes from our HTML. Now let's take a look at how we would navigate to a page from within our code. For that let's create a real simple component. This will become the page that we use to create new events. But for now, we'll just make it a real simple page. So we'll add that in our events folder. And it'll be a real simple component for now. And for now we'll just use an inline template. Okay, so this page isn't going to do much right now, but we'll expand this page out in the module on forms. But we'll use it right now to demonstrate how to navigate back to the All Events page from code when the user clicks this cancel button. So first let's get this page wired up so that we can see it. We need to add our new component to our module. So we'll import it here. And then we'll just add it to our declarations down here. And then let's just add a route for this page. Okay, and import that component. Okay, and the placement of this route is kind of important. And actually, we have it in the wrong place right here. Problem is that this path actually matches the path above it. So Angular doesn't have a way to differentiate between whether we're trying to pass in the ID new to the events/:id path or whether we're trying to hit the events/new path. And so, actually, let's move this up here. That way it'll get processed first. So with Angular sees events/new in the URL, it will hit that first and will send us to this route. Otherwise, it'll keep looking for a matching path. Okay, so this is all wired up. So if we come over here, we can go to /new and that will take us to this page. So let's just add a link to this Create Event element in our nav. So back over in our nav component. Right here where this Create Event is, let's add a router link. Alright, now I should be able to click on this. Okay, there we go. Alright, now this cancel button doesn't do anything right now. What we want to do is when we click Cancel, we want it to just take us back to the events list page. So let's go wire that up. So over here in our Create Event component, let's just add a click handler on our cancel button. Okay, that's wrapping kind of funny, but you can see, when we call click here, we're going to call cancel on our component. So let's go add that method. Okay, now, to navigate from code, all we have to do is inject Angular's router service. So let's import it, and inject it. Okay, now all we need to do is call navigate on the router and pass in the route that we want to navigate to. And that should do it, let's take a look. I refresh here. Now, if I hit Cancel, there, it takes us back to our All Events page. That was easy. All we needed to do was inject the router and call navigate. And there's a practice exercise for this clip, so go check that out.
-
Guarding Against Route Activation
Sometimes we want to prevent a user from going to a particular page or discourage them from leaving a page. That's what route guards are designed to do. If we take a look at the IntelliSense for one of these route objects, I can do that in VS Code by hitting ctrl+space, then you can see all the different properties available on a route thanks to TypeScript. Two of these properties are canActivate and canDeactivate; canActivate allows us to determine whether or not a user can navigate to a route. Let's take a look at how canActivate works. And there are a couple different ways that we could do this. We can either use a function or we can define and use a service. We're going to use a service here because it gives us flexibility and the ability to inject other services, and we're going to need that. So, right now, if we go take a look at the site, I can navigate to an event details page with a URL like this, and that works fine. But if I navigate to the event details page using an invalid event ID like this, the page actually loads, but it sure doesn't look very good because there's no data here. We'd like to put a route guard in here to redirect them to a 404 page if the event ID is not valid. So let's create that 404 page first. This will be quick. First, let's add an errors folder. And then we'll add a 404 component. Okay, and let's grab that component out of our GitHub helper repo. So over here in the app folder, there's an errors folder, and it says 404 component. So let's grab the component out of there, and we will paste it into here. Okay, now we just need to add that to our module. And add it as a declaration. And then we just need to add a route for it. Okay, let's just import that. Okay, now we should be able to hit that page. Come over here and navigate to 404. Okay, good. So now we have a 404 page that we can redirect to. Okay, so we're going to add a route guard that sends us to that 404 page. So let's go create a service called EventRouteActivator. We'll add that in the event details folder. Okay, and the shell of our route guard service will look like this. Okay, so we just have our basic injectible service here, and then we're going to make this implement this CanActivate TypeScript interface. Okay, so that requires us to implement the CanActivate method. Alright, so we want to just check to see if the ID passed in is a valid event. So we need to inject our event service. And we'll inject that here. And then in our canActivate method, we'll look up the event. So we'll need to grab the event ID off of the route. This will be easy since the current route is passed in to the canActivate method as the first parameter. So we can grab it like this. Okay, let's import that. Okay, now we can get our event off of the events service using the ID from the route like this. Okay, now if this doesn't return a valid event, we want to redirect to our new 404 page. So let's just set a boolean variable based on whether this call returns a valid event. And then we'll just cast the result of this call to a boolean. Okay, and then we can just, right here, check to see if the event exists, and if it does not, then we will navigate to our 404 page. Okay, and we'll need to inject that router. Okay. So here we're loading our event. If it does not return a valid event, then we redirect to our 404 page, and then our canActivate method needs to return a boolean. So we'll return here whether the event exists. Okay, so it will return true, meaning the route can be activated if the event exists, otherwise, it will return false. Okay, that's all there is to creating a route guard. Now we just need to go add this as a provider into our module. So we'll import it up here. And then we'll just add it as a provider. Okay, so now we have this route guard. We just need to go attach it to the route that we want to add a guard to. So over in our routes, we have our event details route right here. And then we're just going to add canActivate and pass in our event route activator. Okay, cool, so that's all wired up. Let's go check it out. So now, if I go to events/1, that's not working, actually. I think there must be something wrong with our call to getEvent. Oh yes, we need to cast the event ID to a number here. I think that should fix it. So let's try this again. Okay, cool, that works. But if we go to event 42, then it does not work, it sends us to the 404 page. So that's exactly what we're wanting. Okay, so that was pretty easy. Now you know how to prevent routes using a route guard. And there's a practice exercise for this clip, so go check that out.
-
Guarding Against Route De-activation
Just like we used canActivate to prevent the user from navigating to a page, we can use canDeactivate to prevent a user from leaving a page. This is often helpful if, for example, you want to warn a user if they try to navigate away from a page before saving their data. So let's add a route guard to the Create Event page that warns the user if they try to cancel before saving their event. So we'll start by adding a canDeactivate property to our create event route. And remember, we said that there are two ways to add route guards. You can either use a function or a service. In our canActivate example, we used a service, but we don't need something that involved for our canDeactivate. So let's just use a function. So to use a function, you just add the function name here. So we'll create a new function called canDeactivateCreateEvent. So the question is, where do you define this function? Well, we just need to register this as a provider in our module. Let's go over to our module. And then down here in our providers, we've been defining providers using this shorthand approach. If we take a look at this event service provider, another way we could provide this is using the longhand approach like this. So this is the longhand form. It says when this is requested, use this to fulfill it. And it just makes a lot more sense to use the shorthand form in the cases where we're using services. But in this case, we're going to be requesting a string, canDeactivateCreateEvent. And for that we want to provide a function. So we'll put that here. And then for useValue, we'll create a function called checkDirtyState. Okay, and let's just put these on their own line. Okay, so now we need to define that function. We could define that over in another file, for ease of use, let's just define it right here. Alright, just to demonstrate this, let's just have this return false right now. Okay, let's go check this out. If I refresh our Create Event page here and try to hit Cancel, you can see it no longer works. The cancel button, remember, is wired up to send us back to the All Events page. And it's being prevented by our route guard. And not only is it being prevented here, but also, if I click on anything that would take me away from this page, it prevents me from doing that. So that's pretty awesome, how easy that is to just disable all navigation in your application using a route guard. Okay, so let's make this a little more interesting. What we really want to do is prevent them from leaving this page only if they haven't saved their event. So the question is, how will this checkDirtyState function know the state of that component? We have to have a way to know if they've saved the event or not. Well, that's actually really easy. The very first parameter that is passed in to your canDeactivate function is the component itself. So let's go take a look at our create event component. If we were to define a property on this component that represented the component state, we could access that property in our canDeactivate function. For now, let's just add an isDirty property on the component like this. Okay, so we've created this isDirty property, and it's defaulted to true. And this is essentially a public property. So over in our checkDirtyState function, if we grab that component that's being passed in, then we can check that isDirty property like this. Alright, and if the component is dirty, let's call the HTML confirm dialog and return the result like this. And then if the component is not dirty, we can just return true. Okay, cool, let's go check that out. So let's refresh this page. Now, if I hit cancel, okay, cool, now I'm getting this confirmation dialog that says you have not saved this event, do you really want to cancel? And if I say cancel, it will not, and if I say OK, it will. And then, just to show that this is really based on the state of that component, let's go make that component return true, or return false. So now it's not considered dirty. So now if I hit Cancel, it will just work. Of course, we'll want this to do something more than just return true or false. We'll come back to this when we wire up our create event form and make this return something reasonable. Alright, cool, that's all there is to adding a canDeactivate route guard. And there's a practice exercise for this clip. So go check that out.
-
Pre-loading Data for Components
So we've taken a look at the canActivate and canDeactivate route handlers, but we haven't looked yet at the resolve route handler. Resolve allows you to prefetch the necessary data for a component or to do other checks prior to loading a component. We haven't really felt the need for this in our app yet because the data is all being provided synchronously from local variables. But this isn't very representative of the real world. Let's change our events service so that getEvents acts asynchronously like it would if we were making an Ajax call. In order to make this asynchronous, we're going to use an RxJS observable. Don't worry too much about the syntax we're going to use here. Joe will talk a lot more about observables in his module on HTTP and observables. But for now, we're just going to go ahead and import Subject from RxJS. And then inside our getEvents method, we'll just create a new Rx observables subject like this. And then in order to make our getEvents method act like it's asynchronous, we're going to use JavaScript's setTimeout method like this. And then we'll just return our observable. Okay, just to explain this a little bit. Observables are like streams of data. They're kind of like arrays, where the data arrives over time. Angular is designed to work very well with both observables and promises. So we're going to take advantage of that. So basically, subject is a type of observable, and right here, we're adding data to this observable stream, and we're doing it inside of setTimeout to simulate asynchrony. So after 100 milliseconds, we'll add data to the stream, and then you can see, right here, that we're returning the observable. We were previously returning the data directly. So we're going to have to go adjust the consumer of this data. That's over here in this events list component. So right here, getEvents is going to return an observable, and you get the data out of an observable by subscribing to it like this. And that's going to eventually return instead of immediately. So instead of setting this.events to the return value of getEvents, we're going to set it only when data is received, which happens inside our subscription. So we can set that like this. And to get TypeScript to stop complaining, let's just change this to any for now. Now, let's go refresh our page. Okay, you can see that's still working, but now we're getting our data somewhat asynchronously, and it's happening through an observable. Again, we'll touch on observables more later. Okay, so let's go add in a little bit more of a delay in our asynchronous method. So let's make this wait for two full seconds. Now, if we refresh, you can see the page partially loads, waits for two seconds, and then finishes loading. This doesn't look so bad on this page, but on some pages, this looks really bad if the page partially loads and then the data pops in afterwards. So let's add a resolve route handler to this route so that we can wait for the data to load before displaying the component at all. To do that, let's go add a events list resolver service. And let's add our basic shell. Okay, so this is an injectible service that implements resolve. In the resolve method, we will typically make an asynchronous method call like an Ajax call. And then when it returns, we'll return that data. So we're going to do that like this. We'll have to inject our event service. Let's fix our typo over here. So this is a very basic implementation. We're calling getEvents, which returns an observable, and then we're calling map on that observable, which gives us access to the events that are passed in on that stream. And if you're not familiar with this syntax right here, we're just returning those events. This is shorthand for this syntax. So we're receiving events in to this function, and then we're just returning them right back out. We can just use this shorthand approach for that. Okay, and one more thing to clarify about what we've done here. Typically, when you listen to an observable, you would call subscribe here instead of map. But because this is in a resolver, we need to actually return the observable to Angular so that Angular can watch the observable and see when it finishes. If we were to call subscribe here, the value that would be returned would not be the observable; subscribe returns a subscription, not an observable. And so we use map, which kind of does the same thing as subscription in this case, and it returns the observable. So then, because we're returning the events inside our map expression, these events will then get passed along to the component to find in our route. Okay, so this resolver is finished now. So let's go consume this resolver, and hopefully it will all start to come together and make sense. So let's add this as a provider in our module so we don't forget. Alright, and we'll add it down here as a provider. Okay, and then we need to add this as a resolver to the route on the events list route. So let's come over here. Okay, so here is our events list route. This is where we want to add our resolver. So it will look like this. Alright, let's import that and then we'll talk about this. Okay, so if we take a look at what we're doing down here, where we're adding this resolve handler, notice that we're passing in an object, and that object has a property, events, and that property value is set to the event list resolver. Okay, so basically, what this is saying is, before resolving this route, call this event list resolver, and when that resolver finishes and returns us some data, add this data to the route as a property named events. So it's going to take the events that are returned from the resolver and put them in a property named events on the route. Okay, so now let's go over to our component and consume that. Okay, so in our ngOnInit, we were calling getEvents and subscribing to that. We don't need to do that any more because we're doing that in our resolver. Instead, that data is now put on the route for us, and we can just get that data right off the route. So let's go inject the route. Inject that here. Okay, now, our OnInit is just going to look like this. Okay, so just to tie this all together. Notice that this events right here matches this events over here on the route. So that's how that's all wired up. The resolver gets the events from the events service, the route takes that and puts it on the route, and then we can access it in our component. Cool, let's check that out. So now I'm going to refresh this. Okay, notice, it's still taking two seconds. But this Upcoming Angular Events that is part of the events list page does not show up until all the data has been loaded. Okay, that's pretty cool. And the great thing is, we only have to load our data once. Notice that our resolver loaded our data for us and put it on the route. So we didn't have to wait for the data to be loaded in the resolver and then load it again in that component. Okay, and just to clean up here. Let's go over to our events service and change this timeout back to just 100 milliseconds. And there's a practice exercise for this clip, so go check that out.
-
Styling Active Links
Typically, when you have a navigation header in your site like this one, you want to highlight the currently active link so that users can see from the nav bar which section of the site they're on. So let's go make it so these All Events and Create Event links are highlighted when we're on those pages. All we have to do is add a routerLinkActive directive to each of these links like this. So this is basically saying, when this link is active, apply this active CSS class, and we'll add that class here in just a second. Let's go ahead and add this routerLinkActive directive to this link also. Okay, cool, now let's come over to our component and add that CSS class. Okay, cool, so we're just going to add this orange color whenever the active class is applied. And we had to add the extra li prefix to our styling here so that it is specific enough that our styles don't get overwritten by bootstrap. Okay, so let's go check this out. If I refresh the site now, cool, you can see that All Events turned orange because I'm on the events list page. And if I click on Create Event, you can see it gets turned orange also, but the All Events link should not be orange here. This is happening because the routerLinkActive binding will do a startsWith match. So if we take a look at our router links over here in our HTML, you can see the events route will get the active link whenever the route starts with /events. So that is also matching when the route is /events/new. But we can change this to be less greedy by setting the router link options to require an exact match, like this. Okay, so this will make it so that the active class only gets applied if the route exactly matches /events. So let's go take a look at that. Let's refresh here. Okay, cool, now you can see we're on the Create Event page, and only it is highlighted. If I cancel and go back to All Events, you can see it turns orange, and only it is orange. So this is working better now. Cool, that was super easy, and it's cool to see something like this work with so little effort.
-
Lazily Loading Feature Modules
So far, our application has only one Angular module. It's our main app module. Let's take a look at adding multiple modules and some of the performance benefits that we get from that. Typically, larger sites can be broken down into smaller sections. Imagine we had a section of our site for creating and managing user profiles. Let's go ahead and create the shell of an edit profile page that will be part of a new module. So we'll create a new user folder. And inside that folder we'll create a profile component. Okay, let's just grab the basic starting point for this component from our GitHub helper repo. So over here we have a app folder, and in our app folder we have a user folder, and then we have this profile component. So let's just grab the raw contents of that, copy it, and paste it into here. Okay, so now we have this user profile component that we'll update later for adding a profile. Right now it just has two buttons and an empty spot where we'll put a form later. So the pattern that we've seen so far is that, at this point, we would go and add this to the module and to the routes. And we still want to do that, but the user portion of our site is very different from the rest of our site. So we'd like to make this a totally separate module and feature section of our site. So let's go create a new user module. In the user folder we'll create a module. Alright, and a basic lazy loadable module will look like this. Okay, so we'll declare our module like we normally do. Okay, and it will have imports, declarations, and providers. Alright, this looks similar to our app module, except for our imports are going to be a little bit different. Here we're going to use CommonModule, and in our app component, we actually import BrowserModule here. So that's one key difference between the app module and a feature module, or a lazy loadable module. And then the other difference is, for our router module, we'll call forChild. In our app module, we used forRoot here. And then we'll just pass in the routes for this module. Okay, we haven't created those. Let's go ahead and create import, and then we'll go create them. Okay, so we've already created a profile component that we want to import into our new user module. Okay, so let's declare that down here. Alright, now let's go ahead and go create our routes. So we'll create a new routes file, and it will look like this. Okay, so there's nothing different with this route. However, I do want to make note of one thing. It looks from this like route for this would be /profile. But when we're done with everything, the route is actually going to be /user/profile. And you'll see why in a second here. But just keep that in mind. So this new feature module is ready to go. Now we need to go tell our main app module when this module should be lazily loaded. We actually define that in the main module's routing config. So let's open that up. And then right here, we're going to create a new route, and its path is going to be user. So this is where that prefix to our user feature routes is going to come from. So anything inside the user feature module is going to have a route that's prefixed with user. And then to load that module and its routes, we use loadChildren. And then you supply a string here that's parsed by Angular that has two parts. The first part is the path to the file where your new module is. So that's here. And then the second part follows a hash sign. And the second part here is the name of the module. So if we go over to our user module, you can see what's being exported here is the class userModule, and actually, that should be an uppercase. And then that's what we'll put as the second part of this string. Okay, so this is basically saying, when a route starts with /user, load the user module from this path. Alright, now let's go add a link to our new profile page. So over in our nav bar HTML, right here, we're displaying Welcome John up in the upper right hand corner. And let's add a router link to this anchor tag. And that will link to our new user profile page. Okay, so this is all wired up. Let's go take a look. So I'll refresh here. Okay, so now notice, if I click on Welcome John here, that it loads our new user profile. And notice the URL up here is /user/profile. And one thing that's really cool here. Let's open up our debug tools to the network tab. And I'm going to refresh my app here. And first let's put a filter in here, /user. So let's see what requests are made for /user when I first load this app. Alright, notice nothing is loaded here. But when I click on the user profile and go to the user profile page, notice that only then did it load the user module and the routes and the component. In a bigger application, this could be really helpful if a module is composed of a lot of files. It would avoid loading those until the user actually goes to that section of the site. Okay, cool. So that's how you add new feature modules to your application and lazily load them.
-
Organizing Your Exports with Barrels
We just demonstrated how to organize our code into modules. Let's take a look at another way to clean up our code a bit. If we take a look at our app module, it has quite a long list of imports. And most of them are coming from the events folder. We can simplify this a little bit by exposing all of the imports inside the events directory from a single index file that we can then just import with a single import line. This is referred to as creating barrels. So let's start by creating a barrel in our events directory. So we'll create an index file right here, and then we'll simply import each of the components in this directory and re-export them, like this. Alright, now we can do this for all of our subdirectories. And we'll even do it for our shared folder for consistency even though it only has a single file inside it. So let's go ahead and create that now. And it'll just export the event service. Alright, now we can actually add this barrel to our outer barrel like this. Okay, now we'll do the same thing for the event details folder. And we'll export the two things that are in this folder. Alright, and we need to add that to our outer barrel too. Okay, now that we have these barrels created and they're all rolled up into this one barrel, we can now go simplify our imports in our app module file by creating a single import. So we do that like this. Okay, so there's our one barrel that has everything exported from it. Now we can move up everything from the events directory like this. Alright, now we can delete all these extra import lines. Okay, that looks much better. Now we can go simplify our routes file too. So it has a lot of imports in here from the events folder. So we'll make a single import for those and move each of these. Okay, then let's clean these up. Okay, that feels a lot better too. So this will make things a lot more simple each time we need to add something to these imports. So let's just go take a look at our site, make sure that everything's still working. So we'll refresh. There we go, everything's still loading just fine.
-
Summary
In this module, we learned why routing is necessary, how to define routes for pages, and how to link to those routes from HTML and navigate to those routes from code. We also learned how to create route guards to prevent routes from being activated and deactivated, and how to preload data for a component using resolve. We also learned how to style our links based on whether the routes for those links are currently active. And lastly, we learned how to create feature modules and lazily load them. In the next module, we'll take a look at how to collect data using forms and validation.
-
Collecting Data with Angular Forms and Validation
Introduction
In this module on collecting data with forms and validation, we'll cover everything you need to know about collecting and editing data in your applications. First we'll take a look at how to create data models that we can use to help ensure type safety for the data we're collecting. Then we'll take a look at creating and validating template based forms and then how to create and validate model driven form and the pros and cons of each approach. Next we'll take a look at using two-way data bindings to display and edit existing data and, finally, we'll take a look at how to create custom validators that use custom business logic to validate fields. Okay, let's get started.
-
Using Models for Type Safety
Before we jump in I just wanted to remind you that we've made changes to this course to keep it up to date, this course was originally created without the CLI since it was still in beta. Because of these updates, you may notice some inconsistencies in the File Explorer over here. Those inconsistencies have been explained previously. So far everywhere where we've needed an event object like here in the event thumbnail component, we've just been working with plain old JavaScript objects and we've been using the any type, or not specifying a type at all. That is completely acceptable if you don't care to use much typescript and we haven't really had a need for it since our data has all been hardcoded but now that we're going to be creating new events, it might be nice to add some typing information to our application that will help us see data type errors in our code editor and will give us some compile time type safety. Let's actually create an event model that we can use here to tell typescript exactly what data shape to expect. We'll use this in a number of places so let's create it in our shared folder. So right here we'll create an event model. Okay, and we can either use an interface here or a class. It really doesn't matter which one you choose so we'll just use an interface to define each of the fields on an event like this. Okay, so here are the basic fields and all of these are required. We now have a location, an online Url field that can be null so we'll define them like this. So that question marks indicates that this field can be null and this one's actually an object and if location is defined, we want all of its fields to be provided so we will not make them nullable. Okay and then we have our online Url field that's also nullable, okay, and each event also has an array of sessions. We haven't talked much about sessions yet but we will be using these later in the app. Okay so we'll have a sessions property and it will be an array of ISession, let's go ahead and define ISession here. Okay, cool, now we have event and session models to work with. Let's go ahead and add these to the shared barrel. Alright, now let's go put this model to use. First let's come over to our event service and this is where we are currently hardcoding our event data so let's define the data type for this list of events, so here we'll specify that this is of type IEvent gray. Alright we need to go import that. Okay now let's come back down here. Okay, and right away we're seeing some benefits from having this be typed. Notice that our editor is giving us an error and if we hover over it, it says down here, type string is not assignable to type date and we can also see this error over in our console. So this is where our real time typescript compilation is happening and you can see that it's getting the same error down here and all of this is because down here when we're defining our event data, we're actually assigning strings to our date field. So let's fix that, we'll change these to be dates like this and then we'll do that for all of these sessions so I'll just search for this date field and replace each of these. Okay, cool, so now our data matches our model and you can see we're not getting an error anymore and now we can go specify the return type of our get event method to say that this is going to return an Ievent and then we want to specify the return type for our get event's method also. Now you might think that the return type for the get event's method is an array of IEvents, but we're actually using observables here and we're returning a subject, not an IEvent array. So we could specify that the type is subject and then subject is generic so it wants to know what data types it's going to contain inside of it, which is an IEvent array and then we'd specify here that this is a subject of type IEvent array also. Okay so this works but actually subject is a little too specific, we're going to be changing this to an observable later instead of a subject. And subject is a type of observable and, in fact, it implements observable. So we can go ahead and change this to type observable and we'll need to import that from our XJS. Okay, cool, this works so we're returning a subject, which is a type of observable so our return type now for our get events method is observable of type IEvent array. Okay, cool, so this service now specifies the data type that it returns. Let's go take advantage of this model in a few more places. So over here in our event thumbnail component let's import our new event model and now let's define our input variable to be of type IEvent, alright, and we can add the typing to our event's list component too. So down here we can say that our events property is of type IEvent array and we can specify the type in our event details component. So this is an IEvent. Alright and we could keep going and update everywhere where we use events like in our route guards and our resolvers and other places but let's call this good for now. This is a good example of the fact that you can use as much or as little typescript as you want in your app but now you know how to create and use data models in your application if you choose to. Again, it's not required if you prefer not to but it's available if you like the type safety. And if we go over to our site and refresh, we can see our site is still working now that we've added these data models.
-
Creating Your First Template-based Form
Okay, let's create our first form. This will be a simple template based login form. Angular allows you to choose from two options when you create a form, template based or model based, which is also called reactive forms. Template based forms allow you to build your form completely in your HTML template, it's simple, and easy, and it works great for simple use cases but it has some limitations. For complex forms you end up with a lot of logic in your HTML if you're using template based forms and things like cross field validation are more difficult. Another limitation is that you can't unit test all your form and validation logic if you use template based forms but for simple forms, template based forms are great. Model based, or reactive forms, allow you to build your form and put a lot of your logic in your component instead of in the template and this has some other advantages that we'll talk about later. So let's create our first template based form. For this we'll create a new login component. We'll put that in our user module. And here's the basic shell for our component. Okay, and this is going to have enough HTML that we'll want it in a separate template so let's add a template Url, alright, and let's create that template file and let's grab the HTML from that template from our GitHub helper repo. Let's go over here in our app folder, in the user folder, there's this login component HTML. So let's grab the raw version of that and paste that in here, alright, there's nothing Angular specific here, this is all just plain HTML and you can see here that we have two inputs, a username and a password, and then we just have these login and cancel buttons. Okay, let's go declare this component in our module and add a route for it. So in our user module, we'll import it, and add it as a declaration. Alright, now let's add the route. Alright, let's go take a look at this. So if I change my Url here to user slash login, there we go, we have a login component now. So let's go wire up this form. Okay, so we're going to be using a few forms related directive and all of the forms directives are in their own modules so we need to go import the forms module and since this form is in our user module, we'll need to import it here. So up here with our Angular imports we'll add import forms module and that comes from Angular forms. Okay and now we just need to add this as an import. Okay so that gets us access to a number of different template based forms features. So let's go wire up our form so that we can log in. So when the user enters data into these fields, we need to get that data into our component so we can use it. Based on what we learned earlier about data binding, we could just bind to the input elements, input event like this. Okay so this expression would make it so that when input is entered into this input box, an event is fired and that event updates the username property of our component to be the value of this input box but that's difficult to remember the syntax for and cumbersome especially on a form with lots of elements where you would have to add this over and over. For that reason and others, Angular provides the ngModel directive that will allow us to bind input fields like this, ngModel gives us some extra forms related functionality that we'll explore shortly. Oftentimes when you see ngModel, you'll actually see it declared like this. This is commonly referred to as the banana in a box syntax and it represents a two-way binding. If you remember the syntax module, parentheses are used to bind in the HTML to component direction and it's typically used for responding to events, whereas the square brackets are used to bind in the component to HTMl direction and is typically used for displaying data from the component on the page. The reason while why you'll often see the banana in a box style binding, is that often forms are used for editing existing data and so you'll want to display the existing data and as you type, you'll want to update the data in the component. Thus, the need for both types of bindings but for a login form there's really nothing initially to display so we just need a one-way binding. It's always best to use just a one-way binding when that's all you need. So we'll update it back to how it was. Okay an ngModel requires us to define a name attribute so let's add that. You'll notice that we're setting ngModel and name to the same value, username, this is typical but it's not always the case. We'll explore this more later, for now we'll just set them both to username. Alright let's add this to the password field too. Okay, now the question is, where exactly are these ngModel bindings going to put the data? We don't have properties on our component to match them. So what are they binding to? Well right now they're just binding to the form and we can get access to the form by adding a local variable on the form element like this and we'll use that login form variable in our submit handler, we could just bind to the form submit event like this and technically this would work. When we submit the form, the login method on our component would be called and it would pass in the login form but Angular provides a directive called ngSubmit and we'll use that because that handles a few extra things like preventing the form from submitting to the server, for example, so it's best to use this binding instead and then over here we're using the login form local variable that we defined but there's a lot of info on a form object but all we're really interested in right now is the value so we'll just pass that in. Okay, now let's go add this login method on our component and for now, let's just log this out and see what this looks like. Okay, let's check this out. So let's come over here and refresh our login form and type in a username and a password. Alright, now let's open up our console log and click login. Okay, so if you remember, we passed in in our HTML login form dot value to our login method and then here we just console logged it out and so the login forms value is this object that has a username property and a password. This is actually a really convenient shape. It is simply an object with each of our form fields as properties so now we can just use these values to log our user in and now it seems like everything's working just fine here but we do have a problem. If we jump over to our terminal and stop our server, let's just go ahead and serve this in production mode like this. Notice we have a couple of errors here. So prod mode is catching more errors than dev mode. This will actually be fixed in a future version of the CLI and you'll start seeing these errors in dev mode too but either way we need to fix this. So this is happening because over here in our template, we're binding to these username and password properties but those properties don't exist on the component so we just need to go declare those. So over here in our component, in typescript we can just declare those like this. So we just declare those up here at the top of our component and now if we save that and go back over and build this again, we should see that those errors go away. There we go, now we're not getting errors anymore so just remember if you bind to something with ngModel, make sure you declare it on your component. So in the next clip we'll take a look at how to consume these values that are provided by our template based form. And there's a practice exercise for this clip, so go check that out.
-
Using the Data from Your Template-based Form
Okay, so our login form is now providing the data that we need, so let's use that data to log the user in, but first we'll need a service that we can call that will do that for us. So let's create an auth service in our user module. So right here we'll add a new file, auth service, and just like every other service, this will just import an injectable class. Okay, and this service will have a login user method that will look like this, alright, and this service will keep track of the current user. So let's create a user model that we can use here also and that will just look like this. Alright, now let's just import that model into our service. Okay, now we can create a current user property on a class that will be of type IUser. Okay, now when we call login user, we'll make a call to the server and log the user in and then we'll set this current user property but we don't want to deal with all of that right now so we're just going to fake it out and set the current user to be John Papa. Okay, and it looks like we might have a problem with our interface, let's come over here, yep. This should be capitalized. It's really neat how using typescript allows you to see errors like that. Okay, so we're not really authenticating the user here, we'll wire that up in a later module. What we'll eventually do is pass the username and password to the server, let the server authenticate the user, and return the user's information that we will then set as the current user but for now this fake implementation will work and it gives us a service that we can call and we can worry about the implementation details later. For now when you call login user, it will just log you in and always set the current user to John Papa. Alright, while we're in here our app is going to want to know if we are logged in so let's add an is authenticated method that just returns true if the current user is set. Okay, that will do for our service. Let's not forget to register it, however, as a provider, this is going to be a little bit interesting. Remember over in our user module when we needed the forms module, we imported it here as an import in our user module and now we have a service that we need to register as a provider but we don't want to register it as a provider here in our user module, we want to do that in our app modules. That's because we're also going to be using it in components that are in our app module so we'll use it in both modules and that might make you wonder, why don't we need to register it in both modules? Well, that's because providers are shared across Angular modules. So if we add it in our app module, it will be available for us to use here without having to register it as a provider in this module. It's worth nothing that while this is true for providers, it is not true for imports and declarations. So you need to import those in whichever modules need them. Anyhow, let's go ahead and add the auth service as a provider in our app module. Okay, so we'll import it and then let's add is as a provider. Okay, now back over to our login component. When the login method is called here, we'll call our new auth service, let's import it and inject it and then we'll call it like this when login is called. Okay, so if you remember over in our HTML, when we submit the form we pass in the form's value, which has these two properties on it and then they're available here, we can pass them into the service. Okay, now let's just update our app so that we can tell when we're logged in and then this will all come together. So over in the HTML for our nav component, instead of just displaying welcome John here, let's display a login link if the user is not authenticated and a welcome message if they are. So first over in our nav component, let's import our auth service and then inject it down here and then back over here in our nav bar HTML, let's add a new login link right here and that will link to our login route. So let's add a router link. Okay, so that links to our new login form and then we only want to show this if the user is not authenticated. So let's add an ngIf to this and then we can access that auth service that we just injected right here like this. Okay, cool, so that will only show now if the user is not authenticated. Now let's update this profile link to only show when the user is logged in and then right here we'll just update it to show the current user's name. Okay, now if we go over to our app, and let's refresh, and then you can see up here we have a login link so if I click on that, and then log in, okay, now if I log in, cool, now you can see we're logged in. Alright, cool, so this is our first form and you can see how the data from our input boxes was wired up with ngModel and then how we passed that in with ngSubmit to our login method and then in our login method we called our auth service and passed in the username and password to log the user in. Next we'll take a look at some validation but let's just finish up a couple of loose ends here. First of all, when we log in, it is logging us in but it's leaving us on the login page, so let's get it so that it redirects us back to the all events page and then also if you hit cancel, it'll send us to the all events page. So let's import our router and inject it and then in our login method after we authenticate the user, we will route them back to the events page. Okay, and then let's wire up the cancel button so let's provide a method here and this will also navigate to the events page. Okay, let's go wire that up on our button so when we click on this button, we'll call our cancel method. Okay, so that should be working now, if I refresh here, now if I log in, then it logs me in, you can see I'm logged in now and it takes me to the events page and if I click cancel, it also takes me back. Okay, cool, now let's go add some validation.
-
Validating Template-based Forms
As it is now, we have a bit of a problem with our login form. I can actually log in without entering anything. This is partly due to the fact that have faked our auth service so we're not actually checking the user's credentials on the server yet but we should be able to catch this before the call to the auth service. The user shouldn't be able to submit this form without populating these fields. So let's add some validation that will prevent that. To start with, let's add required attributes to both of our input fields. Alright, and let's go see what that did. So we are getting some validation here but this actually isn't Angular doing this, this is basic HTML five validation. So that we're not confusing HTML five validation with Angular validation, let's just disable the HTML five validation with a no validate attribute on the form. Okay, let's go take a look now. If I refresh this and click login without populating the fields, you can see that the required field validation hasn't stopped us from submitting the form but Angular is providing us with some validation information and that information is available on this login form so I'm just going to paste in a bunch of temporary code here that'll allow us to learn a lot about the state of this login form. Don't bother to type all of this in, I'm just going to delete it here in a second, anyhow but this will show us a lot of information about this form. Okay, so you can see here that I'm going to be displaying a bunch of information about the login form and the controls on the login form so if we go back over and refresh our app and go to the login form, you can see I'm displaying a bunch of information about the form here, such as whether the form is valid, whether it's dirty or pristine, whether it's submitted, or whether it's been touched, and you can see that currently valid is false and also I'm displaying information about the username and password fields. Notice that these touched fields, when I click on username, as soon as I type something in here and leave it, that a bunch of these values changed. So the touched field changed as soon as I left the field and dirty also changed and notice that the username field is now valid but the password field is not valid and neither is the form, if I put a value in the password field, okay, now the password field is valid and so is the form. So while Angular didn't prevent us from submitting the form, it did tell us whether the form and various fields are valid or not and there's a wealth of information here about the different states of the form so you know if somebody has even attempted to type something in a field and whether those fields are valid and that all bubbles up to the form so that the form is only valid once all of its fields are valid. Okay, so let's go utilize some of these values. So first we'll delete all that HTML that we put in here and now let's disable the login button if the login form is not valid. So right here we're going to bind to this button's disabled property and we're going to set it to disabled if the login form is invalid. So now if I come over here, you can see that the login button is disabled and so now the user can no longer log in but we're not giving much info to the user about what's wrong with the form. Notice if I type in my username and password that the login button becomes available and I could submit the form here but we do need to give the user more information about this empty state and why they can't submit the form. So let's add a little validation message that shows up right after the labels. So we'll just add a little emphasis element here that says required and we'll do that for the password too and then we only want to show this if the username field is invalid and then we'll do the same for the password field and then we need to make sure that we use the safe navigator here because these fields are not always available on the login form controls. Okay, now let's just add a little bit of styling for these error messages. Okay, let's go check this out. So if we refresh our app and go to the login page, okay, you can see immediately our form cannot be submitted and we are being told that these fields are required. Okay, so technically this is accurate. These fields are invalid but it's a little annoying to display all the error messages before the user has even tried to do anything so let's just show these errors only if the fields have already been touched. So back over here in our HTML on the ngIf we will only show this if they are invalid and they have been touched. We'll do it for the password field too. Okay, let's check that out. Okay so we are not getting our error messages here and if I tab out of this field, then it shows up. There we go, that's working better but still the original state of the form isn't the greatest. Our form cannot be submitted but there are no error messages explaining why. So you could get creative and do whatever fits your application the best but one thing we could do is add a mouseover handler on the login button and display the error messages when the user mouses over the login button. That should be easy. What we'd like to do is add mouse enter and mouse leave to our login button but disabled buttons don't raise events so we'll have to wrap this button in a span and add the events to the span like this. Okay, so when the mouse enter event is fired on this span, we will set mouseover login to true and then on mouse leave we'll set mouseover login to false and we'll wrap this button in that span. Okay, now on our error messages we can make them only show if the fields are invalid and they're touched or if the user is mousing over the login button. Okay, so let's add parentheses around this here and we'll add an or mouseover login. Okay, and the same thing on password and then we just need to declare this mouseover login property so in our component, we'll just declare that right here, here we go. Okay, let's check that out. Alright, so we're not getting our error messages and if we hover over login, excellent, now it shows what's required. If I make this valid, now only password is required and then I can log in, awesome, so that's really cool how easy that is and how much power there is in those few properties that are available to us on the login form for validation. And there's a practice exercise for this clip, so go check that out.
-
Creating Your First Reactive Form
Okay, this login form that we just created was a template driven form, meaning we built our whole form and all its validation in the HTML itself but there is another way to do it and either one is completely acceptable depending on your needs. The other option is to use a reactive form, also known as a model driven form. Basically this means you define your fields and your validation in your component and then wire them up to your fields in your HTML template. This requires a little extra code but it has some advantages. For example, you have the option of building your form and validation more dynamically based on decisions made in your code and another great benefit is that it makes all of your validation logic unit testable. So let's take a look at how to do this. To demonstrate this, we'll create another simple form. You may remember that in a previous clip, we created this profile page that we'd said we'd finish later. Let's go ahead and create a model driven form for this page. So over here in our profile component, it's currently using an in line template. Let's just delete that and use a template Url, so its template will be here and let's go ahead and create that. Okay, and we'll grab the HTML for that from our GitHub Helper Repo. So over here in the user folder there is this profile component HTML, let's grab that, and paste that in here, okay, and again, this is just HTML, no Angular. Alright, and on our last form we dove right into our HTML template to start wiring up our form with Angular constructs. This time we're going to go over to our component and start configuring our form from here and we're going to want to do this in an OnInit method so let's implement OnInit. Okay, and importing and implementing OnInit is optional. We could've just created this function but implementing OnInit does give us a little bit of typescript help. Okay, so our form has two inputs, a first name and a last name. So we're going to create a form control for each of those like this. Okay, and now we need to add these controls to a form so we use a form group for that. So we'll create a profile form property on our component in a minute and we'll just set that to a new form group and then we set properties on this for each of the controls like this. Okay, so this has a first name and a last name property on it and those property values are set to these form controls that we just created and let's go ahead and create a property for this on our component. Okay, and we want a property on our component for this because the profile form is going to be accessed from the HTML template. Alright, now let's go ahead and import form group and form control. So now we've defined this form but we need to tell Angular which HTML elements these correspond to so let's go over to our template and first we'll bind our form element to the profile form like this. Okay, so this binds our form to the form group that we created in our component and then we need to specify for each of our input elements the property on that profile form that they should be bound to. Okay, and these values, first name and last name, need to match the property values of the form group here. Okay, and it looks like I've misspelled my component HTML. So that should be component. Okay, so this form and these inputs are now wired up to their form controls and the component and you may remember when we were doing the template driven forms that we had to import the Angular forms module into our module. Well model driven, or reactive forms, use a different module called the reactive forms module so we need to import that too. Okay, so up here we're going to also import reactive forms module and add it as an import here. Okay, so now if we go over and take a look at our app, let's refresh here and let's log in first. Okay, now if we go look at our profile page, here's our form and these controls are wired up to their component but there's one thing missing here, I'm already logged in and so my first name and last name should already be being displayed in these boxes and then we also haven't wired up our buttons yet. So let's go take care of that. First let's pre populate these fields with data from my profile. So over here in the component, if we look at our form controls here, we didn't pass in any parameters when we created our form controls. The first parameter is the value that you would like pre populated in the field so we can get the current user information from the auth service so let's import that and inject it, okay, now from the auth service I can get the current user's first name and last name and pass them into our form controls. Okay, so we're creating the first name and last name form controls and when they first render, they will have the values of the first and last name of the current user. Okay, so now let's wire up our cancel and save buttons. So let's come over to our HTML and then we'll add a click handler here and that will just call a cancel function. Alright, and then over here let's create that cancel function and that will just navigate to the events page and we'll have to inject that router. Okay, now our cancel button is wired up. Now let's wire up our save button. That wire up actually happens on the form over on our HTML so we'll use ngSubmit, like we did over on the template based form and we'll have it call saved profile and we'll pass in the profile forms value. So this seems pretty similar to the way template based forms work. Alright, now let's go create our save profile method and that will take the form values in. Okay, now we're going to want to call a method on our auth service to update the current user and we'll just pass in the values from the form. Okay, but this update current user method doesn't exist yet so let's go create that on our auth service. Alright, we'll just add that down here and we passed in the first name and then last name. Alright, and then we'll just do another fake implementation of this for now. Okay, so this is just going to update the first name and last name of our current user property that we've declared up here. Alright, let's go check this out. Okay, so if we refresh our app and log in, and then go to our profile page, there you can see that our first name and last name, which are the values that are currently hardcoded in our auth service are now being displayed in the first name and last name fields and if I change my name, and save it, then you can see my username changes up here but when I hit save it doesn't navigate after I save so let's just go update that. So after we save the user we want to do this same thing. Alright, now if I refresh and log in, and then go edit my profile, now when I make this change, and save, there, it updates my user and redirects me to the all events page. Okay, cool, so that's how you do reactive forms now let's take a look at validating reactive forms and there's a practice exercise for this clip, so go check that out.
-
Validating Reactive Forms
One of the benefits of using reactive forms is that we can define our validation in our component where it's unit testable. So let's add a bit of validation to our form. First we'll just make our fields required and validators are just passed in as the second parameter of the form control so we can just use validators dot required and we'll go ahead and add that to both of these. Okay, and we need to import validators from the forms. Okay, so now we have required validators on our two form controls and it is possible to add multiple validators, we'll look at that in the next clip. For now, let's just go make it so that if our save profile method gets called, it won't save the form if it isn't valid. So right here I can say if this dot profile form dot valid then save and navigate otherwise we won't do anything. Okay, so now if we wanted to write a unit test for this, you could see how I could instantiate a new profile component and stub my auth service to return valid or invalid values and then call save profile and validate using a mock whether or not my user gets updated. I could also get access to this profile form property from my tests and directly run tests against it by populating its form controls and then testing the validity of the form. This seems somewhat unnecessary for a trivial form like this but you can see how in a large form with a lot of validation it would be nice to test different scenarios. None of this is possible with template driven forms. Honestly with a small form like this one, template driven might be better because it's so simple but for larger forms, model driven may be better for testability reasons. Before we jump over into our HTML, let's add some CSS styles that we're going to use over there and we're going to do some more interesting styling with this form. So just like we did before, we'll add some styling for an emphasis tag but let's also add an error class that we can add to our input boxes so that we can style our input boxes and then make them turn red when they're invalid. Okay, and for this to look good, we also need to style the placeholder text and there isn't a standard CSS selector for this so we have to use a different one for each browser. Okay, now we can utilize this error class for styling our input boxes. So let's go add this to our HTML. Okay, so let's make our input boxes have our error styling if the field is invalid and the way designed our CSS selectors we need to add the error class to the div element if the field is invalid. So we'll use ngClass if the first name is invalid and the first name field has been touched. Okay, and we want to do the same thing for last name. Alright, let's go check that out in our form and see how that styling's working. So we'll refresh this and log in. Okay, now if we make this field invalid and leave it, there we go, so our styling is being applied to our fields. Now we need an error message to display. So the logic for displaying the error message is actually going to be identical to the logic we just coded into ngClass. So let's actually move this logic into our component so that we can reuse it. So let's copy this out of here and we'll call a method on our controller called validate last name and if that returns false, then we want to add the error class. Okay, and then we're going to do the same thing up here for first name. Alright, let's go add that over here. We'll just add that in right here, so validate last name and we can get the value of our controls off of this dot profile form but it will be more direct if we can just get it right off the controls like this. In order to do that we need to make our controls available outside of just ngOnInit. So let's set these here that let's declare them up here and then down here we just need to say this dot and same thing right here. Okay, now down here we can find out directly from the last name field whether it's valid and touched and this needs to be an or and we want to say valid here. So we're kind of reversing the logic before where we want to return true if this is valid. So we're returning true if it's valid or if it's untouched, alright, and then same thing for first name. Okay, so this will return true if it's valid and then down here we're ______knotting it so we will add the error class if it is not valid. Okay, and now that we have that in place, let's get back to wiring up our error message. So we can just add a couple of emphasis elements and in our ngIf we'll just call our new functions. So if the first name is not valid then display required and then the same thing for last name. Okay, let's check that out. So I've got to log in again and go to my profile form and now if I leave this blank, you can see that it turns red and we get the required message. Only it looks like we missed something on the styling for the color of required. Okay, we're missing a colon right here. Let's refresh this and try again. Okay, there we go. Okay, cool, so now we have validation in place for our reactive form.
-
Using Multiple Validators in Reactive Forms
In this model driven form that we just created we're passing just a single validator to each of our fields. What if we wanted multiple types of validators on a field? Well, that's easy, we just pass in an array of validators to our form control. So let's add a pattern validator to our first name that requires first name to start with a letter. So we'll just turn this into an array and we'll also pass in validators dot pattern and validators dot pattern takes in a parameter that is the pattern that you want to validate against. So that will just look like this. Okay, so now we have two validators on my first name field. Okay, so let's go check that out. Let's refresh and log in and then over here if I leave this field blank, I'm still getting my required field validation but if my field starts with a number, it's invalid so it is showing that it's invalid here but the problem is my message is still saying required, it should say something about that it's not matching the required pattern. So we're going to have to have a couple of error messages in our HTML. So right here let's add another error message and it will say must start with a letter. Okay, but this still isn't quite right. Now both of these errors will be displayed whenever the first name field is not valid, regardless of the reason. So I need to add another check on each of these like this. Okay, so required is set to true when there is a required error and then we can do the same thing here with pattern, alright, let's go check that out. Let's refresh our app and log in, and then over here if I delete this, then I get a required error and if I make it start with a number, then it says it must start with a letter. Okay, so pretty easy to add multiple types of validation. Okay, so we've shown required validators and pattern validators. We're not going to go through all of the types of validators that are built into Angular but if you come over to Angular.io, and go to docs and then search for validators, this first result here shows the different types of validators so there's required, min length, max length, and pattern. These others are for more advanced cases and then it explains each of them down here and if you need something beyond these, you can create a custom validator. We'll take a look at that in just a few clips here. And there's a practice exercise for this clip, so go check that out.
-
Diving Deeper into Template-based Forms
So far the forms we've created have been really basic like this one. Let's take a look at a couple more forms that have a little more going on. We'll start by creating a template based create event form. We can currently view events in our application but we have no way to create them. We did add this create event component but we haven't created a form for it yet. Let's do that now. If we take a look at this component, the HTML for this is currently in line. So let's delete this and change this to use a template Url and that will live here. Alright, and let's go add that file. Okay, and we'll grab the HTML for this from our helper repo, so under app, in the events folder, we have this create event component HTML and let's just grab all that and paste that in here. Okay, and since we've already gone over the basic form syntax in a prior clip, I've already taken the liberty to wire up the form stuff in this HTML. So you can see our input fields already have ngModel and name attributes and I've already added required field validation to most of our components and the corresponding validation messages and the error styling, except I haven't added any validation to these address or online Url fields. That's because the validation for the location and the online Url fields are a little more complex than we're prepared to handle right now and that's because we want the user to fill out either all of the location fields or the online Url field and we can't really do cross field validation without a custom validator and to do that with template based forms, we need to create a directive so that we can add a custom attribute to the fields that we want to validate. We're not really prepared to do that until Joe talks about directives in a later module. So we'll come back to this validation later after we've talked about directives and so we've added validation to all of our form fields except for these and notice that we have this image Url field that has two validators, a required validator and a pattern validator. So we've added two separate error messages that display appropriately based on which error state we're in. You may remember we did something like this before on the profile form. Alright, and then finally we take a look at our form element, you can see we've already created a template variable for this form and I've wired up the ngSubmit event to this save event method. So let's go add that method. Okay, so over here in our component, we'll add our save event method here and we'll just log out the form values for now and then we just need some styles for our validation messages. Let's just copy that from our profile component because they'll be the same. So I'll grab this styles array and paste that in here, alright, now over in our template we have a bunch of ngModel bindings to these properties that don't exist. So we need to declare those in our component but there are eight or nine of these and I don't really want to clutter up that component that much. We can avoid that by binding to properties on an object instead. So for example for this name binding we can bind to new event dot name. So now this is going to bind to the name property of this new event object. So let's update that for each of these ngModel bindings. So now these are all binding to the new event object and now all I need to declare over in my component is that new event object. So this should work just fine. Alright, now we have this form that we can use to demonstrate a few more concepts. Let's take a look at it just to see the basic functionality working. Before we can do that, though, we'll have to add Angular's forms module to our app module. You may recall doing that before but that was in our user module and this component is in our app module. So let's add that, so we'll just import forms module, and then add it to our modules imports here and actually in one of our next clips we're going to add a reactive form to our app module too, so let's just import that while we're here so we don't have to come back and do that. So we'll add the reactive forms module here and we'll import that here too. Okay, let's go take a look at that. So let's refresh this form. Okay, cool, and if we touch these fields, you can see that the required field validators are working and let's go ahead and fill this form out and then if I open my console, and click save, you can see that our data is being submitted to our component and then logged out but we have a small problem here. Our data doesn't exactly match our event model. If we go over and take a look at our event model, you can see that the address, city, and state fields are nested inside this location object but if we go back and look at the data that's being submitted from our form, you can see that address, city, and country fields are right on the root object, they are not nested inside a location object and we could deal with it and just map it to the right shape in our save method but ideally we'd just get this submitted from our form in the shape that we want. Thankfully, Angular lets us do that in both template based and reactive forms and it's really easy. For our template based form, we just need to wrap our location fields in an ngModel group like this. First I'll just add a div here and then I just need to add an ngModel group attribute and set that to the name of the property that we'd like these fields nested under, which is location, okay, so we'll just wrap these three fields and that div, okay, let's save that and then let's refresh this and put our data back in, okay, so now if I save my form, take a look at this object, notice that we now have a location object inside our object submitted by our form and inside that location object, we have our address, city, and country fields. Wow, that was easy, okay, so that was one major concept that we wanted to demonstrate with this more complex form and then just for fun, let's add a preview image tag so that the user can see the image when they type in the Url in this image Url field. This will just give us an opportunity to demonstrate interacting more with our form and our template. So down at the bottom of our form here, we have this empty image tag. Let's bind its source property to the value of our image Url field and then we don't really want to try to preview that image until the user has entered a valid Url. So let's not display it until that field is valid. I'm using ngIf here instead of hidden because I don't want my browser to try to set the image source at all until the field is valid. NgIf will prevent the image tag from even being added to the dom, so the browser won't even try to render it. If I just used hidden, the browser would try to load the image every time the user typed in the image Url field and so it would it try to load a bunch of invalid Urls, so ngIf is perfect here. Alright, cool, and you can see that we can tap into the validation info and field values wherever we want in the template. So this form's a little bit more complex and you've seen some interesting things that we can do with the more complex form like this. Let's just go finish wiring up our save event method. Okay, so inside our save event method here, we're going to call the event service to save our event and we can just pass form values straight through since the shape of it exactly matches our event model and we'll just have to import this event service. Let's inject that in our constructor. Okay, so that's ready except that we don't actually have a save event method on our event service. So let's go over to that and we'll add one here. Okay, so we're just going to assign an event ID of 999 for now. Of course this really will only allow us to save one new event without having duplicate IDs but we'll come back and make this more robust later and then we also need to default the sessions to an empty array and then we'll just push this onto our events array and let's fix this typo. Okay, so now we have this save event method on our event service that will, for now, just add it our events array. Okay, and then back over to the save method on our component. Let's just copy this down here and then after we save, we'll navigate back to the events list. Now you may remember that we added a route guard on the route for this component that looks at this dirty flag. This will actually prevent us from navigating after we save unless we set dirty defaults. So let's set that. Okay, cool, and now we can add events. Let's just go check that out. Let's refresh this and fill out this form one last time. Okay, now let's save our event. Alright, cool, and you can see we got sent back to our events list page and we have a new Ng Spectacular event that was created using our new create event form that demonstrates a couple more concepts with template based forms.
-
Editing Data with Two-way Bindings
Everything that we've done so far with template based forms has been on pages where we are creating data as opposed to editing existing data. For that reason, we've been using the one-way binding version of ngModel that looks like this but what if this was an edit event page instead of a create event page? Let's just demonstrate that temporarily by changing our bindings to point to an event object and pre populating that object with data. So right now our ngModel bindings are just set to primitives. Let's go ahead and update those to be bound to an event object like this. So we'll use event dot name and event dot date, et cetera, throughout our form and notice as we do this, that the ngModel value and the name value don't have to match. So here the ngModel is bound to event dot date but the name of this control is just date. So the name is what's used to populate the form values and the ngModel binding is used for binding to properties on the component. Okay, let's just go update the rest of our fields. And the location ones will be bound to even dot location dot address. Okay, we could go take a look at this and everything would actually be working fine so changing these ngModel bindings didn't change much but let's go over to our component now and add an ngOnInit, and then I'm just going to create an event object here that I'm going to pre populate with this data. You can go ahead and pause this here and type in this data if you want to but we're going to be deleting this in a minute anyhow. Okay, so up here, need to declare event. Alright, so now we have a populated event property on our component. In a real application, we'd be getting this data from an API call but this will suffice just to demonstrate this. Okay, but if we go over now and take a look at our app and go to our create event page, you can see our data is not being pre populated here. That's because we're still using the one-way data binding in our HTML. So everywhere where we have ngModel, we need to change this to a two-way data binding, or the banana in a box syntax, like this. So I'm just going to do a search and replace and replace all of those with the banana in a box style. Okay, now let's go over and take a look at our form. Excellent, now you can see that that data is being populated and then if I edit this data, then save it, then you can see over here that the edited data is what got saved. So you can see how the one-way versus two-way binding works with ngModel. Okay, this really is a create event form not an edit event form. So let's undo what we've added here for editing the event. So I'm just going to hit control Z and undo all these changes and then over here we'll just get rid of our event object and our ngOnInit but that made for a good exercise to show how to edit data.
-
Diving Deeper into Reactive Forms
We just took a deeper look into template based forms. Let's now take a little deeper look into reactive forms. Right now our event details page is a little boring except for this ridiculously formatted date, which we'll format with pipes in a little while but eventually we want to be able to list sessions for each event so that attendees can see what will be taught when they attend the event. So we'd like to list all the sessions here but first we need a way to create sessions. So let's add a create session page that will use reactive forms. If we go take a look at our session model, you can see that this is what it looks like. So it had an ID, a name, presenter, duration, level, an abstract or a short description, and array of voters for people who upvote the session. Okay, so let's go create a reactive form that we can use to edit this data. We'll put that in the event details folder. So we're just going to add a create session component here and create our basic component, and this will use a separate template file. Alright, let's go add that template file. Alright, let's go grab the basic HTML for that from our helper repo. Let's go in app, events, event details, we have this create session component HTML. Let's grab that and paste that here. Okay, so nothing fancy here, just the basic HTML to edit our session model. Okay, let's add this component to our barrel. Alright, and then we need to just go add this to our app module right here and then down here in our declarations. Alright, now let's go add a route for it. We need to import it. Okay, there we go, so this page will live at events slash sessions slash new and we should be able to hit that now so let's go take a look. So if I go to events slash sessions slash new, okay, cool, here's our new form and you can see we have a few text fields, plus a drop down for duration, and a drop down for level, and an abstract, and if you're curious about those drop downs, let's go take a look at the HTML for that. There's really nothing special here. These are just normal select lists with hardcoded options. Now we could have these options defined in my component somewhere and then I could use ngFor to generate these options but we're just hardcoding them here. Okay, so let's go over to our component and define our reactive form. So first let's import the Angular forms items that we need and then we'll define each of our controls in our OnInit method. Let's create that here. Okay, and then we'll just set up each of our form controls for each of our fields in here and we'll pass in empty data into each one of them and give them a required validator. Alright, and this is going to look the same for the other fields. So we're going to have name, and presenter, and duration, and level, and an abstract. Alright, so we've added required validators to each of these, let's also add a max length validator to the abstract. Okay, so there's quite a bit of validation here and it's going get a little more complex. So you can see how nice it would be to be able to write unit tests to verify all this stuff. Something we can only do if we're using reactive forms. Alright, so now we just need to build a form out of these fields. Okay, so there's our form and now we need to declare each of these variables. So we'll start with the form, which we always declare as a public property so that it can be accessed in our HTML template and let's backspace this a little bit, okay, alright, and we'll declare our form group here. Okay, and then we need to declare each of the fields. Before we do this, let's go take a look at the profile form that we created. So in the profile component HTML notice that whenever we wanted to access one of the controls, we had to do it through the form like this, profile form dot controls dot first name. Well, we had to do it that way because while we made the form public, the form controls were private and that's not really necessary so let's go back to our create session component and let's actually declare these publicly. Okay, now these will be available to us in the HTMl template. So let's go set up our bindings in the template. So first we bind our form to our form group and while we're here, let's wire up our ngSubmit, we'll pass in the form value to a saveSession method. Okay, let's go add that method in the component and we'll just console log the form values again so that we can prove this is working. Alright, and then back in our template, we need to wire up each of our form fields to their corresponding controls and we wire up select lists exactly the same way we do other components and then the same thing for text areas. Okay, that should do it. Let's go take a look at our form. Okay, let's refresh this and enter a new session. Okay, and if I open my console here, and save this, there we go, we can see that our data is being submitted. Okay, and that data shape matches our session model and let's take advantage of the fact that we have a session model that we can use to validate this. So we're going to eventually pass these form values into a service that will save the session for us and, really, we could just pass that as is because it matches the shape of our session model but let's import our ISession interface and map the values over just as a way to sanity check that the shape that we're passing in is correct. So we'll import our session model and then in our save session handler, we'll create a new session object and map each of the fields over from the form values. And you can see that our editor is complaining because this has not yet matched the session model, let's just keep going. Okay, and look at that, it's still complaining because we actually are not adhering to the shape. It's saying that our ID field is missing. Well, we're going to let that be set in our service so we'll just set it to undefined here and then if we look at that again, it's still complaining because voters is missing and this is important and a great example of how the typescript validation can help you in your coding. We actually do need to provide an empty voters array. Okay, there we go, now it's not complaining anymore and then duration actually is a number and we want to cast that to a number. Typescript isn't complaining because the form values that are passed in are of type any and any can be applied to any data type. Okay, and you can decide in your apps whether you want to take this extra step of remapping it to an existing model so that you get the type safety. But for now we're just going to console log this out, we're going to come back and save this later, not in this clip. Okay, so we've defined the validation for our form up here but we still need to make the error messages appear and add our error styling. So let's go do that over here in the HTML. Okay, let's add our error message fields. Okay, notice here that I'm just accessing name directly, I'm not having to say new session form dot controls dot name like we were doing before. That's because we made these form controls publicly available in our component and then also notice that I'm using name dot dirty here instead of name dot touched. The difference between touched and dirty is that if I just put my cursor into a field and then leave that field, it is touched but not dirty but if I start typing in a field then it becomes dirty. I just wanted to demonstrate that dirty option in case you want to use that in your applications. Alright, let's go ahead and add these to the rest of the fields. Okay, and this field has two validators on it so we need to add the extra piece here and the extra error message. Okay, now we just need to add the classes for our styling. Okay, and we'll add that to each of them. Okay, now let's go add our styles. Okay, and let's actually go copy those style from another component because they're going to be the same as the others that we've used. So let's go to our create event component and copy these styles and just add them here. Okay, and then we just need to update this here to apply to our select and text area elements. Now just one more thing, let's just make sure we disable our submit button when the form is not valid. Alright, so let's refresh our page here and notice that I do not get any validation if I just touch the fields, but if I make them dirty, by typing a space, and then invalid, then notice that our validation shows up and if I create a new session, and then open my console and save, there we go, we're getting our data, and it's being mapped to our model and now we have a fairly substantial model based form.
-
Creating Custom Validators
Okay, we've gotten familiar with using Angular's built in validators but it's not uncommon that we want to create a custom validator to do something beyond what built in validators can do. That's easier to do in reactive forms than it is in template based forms. So let's add one to our reactive form. Let's add a restricted words validator that prevents certain words from being used in a field and then we'll apply that validator to this abstract field. A validator is basically just a function and that function just returns null if the control is valid or an error object if it's invalid. So here in our create session component we'll create a validator function that looks like this. Okay, so this function takes in a form control as its parameter. Alright, and then this function just returns a basic JavaScript object. Don't let this typescript syntax throw you off. This really is just saying that this function will return an object, it doesn't matter what shape the object is. Okay, so our validation function is basically going to check to see if the control's value contains restricted words, something like this. So we'll just hardcode it right now to check to see if it includes the word foo and if it does then it's invalid, because that's a restricted word, and so we'll return an object like this and if the control is valid, meaning it doesn't include the restricted word, then we will return null. Okay, so I've just hardcoded the word foo for now. We'll change this to taking a list of invalid words eventually but this is a simple way to demonstrate what it's doing. So if the control's value contains the word foo, then it is invalid and we return this object and this error object typically has a key that matches the validator name and then you can set whatever you want as the value. This will make more sense when you see how this object's used. So in this case we're going to return the invalid words that were found and right now it's just foo. Okay, we'll come back and make this more intelligent but let's see how this is working if we add it as a validator to our abstract field. So up here I'm just going to add it like this. Okay, so you can see better now that the validator's array is really just an array of functions that should be run to validate the control. Let's go see how this is working. So let's refresh our new session page and then notice that I'm still getting my required field validation and if I type in some text here, we're valid unless that text contains the word foo. So you can see that the field is invalid because the field has turned red but we're missing an error message. So let's go add an error message for our new validator. So over here, I already have two validation messages here, let's add a third one and we want this to still display if it's invalid and dirty but only if it violates the restricted words validator. Okay, and then our message will be restricted words found. Okay, so we've started to use our error object that was returned from our validator here. This restricted words keyword right here matches this restricted words key on our error object. Okay, so we've used the keyword restricted words. What about the value foo? We can also access that in our template. So we can actually use that value right here. We'll say restricted words found and then we'll list the restricted words. Okay, so notice that abstract dot errors dot restricted words is actually that error object. It's the same thing that we're using right here and here. So really that errors object contains any validation errors and the values of the properties on the errors object is actually those validation errors. So let's go take a look at this. If I refresh this now and say whatever foo. There, now it says restricted words found, foo and that foo came from our error object here. Okay, now we'd like to be able to make it so that we can pass in the restricted words to our validator like this. So we want to be able to pass in an array of invalid words like that. Just like we are for that max length validator where we're passing in 400. Okay, there's nothing really Angular specific to do here. We can handle this with just plain JavaScript. Remember that what we actually provide to our array of validators, must just be a function. So if we make it so that restricted words is a function that we call that returns a function, we can make this work just fine. Okay, so down here we're going to make this a function that takes in the restricted words and then we're going to have that return a function, like this, so this is just a fat arrow function from ES6, with some typescript typing information here. Okay, so now restricted words is a function that returns a function and that function is our validator function and now we can access these words right here down inside our inner function. Okay, and this is going to be a little bit more complex because here we just had a string and now words is an array. So our logic is going to look a little bit more like this. So first we'll check to see if words are passed in and if not, then we will return null, which remember makes it so this validator passes. So basically this validator is a no op if you don't pass any words in. Okay, then we will find any restricted words that exist in our controls value. Okay, so this map function is basically looping over all the keywords and checking the controls value to see if it includes that word and then returning that word or null if it's not found and then we just need to filter out those nulls. Okay, so now invalid words contains any invalid words that were found inside the controls value. Okay, and then if any invalid words were found, then we will return the restricted words error object and this time we'll put all of the restricted words in it. So they will be comma separated and otherwise we'll just return null. Okay, so up here we are using our restricted words validator and we're restricting foo and bar. Let's go check that out. Okay, so I can type anything in here expect for foo, notice it says restricted words found, foo, and if I add bar, it also finds that. Alright, cool, that's all there is to creating custom validators. Now we could make this a reusable validator by moving it out of here, let's just do that. So let's move it into our shared folder. So over here, we'll create a new file, restricted words validator, right, let's go grab that out of our component. So we'll take this out of here and move it over here and then we'll just export this. Okay, and it looks like we'll need to import form control. Okay, cool, now we have a reusable validator. Let's just add this to our barrel. Alright, and then we can just import that into our component now. Okay, cool, so now that's being passed in as an import. We can get rid of the this keyword and everything should be working just fine. There we go, now we have a reusable custom validator and there's a practice exercise for this clip, so go check that out.
-
Summary
In this module, we learned how to use data models to help with type safety and we learned how to create and validate template based and model driven forms. We also learned how to use two-way data bindings to edit existing data and, finally, we learned how to create and use custom validators.
-
Communicating Between Components
Introduction
In this module we'll get some more practice passing data back and forth between components. Now that our app is built out a little more this will give some more real world examples to work with. There's not much to introduce here, so let's just jump right into code.
-
Passing Data into a Child Component
Let's take another look at passing data from one component into another. Right now our event details page is showing basic information about each event, but there's something missing here. Our events have sessions that we're not displaying yet. If we go over to our events service and look at the data that's in there, you can see our events have these session arrays. So let's work on getting those displayed. So over here in our event details html we want to display those right here, right after we show the basic event details information. And so let's add a divider line, and then we'd like to be able to just display our sessions with an element like this. So we'll create a component for this, but how is the sessions list component going to get its array of sessions to display? Well, we have that data available to use on the event object that we're already binding to on this page. So we'd like to just pass it into this component. If you remember from earlier we can just pass data into a child component like this. So this is basically saying I want to bind the session property of this session list component to the sessions array on this event object, and that event object is in our even details component. Alright, so let's go create our session list component. Okay and here's what that will look like. Okay so there's the session list selector that we used in our events detail component, and then this will use a separate template file. Alright, now we just need the component class, and then if we go back and look at our event details component html you can see that we're expecting there to be a sessions property on this session list component. So let's add that and that will be an input property. Alright, we'll have to import input and Isession. Alright, and that's all there really is to this component other than the html template so let's go add that. Alright, and let's go get the html for this template from our Github helper repo. So over here there's this app folder and then in events and event details there's this session-list component html. Let's grab that and paste that in here. Alright this is pretty simple. So we just have this ngfor that is going to loop over each of the sessions in the array that was passed into our input property, and then display each one. Okay, so we just need to add this component to our event details barrel, and then we just need to add that to our app module. So we'll import it here and add it as a declaration here. Okay, cool let's go check this out. So if we refresh our event details page here. Alright awesome, now we have all of our sessions displaying, and they're displaying for all of our different events. So that's another great reminder of how easy it is to pass data from one component into a sub-component.
-
Passing Data out of a Child Component
We just saw how to pass data from a parent component into a sub-component, but what if a sub-component needs to pass data back up to its parent? This is typically done in response to some event on the child component like clicking a button, and this is made possible using output parameters. If you remember we created a create session component previously, but it isn't completely wired up, and it's only available in our application by typing in the url, and when you click save it simply logs the session to console. Let's make it so that we can add sessions right from our event details page. Okay so on our event details page let's start by adding a little header above our sessions with an add session link. Okay so this will just add a little sessions header with an add session link, and that anchor tag when you click on it, it's going to execute code in our component. So it has no href, but when an anchor tag has no href it won't have the pointer cursor when you hover over it. So let's just add a style for that in our component. So we'll just say anchor tags should have a pointer cursor. Okay, and that link is calling add session on our component so let's create that method. Alright, and all this add session method is really going to do is put us into an add mode. So we're just going to toggle a flag on our component. Alright, and we'll need to define that property. Alright now we can use that over in our template. So if we're in add mode we're going to hide our session list. Okay so we'll only show that if we're not in add mode, and then instead of the session list when we're in add mode we will show the create session component. Alright, but that create session component actually still needs a selector so let's add that. So this is the create session component that we created earlier, and then we only want to display this if we are in add mode. Okay let's go check that out. So let's refresh and go to event details. Okay so you can see here's our header and we have this add session link here. So if I click on that then we get our create session form, and this is where our output parameters come in. Our sessions need to be added to the current event that we are looking at in the events details page. So when we click save here this session that we add needs to be added to this event, and then this event needs to be saved with its new sessions, and so when we click save on this create session form we just want to pass that session data back to the event details phonic and let it handle all of that. So over in our create session component we just need to create an output parameter for our parent component to bind to. So right here we'll add our output property. Okay, and we'll have to import output. Okay so we're going to use this output property to emit a message back to our parent component when the user clicks save. So this save new session output property is going to be an event emitter and we'll have to import that too. Okay so now when we save our session instead of just console logging it we will use our save new session event emitter to emit an event, and the data that we will emit is the session itself. Okay so now we have an output parameter to bind to. Let's go bind it to our event details page. Alright now we'll go right here. So we're going to bind to the save new session event, and when that's fired we're going to call save new session, and pass in the event that is emitted. Okay so that event that's being passed along is actually our session object. So let's go create our save new session method on our event details component. Okay, and a session is going to be passed into here, and it should conform to the Isession model. Alright lets go import Isession. Okay if you remember on our create session form the session id on this is going to be undefined. So we need to assign a new session id to this session when it comes in. So we'll the max session id off of the sessions in the event, and then increment it and assign that to our session id. Okay so this is calling math-dot-max with all of the session ids from the sessions array. So that should return us the maximum session id, and then we'll just set the id on our new session. Alright, and then we'll just add this session to the event. Okay so we're just pushing that onto the sessions array. Alright, and then we just need to call update event on our even service to save this event. Alright, and then remember we're toggling an add mode for whether or not we are currently adding a session, and so let's toggle that back to false since we're done adding, and that should display the session list again. Okay so now we need to go add that update event method on our event service so let's open that up, and we don't want to use this save event because that's for saving new events. We just want to update this existing event. So we will call update event here. Okay so this will just need to find the existing event in the array, and replace it for now. Okay so our create session component now emits a save new session event, and we are binding to that in our event details, and when that event is emitted we call save new session on our event details component which adds the session to the event and then updates it. So let's go check that out. Refresh here. Okay so here is our list of sessions. Now let's add a new session. This will be a one hour beginner level session. Alright and then when we save that you can see we excited add mode and if we come down here we have my new session on this event, and if we go back to the event lists page and come back in you can see it's still there. So it did save it in memory to this event. Okay awesome now there's just one more output property that we need. We need to honor this cancel button. So essentially when we click cancel in our create session child component we need to let our event details component know so that it can exit out of add mode. So over in our create session component we will add another output parameter, and we will call that cancel add session, and that will also be event emitter, and then we'll wire up the cancel button click on this component to emit that event. So over in our html we need to just wire up the click event on our cancel button, and that we'll just call cancel, and then back in our component we'll handle that, and we will just call emit on our cancel add session emitter. Okay, and then over in our event details component html we'll just need to bind to that. Okay so we're binding to that output property on the create session component, and when it's emitted we will call cancel add session on our component so let's go add go a method for that, and all that needs to do is set add mode defaults. Alright let's go check that out. So let's refresh our event details page, and we'll click on add session, and now if I hit cancel you can see it takes me out of add mode. Okay great, so that's a great example of how to use output parameters to pass data back to parent components.
-
Summary
In this module, we took a deeper look into inter-component communication including how to communicate with child components, and how to communicate back out to parent components. These are important concepts to understand since communicating between components is a common practice, and we're now prepared to take advantage of this in our own applications.
-
Reusing Components with Content Projection
Introduction
In this module we're going to cover creating reusable components with content projection. Any component can be reusable, but what makes content projection so powerful is the ability to change the content inside of a component based on the needs of the application. We're going to start off by looking at content projection itself, and once we get a handle on that, we're going to look at how multiple slot content projection works where we can project content into more than one place. Content projection is a feature where you can have a component with some kind of visual wrapper, shown here in orange, but the content within the wrapper is decided by the developer. That lets you take a common wrapper like say a dialog box, and reuse it with different pieces of content. That way you don't have to re-implement common functionality. Again in the case of a dialog box, this would be like the cancel and save buttons, or the positioning logic. This is called transclusion and Angular 1. With multiple slot content projection things work exactly the same as with regular content projection, but this time we have more than one slot into which the variable content can go. Here I show these different slots side by side, but their arrangement can be anything. They can be side by side, on top of each other, or anything really. Also were not limited to two slots, I could be three or four or any number of slots. Although two is by far the most common after one. We'll see exactly how to do content projection, and even multiple slot content projection in the following sections.
-
Content Projection
Here in our events, if I click on a specific event we can see a list of the sessions. I'd like each of these session items to be collapsible. So if I click on this little well here, that it would collapse and just show the title of the session, and not the entire session. Now of course I could just go into the component here and add that functionality so that it can collapse and show just the session. But this sort of functionality is actually fairly reusable type of functionality. A component that can show a title and a detail, and collapse to just the title on click, and then expand just the title on the click. So let's implement that with a new reusable component. We're going to go into the session list page, and start in the HTML. And here I'll define how I want this to look. Right now I've got my 'div' with my column of 10 right here and then this 'div' with a class of well. I want to replace this with a new component. So let's write in the HTML ourselves. This will be a collapsible well. That'll be the name of the new component we create. And of course we have to tell it what the title is, we'll bind to a title property, and this we will set equal to the 'session.name'. And because we've done that we don't need this 'H4' anymore, so we're going to remove that. And down here instead of a close 'div' , we'll have a close 'collapsible-well'. Now this is what our HTML will look like. Let's now create the component file. I'm going to create that in the common directory since this is a common component that could be used in any area. So in my common directory I'll create a new file called 'collapsible-well'. And this is going to be a component so I'll have to import component. Now just a quick note, notice that I put a semi-colon at the end of the line of code that I typed. I'm more of a semi-colon user, Jim is not so much. So you'll see some inconsistent styles here for a bit. We are going to address that later on in the course. And we'll create the component itself. The selector is collapsible well. And the template we'll define in line. We know that it's going to be a 'div'. And that 'div' is going to need a click event to handle when you click to toggle the visibility of the body. So let's go to click event and we'll just call a method named 'toggleContent'. Then we'll go with a class of well, and also pointable. Let's close up that div. And we know that our title was in a 'H4' so let's put that 'H4' here. And we'll give it a class of 'well-title'. And we'll bind to the title property. And finally we want our content to go here. For now I'm just going to leave this as a comment. Of course that comment doesn't actually work, because this is HTML. But we'll come back to this in just a second. Now let's create our class. And we know that it has one input property, which is a title, and a type string. We need to bring in input as well. And we know we need that toggle content method. And in this case we just want to toggle some visibility, so let's create a visible property. And inside of here we'll set it to the opposite value. And now we got the basics of our component, but the magic is going to be back up here on line eight. This is where we want our content to be. So were going to use a special tag which is the 'ng-content' tag. This element is what tells Angular whatever content exists inside of my component, which we can see back here in our session list, is all of this HTML right here, I want you to put it right here, and put it inside of it. So you can just close this up. And that's all we need to do in order to get the content projection to work, but of course we have our toggle content functionality, and to make that work we're going to want to toggle the visibility of this element itself. So let's just put an 'ngif' right here setting it to visible. And now our functionality will work to toggle that body content. Let's go back and save our session list component. And of course we have to add this to the module. So let's go to our module and down here where we got the 'common/toastr.service', this is probably the place we'll want to bring in this component as well, since it's inside the common directory. I could go in and create another index file, but just for simplicity I'm going to add its own line. And then of course we need to take this and add it to the declarations, so I'll just add it to the end. Now we'll save that change and we can go back out to our website. And let's refresh. And now if we've done things correctly, if we click on one of these session items, we can see that it's collapsing down to just show the title. And there's how we can use content projection to make a reusable component. There's a practice exercise for this clip, so take a moment to do that now.
-
Multiple Slot Content Projection
Our collapsible well is working, but let's now add another piece of functionality. I want to be able to indicate which of the sessions is extremely popular, and has a lot of votes. I'd like to do that by adding a little 'on fire' icon, next to the title of the session right about here. To implement this, I'll need to use some business logic that shows a flame icon if the session has a certain number of votes. This is obviously specific to a session if I were to add this kind of logic to the collapsible well it would tie it too closely to the specific implementation and not make it as generic and reusable as I'd like it to be. The problem is that our collapsible well currently can only bind to the title which is a string. Here we are using the collapsible well, I can send in my session name as my title, but I'd like to have a little bit more logic to show up inside of this 'h4' right here. I don't want just a simple 'h4' with a simple binding. I want some logic in there, and implement that inside of my collapsible well wouldn't be a good idea. So instead what I'd like to be able to do is have another area inside of my collapsible well. One for the title, and one for the body. Both areas being able to contain whatever HTML I want. Now this won't work exactly as is, because Angular doesn't understand what to do with these title and body sections, but we're going to come back and address that in just a minute. For now we're going to leave these tags where they are because they're illustrative and useful to us to understand what we're putting where. So we got our body, let's talk about how we'd implement the title. Now I want the name of the session to show up the same way we've got it bound right here. But I want it here in this section. And then next to the name, I want a little icon that's going to show up a flame red icon that will show up if we have at least a certain number of votes. I'll use the 'i' tag for that with an 'ngif' that will say we only want this to show up if the 'session.voters', which is the array of voters, has a length of greater than three. So if four or more people who've voted for a session. And I'll use a class of glyphicon, and 'glyphicon-fire' to use the fire icon, and we'll set that to red with a style attribute. And there's our fire icon. Now we can get rid of the title binding, because we're no longer getting our title that way, we're now getting it in the section. And again Angular 2 doesn't understand this title and this body attribute. Obviously body is a well known HTML attribute, so we can't have this as our body. Title we don't want to use that either. We need to tell Angular 2 that this is the title section and I want to stick it in a certain place, and this is the body section, and I want to stick it in a certain place. Let's go back to our well component and look at that. Here I've got our 'ng-content' tag that allows us to stick in whatever content is going to be inside of the collapsible well. And then of course we're bringing in the titles in input and we're binding it right here. So let's change this to work with instead of a bound title any HTML that we want. What we need is another 'ng-content'. We still want it to be inside of an 'h4' tag, but I want to change this class, I'm going to remove from the 'h4' tag and just do that as a wrapper. And instead, inside the 'h4' I'm using 'ng-content', and this is where I want my title to go. I want my title to end up right here, and then my body to end up right here. And back to our session list component HMTL, it's this piece of HTML that I want to show up right here. So we've got to tell Angular 2 that this piece of HTML should show up inside of this 'ng-content', whereas this piece of HTML here, should show up inside of this 'ng-content'. And we can do that by using the optional select attribute with an 'ng-content' tag. And the select attribute, works like any CSS selector. So I can give it a class selector with a dot, I can give it an ID with a hashtag. So I can use a CSS selector that we're familiar with. So I can go in here and change this title to a 'div'. And let's change that as well. And we'll do the same thing with body. And instead let's give this 'div' a class of title. And this 'div' a class of body. And now if I set my selector here to '.title', and add a select attribute to '.body', then Angular 2 will match up the correct content with the correct piece of HTML based on the class selectors. Now we've used class selectors a lot in the past to do things like this, but there's always the possibility that a class that we put in here could conflict with something that we have in a CSS file. So there's another way that we can do this. Instead of giving it a class, let's give it an attribute. Let's give this 'div' an attribute of 'well-title', and we'll give this 'div' an attribute of 'well-body'. Now in order to select that we change this from '.body' to the attribute selector 'well-body', and the same thing here, 'well-title'. I personally like this selector a little bit better than the class selector just for the reasons that I've mentioned, it doesn't give us a potential conflict with CSS classes, and I also think it stands out a little bit more saying that there's something about this 'div' that has to do with being a 'well-body', rather than hiding it inside the class, especially if we had a reason to have other class tags in here like pointable, or something else. We'd have to put our body class, our selector class alongside actual style classes which I really don't like. So this is the method that I recommend. As of right now the style guide doesn't have any opinion on how you do this. That may change in the future, so you can always refer there for more opinions. But this is what I like is to use an attribute like this. So let's save this HTML here, we'll go back to our component. We can make one further change, this title we don't have to bring in anymore as an input. And now if we go back to our HTML and refresh, the session lists look the same, they're still collapsible, but we've now got a little fire icon next to the sessions that have at least four votes. And that's how we can use multiple content projection with Angular 2. There also is a practice exercise for this clip, so you can take a moment to do that now.
-
Summary
In this module, we saw how to build a very specific kind of reusable component. Re-usability is very important when programming, and anything we can do to reduce duplication of code is going to help us out. With content projection we can create components whose reusable pieces are limited to just the external portions of the component, and the inner pieces can be different based on the needs of the application. This is content projection and it is an important tool to have in our belt when developing applications with the Angular 2 framework.
-
Displaying Data with Pipes
Introduction
In this module we're going to learn all about how to display our data. The focus will be on using pipes to do this, but we'll also see that for some tasks, we won't use pipes at all. We'll start out by looking at some of the built-in pipes available in Angular to format our data the way we want it. We won't look at all the pipes, instead just a few of the more common ones, with a focus on how to use them. It'll be up to you to look at what pipes are available in Angular. After that, we'll look at how to create a custom pipe to get data formatting functionality that isn't already built in Angular. Then, we'll look at how to filter data and how to sort data. In Angular 1 we used to do this with filters. In Angular 2, things are different. For those who are familiar with Angular 1, they'll be used to using filters for three different purposes, formatting data, sorting lists of data, and filtering lists of data. The filters in Angular 1 did this job beautifully. Unfortunately there was a problem with two of these three jobs: sorting and filtering. There were potential performance problems. Not everyone saw these issues, but they were lurking, and would show up in the right instances. As a result, pipes in Angular 2, don't have all the same functionality as in Angular 1. They still handle formatting, but aren't used for sorting or filtering lists of data. We use a different mechanism. We'll still cover how to sort and filter data in this section of the course, though, and we'll see how to do that in just a little bit here. But first, we'll look at pipes. They are powerful, useful, and generally very simple.
-
Using Built-in Pipes
Now there's a few problems on our all events page that we can fix with pipes. I want to avoid the most obvious one, the problems with formatting dates for just a second, and instead fix an easier one, which is the names of the events. Notice that the names of the events are cased however the data was given to us. I'd like to fix that. I want all events in uppercase. So we can go over to our code, and into our event-thumbnail-component.ts, and here where we have the event name, we can add a simple pipe of uppercase. This is one of the built in pipes in Angular 2. And I'll save that, go back and refresh the page, and now all the names are in complete uppercase. That gives our page a bit more of a uniform look, and in the details, we've got the same problem. Here the name is not uppercase, so let's go into the details page, and here we're just going to go into the HTML component and add the same uppercase pipe. And we'll refresh, and now the name is all in uppercase. Now we've got the obvious problem, the date. These dates are obviously not the way that we want them formatted, so we're going to go in to the date, and we're going to add a date pipe. We'll just simply add date, save that, and refresh, and we can see that already the date is formatted much nicer than it was before. Now this is the default formatting for the date pipe. We could actually change the formatting using parameters. Parameters on pipes are very simple, you put in a colon, and then you can add the parameter you want. In the case of the date pipe, the parameter that it takes in is a string, and there are several values for what the string can be. There's some pre-built in values like short. That's how the short looks, or just short date, which gets rid of the time. And that's what I want to use. You can also mess around with values like... Y for the year, a capital M for the month, d for the day, and you can add in slashes on that if you want. So let's make this change, and we'll see the year, slash the month, then the day. We're just going to go back to short date. It's a nice, simple format, and we want to make that same change in our thumbnail component. Okay, and our dates are formatted nice. Now there's one more piece of data I want to put a pipe on, and that is the price. Notice that the price looks fine, it's got the dollar sign and then the price. The formatting is just fine, but the problem is, is that we've hard-coded in our dollar sign, right there. And I don't want those hard-coded in, I want those to be specified. That'll position me up later on, in case I want to use different units. I can parameterize that and some events can be in dollars, some events can be in pounds, some could be in Euros. So, we're going to change this to have a pipe of currency, and we'll get rid of the dollar sign, and of course we had to escape that, so we'll get rid of the backslash as well. And we're going to add some parameters to the currency pipe. We'll want to specify that this is in US dollars, so USD, and here is where we could be reading this from our data. I'm just going to leave this hard-coded to USD for now, but it gives us a place that in the future, we could use different units. So save that, and refresh the page. And we're now back to seeing the dollar signs. And let's make that same change inside of our details page. Change price to currency filter. It's USD, and get rid of the dollar sign, and save. Back to the page and refresh. And we've still got the dollar sign showing, but we're now using a pipe to format the price. And those are some of the more commonly used built-in pipes in Angular 2. There are homework assignments for this clip, so go ahead and do those now.
-
Creating a Custom Pipe
Another formatting problem that we've got inside of our application is the duration. Currently it's just showing a number, and we actually want this to show what the actual duration of the session is. If we look at the Add Session page, we can see that duration is selected and has one of four options. Half hour, 1 Hour, Half Day, Full Day, corresponding to one, two, three, or four. So let's fix that. Let's display our durations as their string value, not as their numeric value. Unfortunately a duration pipe is not built in to Angular 2. Of course it's not, because this is very business specific. So we're going to have to create one. Go back into our code, and underneath the shared folder under events, we're going to create a new file called duration.pipe.ts. And we're going to import from angular/core, Pipe, and PipeTransform. And now we can use the type decorator and that just requires one piece of information, which is the name of the pipe, and this pipe's name is going to be duration, and now we'll export a class. We'll call it DurationPipe, and that implements PipeTransform. And we're getting an error because it's not implementing the correct piece, which is a transform function. It has to implement a transform function. And this transform function takes in one parameter of value. In our case we know this is a number, so we're going to type this as number, and it returns a string. And now string is underlined because we're not returning a string yet, so let's create a switch on the value, and in the case that this is a one, we're going to return the string Half Hour, and we need three more cases. And finally, we need a default, so we're going to write, in that case return value.toString. And now our pipe's been created, but we can't just go into our session-list.component and enter duration, add the duration pipe. If we do we'll get an error, and the error pops us and it says, "The pipe 'duration' could not be found." We have to make Angular 2 aware of this duration pipe, so we're going to go into our index barrel file, and we're going to export, star, from the new file we created, duration.pipe. And then inside of our app module, we're going to import this duration pipe. It's from this events index, so I can just add that here, and then we need to add it to the declarations section, so I want to go past these components and add it last. If we go back and refresh our page, we no longer get an error, and our duration is now displaying as its string value. So that is how we create a custom pipe. It's very simple. All you have to do is run a transform function that takes in the input value and returns the output value. There's a homework assignment for this concept, so if you want to do that, go ahead and do that now.
-
Sorting and Filtering Overview
In this section, we're going to get an overview of how we filter and sort data in Angular 2. There's no built in order by or filter by pipe in Angular, so in this section we're going to dig in a little bit, so that we can understand why. But in order to do that, we will first need to talk about identity and mutability, which is the opposite of a current hot topic you've probably heard about, immutability. Once we understand those concepts, we will talk about why in Angular we don't use pipes for filtering and sorting. It's helpful to understand that variables that contain primitive data, like numbers, are kept in a specific kind of memory where the variable name and the data are together. I've created a diagram of these different memory slots shown in orange. These are the variable A and B, which we can see the code on the left has assigned each of them to three. This creates two different variable slots, each containing that value of three. When I compare them, I get true, because three is three, and with primitives, we just compare the values. Now if I assign a new variable, C to A, it creates a new slot named C, that has a three in it. When we compare these variables, again they return true, because they contain the same value. But when we change the value of one of them, so let's assign four to A, the effect is that the variable slot for A now contains a four, but C and B still contain three. A is now different from B and C, an if we compare them, they return false. Object identity on the other hand, is different. Objects are stored differently in memory. Object variables don't contain data like they do with primitives. They are pointers to the data. Here I've got an A variable and a B variable that each point to an empty object. In this case, just like with primitives, I created two different values, which you can see in the code on the left. The big difference is that when I compare them, the result is false. With objects, Javascript doesn't just care if the values look identical. They are two different objects, so the comparison returns false. By the way, none of this has to do with either double equals or triple equals. That's only about type conversion. So don't think that any of this is different depending on what comparison you use. So now, if I create a C variable and assign it to A, it behaves differently from primitives. It doesn't create a new empty object, it just points C at the same value as A. And of course, this will return true if we compare them, because A and C are now pointing at the same exact object. Now we can change A. With objects, we change them by mutating the object. So here I'm adding a var1 property to the object that A points at. That changes the actual data. Notices that C still points at this data. So now, if we compare A and C, they are still the same object, so they still return at true. Let's back up now. Instead of mutating the object by adding a property, let's assign A to a new object. This, then creates a new memory slot with that value, and A no longer points to the old slot, it now points to the new slot. And now it's obvious that if we compare A and C again, they are different. C still points to the old object that A used to point to, but A now points to a new and different object. The next concept we need to understand is mutability, and how that affects change detection. Mutability is the fact that things like objects can change. With ordering and filtering in Angular, we're obviously talking about lists of data. We order and filter lists. In Javascript, lists are implemented with arrays, so we'll start talking about arrays. Remember, arrays are just a specific kind of object in Javascript, so all the rules that we learned about identity with objects applies to arrays as well. Here I have a simple array with two items in it. These are users, Dave and John. If something happens in our program, Angular needs to know what it should re render, so it runs through a change detection cycle. Let's say that we delete the John user. Now Angular wants to know if the array has changed, so that it can display the new set of data on the page. You might think it could simply check the length property and notice that the length used to be two, but is now one. But what if, in addition to removing a user, we added an entirely new user? In that case, the length of the array hasn't changed, but the data has. Notice that we didn't change the second user object. We truly deleted it and added a new user. Now imagine this isn't an array with two items, each with only one property, but instead it's thousands of items with dozens of properties each. In a typical scenario we'd want to filter and sort. The algorithm to compare the new values in the array to see if it has changed is a very long and complex algorithm, and can end up taking a long time to run. This is one of the core performance problems in Angular 1. This is mutability. Objects and arrays in Javascript are mutable. They can change any time without changing their identity. Now let's complicate the matter with a pipe, which we're trying to use to sort our data. The situation is the same for filtering, but we'll use sorting as our example. Here we have our user array, but we're going to run the data through a theoretical sorting pipe, so that the data can display sorted. We ran it through our pipe and the data is not sorted. That's great, but what if we change our source data? Here we've changed the Dave user's name from Dave to Ralph. Now the resulting sorted data is incorrect, and out of order. We want the display to update. Angular needs to know about that change, but remember that to tell if the array has changed, it has to go through each item and each property to see if things have changed. This can be a rather expensive operation, so by default, Angular only runs a pipe when the identity of the source data has changed. Remember that in past sections we were running primitive data through pipes, strings, numbers, and dates. So this works great, because comparing an identity is so quick, you could do it hundreds of thousands of times or even millions of times every second, and still have plenty of CPU cycles left. But with filtering and sorting, the source data will be objects, so the identity isn't going to change, so the pipe isn't going to be re-run, and therefore, the display isn't going to be updated. There is another option. This is called an impure pipe. Impure pipes run on every cycle of the change detection engine. This is a problem because that means that the sorting or filtering operation you write, will now run unnecessarily every time an event happens in your application. Let's say, you're listening to the mouse move event, and every time you move the mouse around, your pipe is going to execute and produce the new filtered or sorted view of the data, multiple times, possibly hundreds or even thousands of times. Don't forget that the results have to be re rendered to the DOM. This method of executing the filtering or sorting code every time change detection runs is how Angular 1's filters worked, and this is one of the reasons that Angular 1 often had performance problems. So even though, Angular 1 let you shoot yourself in the foot this way, in Angular 2, that ability has been hidden. Pipes are no longer recommended as the way to filter and sort your data. Now, the recommended way is to filter or sort your data in the component that displays the data. That way you can update the filtered or sorted list whenever the source data changes, which you will know about, since it's your code that has to actually change the data. It also means that instead of a generic filtering or sorting method that has to work for every scenario, you can write this code yourself so that it can be customized to be efficient, and that's what we're going to do in the next sections. We're going to add filtering and sorting to our application, but we're not going to use pipes, even though we're in the module about pipes. This functionality is very common and we need to understand how to do it, but we also need to see how to do it correctly.
-
Creating a Filtering Display
In this section, we're going to add filtering to some of our data. I want to filter our sessions list. If we look at a list of sample sessions, we can see that each session has a level. This session is an intermediate session, this one's also intermediate. This one's advanced and this one's advanced, and the last one is a beginner. So let's put four buttons right up here at the top, and the first one will be all, and that'll be selected by default. Then we'll have beginner, intermediate, and advanced next to each other, and whichever button you select, will filter the sessions list to just the sessions that match that level. So we're going to go to our code, and we're going to start in the event details HTML page. Right here is where we're going to put the buttons, and we're going to stick them inside of this div here. So let's start out by creating our all button, and then we want to put a binding to set which button is active. So we're going to use the class.active binding. That binds to the active class. So the active class will exist on this element if the following condition is true. Let's pause and keep this un filled in for the moment, and we'll come back to it. Next we're going to need a click event, and inside of our click event, we're going to set our filter condition, and again, we're going to pause and come back to this in just a minute, and finally we got the text. 'Kay, so there's our first button, and this one will be Beginner, this one'll be Intermediate, and the last one is going to be Advanced. Now let's talk about how we're going to implement this, and that will dictate what we put inside of the class.active binding, and the click binding as well. Let's go into our component, and here we've got the EventDetailsComponent class. This is where we want to implement our code. So what I want to implement is a filter property. I'm going to create a new property on this component and we'll call this filterBy. This is going to be a string, and I'm going to default it to the value all. I'm just going to use lowercase. It's not going to match the casing of the word in the display. Now this filterBy property starts off with a value of all, but it will switch at certain point to beginner, intermediate, or advanced and we'll just use lowercased strings for those, and save this, and go back into our HTML, we want our class to be active when the filterBy property equals the value all. Right, or the string all, which is why I put inside the single quotes here. And the same thing when we click this button, we want to set that filterBy property to the value of all. So we're just going to put that in line. We're just going to set it right there. Now that we know what we want inside of each of these, we can set these for each of the other three buttons. So let's start with the filterBys. This'll be beginner for the click event, intermediate, and finally advanced. And then we'll also set the class binding. Again, beginner. Scroll that down a little bit. Intermediate. And advanced last. So let's just go back to our HTML page and take a look at how that looks. 'Kay, we've got our buttons showing up, the all has the active class applied to it. Notice that there's no gap in between the buttons and this first session. It almost makes it look like tabs. Let's put a little bit of a gap in there. We're going to put in a style. We'll just give it an a margin bottom of 10 pixels, and let's look at that. All right, that looks okay.
-
Filtering Data
So now we need to change the data. We need to pass this setting, this property, filterBy property into the session list that we've got, and down here we've got this session-list component right here that we're passing in a sessions, so since we're already passing in the sessions let's also pass in what the active filter is, and we'll let the session list itself be responsible for the filtering. We'll just tell it what it should be filtered to. So we're going to create a new property binding, and we'll call this filterBy, and we'll set it to our filterBy property. So this right here, is this property here, and this is a property on the session list component, so let's save that and go into our session-list component, and we'll create that input property. We've already got the sessions coming in, so we'll create a new input property, and this is called filterBy and that's a string. And so now we're passing the value into this component, but we need to take action on that value, and we need to change what sessions are being showed when this filterBy property itself changes its value. And there's a great way to do that. There is an interface called OnChanges, and if we implement that interface in our class, then what it wants us to do is implement a method called ngOnChanges. This ngOnChanges method is going to be called every time one of the input variables to this component gets a new value. And that's exactly what we want. We want to be able to react whenever our filterBy value changes. Now the ngOnChanges can actually be called before any of the other data is set, so remember again, that our sessions are being passed in, and we don't want to react and execute any code here in the session list in ngOnChanges event if the sessions haven't been set yet. So we're going to first make a check and we're going to say if this.sessions, which is just a quick and dirty way of saying if we actually have sessions set. If they're not set, this is going to be an undefined value and therefore it'll be faulty so this won't pass. But if it is set, then I want to filter my sessions. So I'm just going to call a method on this same class, named filterSessions. So this dot filterSessions. And I'll just pass in that filterBy setting. Now I don't have to pass in the setting, because it's available on the class, but it does make the method itself stateless, so let's implement that method. filterSessions. I'm going to be passing in our filter value. If the filter's value is set to the value of all, the string of all, then we don't want to do any filtering at all, we just want all of the sessions to be visible. Otherwise, we want to filter our sessions to just the sessions that have a matching level. And the reason I chose those strings is because the data is the same way. It has different casing so we'll have to deal with casing, but that way I can filter to just the sessions that have a matching level. So let's talk about filtering our sessions and what that displays like. We go an look at our session-list.component, and right at the top, we've got this ngFor that says for every session of the sessions variable, we've got a div, or essentially a row. So I want this variable, this list of sessions to be a sub set. Now I don't want to actually go in and modify the sessions property itself, and give it a subset, because then I might lose the entire set of sessions. So I want to leave this alone. Instead, let's create a new property, and we'll call it visibleSessions. It'll also be an array of Isession, and we'll default it to an empty set, and that is what we're going to bind to and display on our page. So go back into the HTML, and instead of sessions, we're now going to be displaying visibleSessions. Save that change. And back here inside of this filterSessions method, that's where we're going to change what the value of the visibleSessions array is. So if the filter is set to all, then we want this.visibleSessions to be all of the sessions. Now you might think that we just want to set it equal to this.Sessions, right? And just set it equal to that variable, but we don't. We want this to be an entirely unique list or duplicate, so we want to really clone the array itself, and there's a quick and easy way to do that with the slice method. We're going to slice it from the very first element, and that creates a complete duplicate of the array with all the same elements. Then in our or else clause, we want to set this.visibleSessions to some sub set of the sessions array. And there's a nice method on arrays called filter, which creates a brand new array and we'll pass in a little lambda. We're going to take in the actual session, and we're going to return the session's level, and let's lowercase that so we're going to go toLocaleLowerCase, and we want to return true if that matches the filter value. And, it will return false if that doesn't match. So anything that matches true or returns true is going to be included in the new array. Anything that returns false will not, so therefore any rows or any sessions that have a level that matches the matching word beginner, intermediate, or advance will then be included in the new visible array, or visibleSessions array. And anything that doesn't is not going to be included. So now we've got a brand new array that's being copied and that's what's being displayed. So let's save those changes, and go back to our HTML and refresh. And we're defaulting to all. Let's just check and make sure, yes it is still displaying all five of those sessions. And let's click beginner. And immediately, you can see that only the beginner session is now visible. Let's click intermediate, and now we're seeing the two intermediate sessions, and advanced. We've got the two advanced sessions and nothing else. And back to all, and we're now viewing all of our sessions. So there's how we can implement filtering ourself inside of our component, and notice that the code did take a little bit of thinking through, but it's very efficient code. Nothing changes unless we take the correct action. So no matter what events we're listening to inside of our application, none of this code is getting run unless we take an action that actually applies to filtering sessions. So, in other words, clicking one of these buttons. Now we implemented this functionality by adding code to the session-list.component, and that's the component that's in charge of displaying the sessions. So all of the logic is essentially inside of there. We didn't have to implement that code there. We could've implemented the code in the parent component inside of the event-details.component. It's really up to us to decide which component is responsible for the filtering. Does the event details just pass a filtered list into the session-list.component or does the session-list.component receive what the filter is and then filter its own view. So is this component a little bit smarter, or a little bit dumber? So really the choices are, is as to where we want to add the logic based on which component we feel like should be responsible for the functionality. There's a couple of homework assignments for this section, so go ahead and do those now.
-
Sorting Data
Now that we've got filtering implemented, let's add sorting functionality. I want to add sorting pretty much the same way we added filtering with a couple of buttons. I'm going to put a couple of buttons right here to the left of our existing buttons, and one will be for sorting by votes, and the other one will be for sorting by the name. Those seem like the two most logical was that users might want to view sessions is either by name in alphabetical order, or by vote to see the highest voted sessions first. Now if I'm going to add these buttons, I could already see that I might have a space problem. These buttons here are kind of big, and they're taking up a lot of space, so to add more buttons over to the left as these slide over is going to make this whole thing seem a little bit crowded. So let's make a small visual change. I want to take all of these buttons, and I want to wrap them into a button group. And we'll go with a small group of buttons, so btn dash group dash sm. Refresh our change and that's much more manageable. So let's add the exact same thing, a button group. Again, we want to set the active property, so we're going to bind to class.active, and instead of filtering, we're going to be sorting, so we'll have a sortBy property and we're going to have two potential values, names. So we just use name. And then, that button will be By Name. We also need a click event. And our click event will just set our sortBy property, which doesn't exist yet to the value of name. And we'll need a second button, and this one won't be by name, it'll be by votes. These will be again, magic values, so we got those buttons created. Let's go back and look at our visual display and see if it looks okay. All right, there's just not enough space in between these two groups of buttons, so let's add a little extra space. I'm going to set the right margin to be 20 pixels, and, I think I might want to move it a little bit more to the right as well, so I'll set a left margin as well, of 20 pixels. And let's see how that looks. All right, that's fine. Back to our code. Now that we've got our sortBy property set, we've got to implement that and the code behind. So we just need to set a sortBy property, which is also a string, and we'll default that to votes. And of course we have to pass that into our sessions-list.component. And we'll pass in our sortBy value. And now we can go to our session-list.component, and add this new input property sortBy. So in our ngOnChanges event, after we filter the sessions, let's also sort them. So let's just check and say, all right, does this.sortBy equal the value name, and if it does, then we're going to sort our sessions by name. So again, an array has a nice method already on it called sort. And this is a mutating method, it doesn't create a new copy of the array and leave the original array unsorted, it actually sorts the array in place, which is what we want. But you have to pass in a comparison function. So rather than doing that in line, I'm going to create a new comparison function, and I'll name that sortByNameAsc, and then of course I want to worry about if the value is not named, which means it must be votes, then we'll do the same thing calling the sort method, but this time we'll pass in a different function which we sortByVotesDesc. So that'll be descending sort. Put a semicolon there, and we need to implement these two functions and I'm just going to implement them outside of the components class because they're just sorting functions, they're stateless functions. They don't actually need to be methods of the class. So our sortByName, will take in two parameters., the first session, which is an Isession, and the second session, and it has to compare those two sessions, so if the first session's name is before alphabetically, the second session's name, then we'll return one. Else if the first sessions name is the same as the second session's name then return zero and otherwise we're just going to return negative one, meaning that the second session's name is before the first session's name. This just compares any give two values and tells the array's sorting method how those two values should be in relationship to each other. And our votes sorter is going to be very similar. First session and a second session. And in this case, this is a lot simpler. The way that votes work, is there's a voter's array, so for example, the session s1 has a voter's array, and it's length property is the number of votes. So we can actually very easily compare two different sessions' number of votes by subtracting them, and since we want a descending sort, we're going to return the s2.voters.length minus the s1.voters.length. So if they're the same, if they both have the same number of votes, the second one minus the first one is going to be zero. If the second one has more votes, then it's going to be a positive number. And if it has less votes, it's going to be a negative number, and that's how the sorting functions work. So let's save that, and refresh. And now we're by default, sorting by votes. You can see the session that's on fire is first, and that must be the one with the highest number of votes. If you click the name sorting, then we get an alphabetical sorting of the sessions by their name. A's come before B's. And can go back to votes. And there we go, we've implemented sorting. Again, just like with filtering, just up to us as to where we feel like the functionality belongs. There's a homework assignment for this section, so if you want to, feel free to do that now.
-
Summary
In this section, we talked about various issues of displaying data. We started out by focusing on pipes. Pipes in Angular allow us to display data formatted in specific ways. This is critical when writing an application. Being able to keep the formatting in the client layer is a great way to avoid unnecessary code converting data to and from specific display format. After looking at pipes, we talked about sorting and filtering data. With Angular 1, we did that with filters, but now in order to keep our application's performance, this is something we handle in the component. That puts a little extra coding burden on us, but the payoff is more than worth it.
-
Understanding Angular's Dependency Injection
Introduction
In this module we are going to take a look under the hood of the dependency injection system, and learn how to take more finely grained control of how it works. We are going to focus on some more advanced pieces of dependency injection. We'll start with a look at dependency injection, and how we tell Angular that we want a specific service. Then, we'll look at how we can deal with things that aren't simple services. We can do this using two pieces of Angular, the InjectionToken and the Inject decorator. We'll see how these pieces allow us to register services with the dependency injection system in various ways. While we're looking at that, we'll look at some alternate provider methods, so that we can really take control of the dependency injection system, for those rare situations where we need fine-grained control.
-
Using Third Party Global Services - The Problem
One of the things that our application has been missing for a while, is use of the Toastr Service. If you remember the Toastr Service gives us little notifications that'll pop up in the upper right hand corner, to let us know when certain actions are taken. There's a really good place where we could be using the Toastr Service right now, and that is inside of the profile. Once we login, and I can login here with anything, the server does not care what I put in for username at all, logs in to the John Papa account. You got this profile page, and if we make a change and we click save, it immediately navigates us back to the events place and we aren't 100% sure if our change has actually been made. In this case, we changed the first name so it's obvious because the welcome message has changed. But, if I were to change the last name to Smith and click save, I really wouldn't know, I don't get any feedback that a change has been made. Maybe a better user experience would be, when you click save instead of navigating away, to give us a little notification using the Toastr Service that the change was successfully made. So, let's go into our code, and what we want here in the profile component, is to bring in the Toastr Service as a constructor parameter, which will cause it to be injected by the dependency injector. We can make use of it down here in the save profile format, instead of navigating away we will use the Toastr Service to give us a success message. Let's open up our Toastr Service and take a look at it. That's up here inside of common. This is our Toastr Service right now, and there's a problem with this Toastr Service. This is important to understand, right now the Toastr Service is just a global, in our index file we've got a reference to the Toastr js file right here. The way that this js file works, it's just creates a global variable and puts it on the window object. That's how we access it, is through the global variable reference. The Toastr Service that we created is actually a service that's available for Angular 2 dependency injection. What we've done is we created a class that wraps the Toastr's API, it re-exposes the same methods. There's four of them, success, info, warning and error. Which is fine, because Toastr has a small API, but imagine a big third party component like say jQuery, which had an extremely large API. We wouldn't want to reimplement that, besides the fact that the Toastr object by itself is just fine, it doesn't need a wrapper class. We've had to create this wrapper class, so that we can use it with Angular 2, but it doesn't actually need a wrapper class. If we just add a handle to the global Toastr object, we'd be fine. Unfortunately, just referencing the global Toastr object inside of our code, would be problematic. There is a lot of problems that come from using global objects, one of which is that we lose the ability of ES6 modules to do things like tree shaking and other features that comes with ES6 modules, and it's just a bad practice. Using globals is not a good idea, we want a new way to access this Toastr Service that doesn't involve creating a whole class just to wrap it. Now to make that change, we're going to have to understand some very specific features of dependency injection.
-
Angular Dependency Injection Lookup
Let's start by going back to our profile page. Going down to the profile.component.ts. Let's look at the constructor, and let's talk about this AuthService right here. Now you've learned that if you want to inject something like the AuthService, that we wrote, that you have to import the AuthService from its file. Then, using the constructor, mark it as a parameter and mark the type correctly. This type here is the class, if we were to go into the AuthService, we can see that we're exporting a class off service and this class wrapper, this ES6 class functionality, is just syntactic sugar for a constructor function. What we're getting out of this is just a function. Inside of our profile component, where we inject that, we're marking that parameter with this type. But, what we want is an instance of the AuthService. We don't want the class, we don't want the constructor function, we want an instance of that. We want an already created instance of that. And so, what we want, is for Angular 2 to take the AuthService class, create an instance, and give us that instance. And that's exactly what Angular 2 does. When we register the AuthService class, Angular 2 will create an instance of that class, and give us that instance whenever we reference it in a constructor function and that happens inside the app module. We can see that here in the app module. We are bringing in the AuthService class right down here. We register that under the providers section. What's going on behind the scenes, is that Angular is taking this class and creating an instance of it, for us to get whenever we inject it. And again, if we go back to our profile component, that's what happens. We're getting the instance that it created. Now, the question is raised, how does Angular know that we want the instance of the AuthService... And let's go back to app module. How does it know we want an instance of the AuthService, and not the even list resolver? How does it know? Well it's this marking right here. This TypeScript type declaration. Angular 2's hijacking that declaration and using that as a way to look up the service that we're looking for. Essentially, what it's doing, is it's using the class as a key to find the correct object, in this case the instance of the AuthService that's been created, inside of it's dependency injection registry. In Angular 1, we used strings as keys. You'll recognize this syntax if you're familiar with Angular 1, we would call Angular dot module and pass in module name, like app for example, and then we would call the service or factory functions. The first parameter would be a string, and the second parameter would be a function that returns whatever object we want to be that service in the dependency injector, but the key was this string, my service, whenever we wanted an instance of that service, we would have to create a parameter that had that same exact name. It was a string match up, and of course that came with all the problems of using strings. If you happened to mistype the name, then it wouldn't find your service correctly and wouldn't be able to look it up. Strings were how Angular 1 registered dependencies. In Angular 2 we're using classes, or types. That's our key, or in other words our token. This tells Angular 2 to find the correct object to give to us. So again, what Angular 2 is doing is, it's hijacking this type declaration, this serves two purposes now. One purpose is to type it so that in our JavaScript code we get auto complete. If I type in AuthService and a dot, I get this nice Intelliscence that tells me what the methods of the off service are, and that's because TypeScript is reading the type declaration right here. In addition, Angular 2 is using that type declaration to look up the correct object inside of its dependency injection registry, so it's using it again as a key. Of course this works just great, because whenever we want to create a service, we create a class. For example, when we want to create our off service, we're creating a class, export class AuthService. But, there's a problem with Toastr and that is the Toastr is already an object, it's a global object. We didn't create a Toastr class that we can export and then use as our key inside of our app module. I can't come up here and import the Toastr class, and then add it to the providers array. What I don't want, is this class, this wrapper class, but I still need some kind of a key to give to Angular, so that it can use it as a key to look up the item in the registry. I will fix that problem next.
-
Using Angular's InjectionToken
Let's start off by deleting this class. Now, because of that I don't need injectable anymore. What I want out of the service class is to create a key, or a token that I can use to register with Angular's dependency injector, so that whenever I want an instance of the Toastr class, I can use that key or that token as a way to look it up and find the instance that I want. Now, there's actually a way to do this, Angular provides a mechanism for us to create a key or token that we can use, in the dependency injector, without creating a class, and that is called the injection token. The injection token's job is simply create a token, used for the dependency injection registry, in order to find the instance of the service that we want. I'm going to export let, and then I'm going to use caps. This is a convention that is often seen with tokens. I'm going to call this Toastr underscore token, and that's going to equal a new injection token. Now, the injection token's constructor takes in a type parameter, and that type is the type of the object that is given back for the service. When we use this token to look up the instance of our Toastr Service, this is the type that's given back. We have not created that type yet, we're going to create it in just a second, so I'm going to leave this blank for a moment. Then the constructor takes in a single parameter, which is a string, which is the description. This is just using debugging, it's not particularly important what you put in here, other than that it's a string that you might recognize, so I'll just put in Toastr. What this is doing, this is creating a token that I can then use to look up the Toastr object inside of the dependency injection registry. I haven't registered it yet, I've just created the token. We're going to register it over in the app module, just a second. Now it's important to relay something, even though I'm passing in a string here, this object right here is not a string. This is an actual JavaScript object. If we remember, back in previous sections, when we were talking about identity, if you have objects, two objects that look identical, are not the same object. We've created an actual object and as long as we export this object, and use the specific object to look up in the dependency injector, nobody else can accidentally use the same token or key, in the dependency registry, so we don't get any conflicts. If I were to use a string, then nobody else could use that string with their service. If they accidentally did, we'd get a conflict. But, because this is an object, somebody else can have another injection token that they happen to feed in this same string, It doesn't matter what they name their object, or what they put it in as the string, because it's going to be a different instance of an object. They might look the same, but they will definitely be two different instances, so they'll be different in the dependency injection registry. In our app module, when we import, we're going to import a specific object from a specific service, so here I'm going to import that Toastr token. It doesn't matter if somebody else were to import another token with the same name, maybe do something like this, but from a different Toastr Service, say Toastr 2 Service, and that also exported a Toastr token, we would have to rename that using something like this. We would be using two completely different injection tokens, two different instances of two different objects, so we don't have any name collisions. Now that I've got the token, I can register the service with the Ng Module, but unfortunately I've got to go back into the Toastr Service and I've got to pull this declare statement out right here, because I have to have the Toastr object, this global object, declared in the app module where I use it. Remember what this statement is, this is just a little statement using TypeScript to say that there's already a global Toastr object, so that I can use it inside of my TypeScript and I don't get any errors. I have to pull it out, and I have to move it over to the app module. I'm going to put it right here, and right down here I've got the Toastr service, but this is no longer valid, I've got to create a new dependency objection registration. I'm going to use that token and I'm going to say that whenever we want the Toastr object, we'll just use our token to find it. That's basically what we're doing with all of our other registrations right here. We're saying whenever you want the event route activator instance, use the event route activator class. We're going to be doing the same thing. The way that we do it, is the exact same way that we did it down here for the can deactivate create event provider. We use the longhand provide, and this is going to be the token and then we tell it we're going to use a value. The value that we want to use is that global Toastr object. That is now our registration for the Toastr object. We're telling Angular's dependency injection, that when somebody wants the Toastr object, they have to ask for it using the Toastr token. With the registration completed we can go back to our Toastr service, and remember we still have an issue here, the injection token needs a type parameter. I could of course just say any, and that would be just fine. I'm telling TypeScript that I'm not going to give it any information about the shape of whatever this service is going to return, using this token. It's a pretty simple service, the Toastr Service. I can actually use TypeScript and create an interface for it very simply. I'm just going to paste in this interface that I've already created. It's very straightforward, four methods, each methods has two parameters, the second one is optional, really easy simple interface. Now I can set that as the type of the injection token, and clean up a little bit here. Now I can go back into the app module and right up here where we declare the Toastr variable, I can actually give it the correct type, that interface. I've got to grab it from this import statement, and now I can use that as the interface. This isn't going to benefit us right here, because we're not actually using this Toastr, so we're not doing anything in this file with it other than using it in the registration there on line 50. Later on when we actually consume this service, we can use that interface to get Intelliscence. Finally, we do need to do a little bit of cleanup, because we're using the Toastr Service in one place in our project. Let's save this change, and let's open up events list component. You can see that we're using the Toastr Service here, so let's delete that line. We we're using this click event, but now we're just routing using a routing component. We don't even need that click event anymore, so we can delete that and we can delete the handler for it, and get rid of the construction parameter. And we've got our clean and complete. We'll save that change and close the file down. In our next clip, we will look at how to get ahold of our new Toastr Service.
-
Using Angular's @Inject Decorator
Now that we have the Toastr Service registered, we can use it here in our profile component. Normally when we want to inject a service, we do something like this, let's put these on separate lines, we'd add a new private and then for the Toastr Service, we'd call it Toastr and we would just give it a type. For example, Toastr, and that's all we would need to do, but since we're using an injection token, we actually have to use a slightly more complex method to inject this service. Let's go up, I've got to bring in, a couple of things. First I'm going to need the token and the Toastr interface. That's going to come out of our Toastr service, the token and the interface. One other thing that I need is going to be from Angular core, I'm going to need the inject object. Inject is a decorator just like component, that allows us to use a separate token besides the type of the construction parameter. So again, here for the AuthService, the token is right here, and that's also the TypeScript type. In our case, the TypeScript type is just the Toastr, which is our interface, but that is not our injection token. Instead, I'm going to add that decorator inject, and it's parameter as you can see is a token. I'm going to use the Toastr token here, and that tells Angular, for this Toastr variable, that we are creating, that is going to be a private member of this class. You're going to get your value by using the Toastr token to look up the service in the dependency injection registry. I'm still giving it type information, but that's simply for the Intelliscence here inside of this file, and that's all that's going to do. And now, with the service injected, I can go down to this save profile and instead of navigating, we're going to call this dot Toastr dot success and give it a message of profile saved. I'll see that change, and now let's go back to our webpage. We'll go to the all events and refresh, and we got logged out, so let's login. We'll go to the profile, and now let's make a change and click save, and there we get our Toastr popup telling us that the profile was saved. Of course, it disappears after its timeout. That's how you can use the Opaque Token to register dependencies and avoid difficulties of using things like strings for keys. There is homework for this clip, so you can go ahead and check that out now.
-
The useClass Provider
In our last section, we talked about the Opaque Token and using that when registering this provider, the Toastr Service provider. One of the things that I kind of glossed over, that I want to talk about a little bit more in depth, is how this syntax using the object is a little bit different than this syntax right here. The reason for that, is that this syntax where we simply pass in a class, this is just shorthand for using the object syntax, like this. We can replicate that with the following, so what we've got here is the same type of syntax. We got a provide key in this object and instead of use value that we used, because we already had an existing object, we're saying use class. Again, this is our token, is the class self, and we're saying whenever somebody uses this token to look up something in the dependency injection registry, I want you to actually use an object created by this class here. So, use the event route activator class to create an object, and return that whenever somebody asks for an object using the event route activator class as the token. So, it's longhand and we just shorten it out by just removing all the syntax and just passing in the class name itself. Now, because that longhand works and it actually has two different values, so let's use it on this off service, we can actually sort of do some funky things by saying use class, but instead of passing in the off service class, I can pass in something entirely different, like event service. And now, whenever somebody asks for an off service, they're actually going to get an instance of the event service. Of course, that would be really bad in this case they don't have the same APIs, you would never want to do something this weird, but you might want to utilize this functionality if you're doing something like, you have a very specific kind of implementation of the class, but you have a generic class you're using. Say we've got a logger class, and that's what people are going to ask for, but we actually want to use a file logger specific implementation. Of course those classes don't exist, so that wouldn't work in our application, but that's an example of where you might want to utilize the longhand syntax. Let's just reset this back to the way that it was, and that's the use class syntax for registering providers.
-
The useExisting and useFactory Providers
Now we've looked at the use value way to provide services, and also use class, which is the longhand for what we normally do with services, when registering them as providers. There are actually two more ways to register providers, that we're going to talk about in this section. We're not going to look at very concrete examples of these, because it's really unlikely you will ever need to use either of them, but they exist and so it's good to know about them. One of them, is called use existing and it looks like this. This is also known as the alias provider, and there's a fairly limited set of scenarios where you'd want to use these. One, that comes from the documentation, is to minimize an API. Say, for example, you had a logger service and that logger service was a big service that had a very large API, maybe it had 20 or 30 different methods in it, but in your application you're only using the most common five. You create a minimal logger service, and what that does is, now you're getting the API for the minimal logger, and of course it has to be a class, this cannot be an interface, the minimal logger must be a class. It's never going to have an instance created of it, because whenever somebody asks for a minimal logger, they're going to get an instance of the logger. This would be the kind of class that has no implementation, you would just create the methods just for the API. Whenever they ask for minimal logger, they get the logger, but the only methods that they can see, in IntelliSense are the three year, two or four methods that are on minimal logger that you actually want to use. That's one example, it's really not a common case, not something you're likely to use, but it is something that exists and that is this use existing provider. The last one is used factory, and this is even more complex. In this case, what you're going to do, is you're going to register a class. Say, you're minimal logger... Well let's change this to something simpler like logger, but what you're going to do, is you're going to give this use factory parameter, a function that is a factory, okay? You can actually call the function and pass in some parameters. What that does, is it allows you to parameterize the creation of an object, this is really only used to very complex cases, all of which would be on the scope of this course. If you need more information, you can check out the official documentation. That is the basic usage of the use factory. Again, you're unlikely to run into a situation where you need it, but if you do need to have a very complex way to construct an instance of a class, to use as a service, then use factory is the way to do it. There is a practice exercise for this clips, so you can go ahead and do that now if you wish.
-
Summary
In this section, we learned how to register other kinds of services, with the dependency injection system. We do this using a combination of the injection token and the inject decorator. We saw how we can use alternate provider methods to register these services. These methods include, useClass, which is the longhand form of what we normally do, but can be used to enable some advanced scenarios. The useValue is the next most common method we use. This lets us add an existing object to the dependency injection system. Finally, we have useExisting and useFactory, both of these are for advanced scenarios, that we aren't likely to run into, in a normal application. The dependency injection system is complex under the hood, but with some basic understanding, we can do some common tasks, that will help us out in various situations.
-
Creating Directives and Advanced Components in Angular
Introduction
In this module, we're going to learn how to create directives and deal with advanced components. Our focus will be on learning more about components, and how to deal with other scenarios that we haven't seen before. And also how to create directives, which we don't create a lot of, but are useful to be able to do. You should feel comfortable with components by now, so we'll push that knowledge, seeing some new scenarios. We're going to create a modal component. While we do that, we're going to learn some new things. For example, we'll learn about routing to the same component. In Angular, there are a few things to be aware of when routing from a component to itself, and we'll see how to handle this. We'll learn about DOM manipulation and how to reach down and interact with the raw DOM when necessary. We'll also learn about using the @ViewChild Decorator, and some similar decorators in order to get a handle on the underlying DOM node. We'll also be creating a directive in conjunction with our modal component. We don't often need to create directives, but it does happen often enough that we want to know how to do it. Through this module, we'll really get a handle on components and their counterparts, directives.
-
Implementing the Session Search
Since this section is about advanced components and directives, we're going to be adding some more interesting functionality to our application. One of the pieces of missing functionality we've got is this search box up here in the NavBar. We'd like to implement that in this section. We'll do a few other things as well, but we're going to start by implementing our search box. This searches sessions, so it's not looking for an event. We've got the events list, so let's just find an event. What we want to do is find a specific session within an event. Maybe we know that somewhere there's a session about pipes, and we're looking for that specific session. Or we just want to look for any session about pipes. So, if the user types in pipe and hits enter or clicks the search button, we want to pop up a list of search results. And from that popped up list, we'll let the user click on a specific session and navigate to the details for that event. That's the functionality we're going to add, and to implement that, we're going to use Bootstrap's modal dialog, which is actually added into jQuery, which means we got to have both jQuery and Bootstrap in order to do this functionality. We're simply going to write the Angular 2 wrappers around that functionality. That'll let us avoid a lot of the complexities with implementing modal dialog boxes, but it's still going to be a fair amount of work on the Angular 2 side. Let's start by hooking up to the event. We're going to go back to our code, and we're going to go into the NavBar component. And down here where the search button is, actually the search form is what we're looking for, we're going to add a new event handler to this form. Forms in Angular 2 have a submit event that's actually bound to ngSubmit with a capital S. That's the event. It's a custom event that exists on the form. It's added in by Angular 2. And we're going to hook up to that. What's nice about listening to this event is it doesn't matter if the user hits enter while in the input box or if they click the button, it's still going to fire the submit event. And we'll call a method called search sessions. And we want to pass in the search term that the user is looking for. I'm going to put in a variable called searchTerm that doesn't exist yet. And now, let's create that search term. That's going to be whatever they type into the input box. So, in order to do that, we want to hook up our input box with an ngModel, so we're going to use a banana in a box and the ngModel, and that will be hooked up to search term. We'll have to give it a name, of course. And now, with those in place, let's click save and we'll go into the component. Now we've got to add those properties to the component. So, we need our search term, which is a string. And we'll initialize that to an empty string. And we're also going to need a search sessions method, which I'll create right here after the constructor. That takes in that search term, remember. And ultimately, our search is going to return to us a list of found sessions, so I'm going to need a found sessions property as well, which will be a collection of ISessions. And let's import the ISession interface. And now, we need to think about implementing our search sessions method. Obviously, we don't want our component itself to be doing our search. We want a service to handle that. So, we're going to use the event service. Seems like a natural place to put functionality like this. But that also means that my NavBar component needs to have a handle to an event service. So we'll have to import that, and now we can add it to the constructor. And once that's in place, we can call it inside of our search service method. And we want to call a method that will search through the sessions. That method doesn't exist yet, but I know what it's going to look like. I'm just going to call it searchSessions. And it's going to take in that same term. And I'm going to make this return an observable. Since we have our data locally, I could just return the data synchronously, but in the next section we're going to be adding in HTTP, and in a real application, you probably wouldn't have all your data locally. You'd probably be making an HTTP call for something like this, so an observable is fairly appropriate. So, we'll subscribe, and we'll probably receive back a list of sessions. And once we get back the list of sessions, we'll set our found sessions variable equal to that returned list of sessions. That's how the call will look. Now, we need to go into the event service and implement this new method. I'll save and open up event service. And let's just put it right after updateEvent. Remember, it takes in a search term, which we know is a string. Let's lowercase that, just to make it a little easier, so that no matter what case the user types in, it will be consistent. And we'll need a list of results that'll start out as an empty array. And of course, those are ISessions. And we'll initialize that to an empty array. We don't have the ISession, and we don't have the interface brought in, so let's go up and import it. And now, to find the correct session, we'll have to look through each of our events. And we'll grab a list of matching sessions from this event. We do that by calling event.sessions, and we filter that down to a new list where the session's name ,.lowercase, contains the term we're looking for. So we can just do index of term is greater than negative one. And now, since sessions don't actually contain the event ID that they belong to, I'm going to add in the event ID to the session. That'll be necessary because we're just getting back a list of sessions. When they click on a session, we need to go to the corresponding event, and therefore, we're going to need the event ID. But we're not bringing in the event along with the sessions. We're just getting the session itself. So, let's add in the event ID by mapping our matching sessions that we've just created. And we're going to specifically map the session to an any, so that we can add in the event ID. And we'll set it to the event's ID and then return that session. And that's a quick and easy way to add the event ID to every session that we've created. And now, we're going to take that results array and we're going to add to it using the concat method, this matchingSessions. Now we've got our results, and we want to return those back, but remember, we want this method to work when we move to HTML, and have the same kind of interface, which is going to be an observable. So we need to return an observable. And there's an easy way to do that. We can use the event emitter. So we'll create a new event emitter. We'll pass in a true, which will tell this emitter to deliver its events asychronously, and we don't have the event emitter importing yes, so let's go back up, at the very top, and it's an @angular/core. And we'll add an EventEmitter. Back down to our code. Once we have the event emitter, we're going to set a little time out just to kind of simulate what it would be like if we were actually using a web request. And we'll have the emitter emit the results, and let's time that out for 100 milliseconds. That'll simulate HTTP a little bit, and then we return the emitter, and now that we've written the methods, we want to see if they work, but I don't want to worry about hooking up UI quite just yet. So I'm going to go back to my NavBar, and I'm just going to log out to the console the sessions once we find them. We'll move that later on, but for now it's a quick and dirty way to find out if we returned the sessions that we were looking for. So let's save this, and we'll go back to the web page for our site, refresh it, and now let's put in some kind of a term. Let's look for pipe and hit enter, and look at the console, and we got an array with one item in it. And that item has an abstract, or a name, excuse me, is what we're looking for, of using Angular 4 Pipes. And that's what we did in our method. We searched for just the matching term in the name. Now we've got our search implemented, and we can now begin to hook up some UI.
-
Adding jQuery
Now that we've got our search results coming back, we need to display those search results, so we're going to start hooking up the UI. This is going to take a little bit, because it's not a trivial matter to hook up this modal dialog and wrap it correctly in Angular 2. As mentioned before, the Bootstrap modal dialog is part of Bootstrap, but it's actually utilized through jQuery. So we're going to need jQuery, which means that we've got to now have jQuery inside of our application. And jQuery is a third-party global, which means we got to do the same thing we did with Toastr. We've got to create a wrapping service for it. So let's start out by creating a wrapping service file, and we want to do pretty much what we did with the Toastr wrapper. Let's create a new file here, and I'm going to call that jQuery.service.ts, and I'm going to pretty much copy what we have in the Toastr service, except I'm not going to get the interface. jQuery's interface is far too complex to recreate. Let's change this. Let's set this type grammar to Object. At least it's slightly better than any. We're going to change the token from Toastr Token. We'll call this JQ Token. And of course we don't want the string to be Toastr. It'll be jQuery. And that's all we're going to do in this file, and now we've got a couple of places inside of this directory that we're exporting, so let's create a barrel file. Index.ts, and we'll export from the jQuery service and from Toastr, and from collapsible-well. We'll save those changes and go to our app module file. We need to do the same things we did with Toastr, but while we're in here, let's fix some AOT compilation problem. You may have already fixed this on your own. Change this, get rid of the declare. Let Toastr equal window toastr, and get this same thing for jQuery, though we won't give it a type, and we'll grab the $ variable. And up here where we're importing, we're going to import from our new index file. And from there we can grab the JQ TOKEN, the TOASTR TOKEN, and the Toastr interface. And we can delete that line now. And we can also get the collapsible-well from the same place. And now we've successfully got jQuery imported and wrapped, and we'll save our changes. And in the next section, we will start creating our modal.
-
Creating a Modal Component
With jQuery created, we can now begin creating our modal component. I want to know what that modal component's going to look like first. I'm going to go into the nav, into the HTML. Here is where I'm going to have the definition for my modal, from an HTML standpoint. We're going to use content projection as we learned previously. So we're going to have some kind of a modal component, and within that I want to put in the contents and display of the search results, so let's just look at that first, and we'll come back to our modal tag in a second. We're going to give this a class of list group, and for each item we're going to have an anchor tag. That's going to be a list group item, and we use an ngFor to repeat over our results, and it'll be a session, and that's the found sessions variable, remember, is getting filled in, and now we also need to link to the specific event, so I'm going to use a router link, and we're going to bind to our events path, and the second part is the ID, and again, this would translate to a URL of events/the session ID, so if it's Id3, would be event/3. And let's close up this tag, and now we want to print out the session's name, so I'll use just a plain binding, two open curly braces. Notice that it looks like there's only one open curly brace, but the second one is on the line before, the very last character, so don't let that confuse you. It's just the wrapping that's making it look different. And then we close up our a tag, close up our div, and then we will close up our modal tag. Now, calling this modal is probably not the best idea for a tag name. It's a very simple little modal component, so why don't we call it simple-modal, and I'd like to be able to set the title on my modal display. However that display looks, I'd like there to be a little title up at the top, and I want to set that, so let's add a title attribute, and we'll set it to Matching Sessions. Now one more thing I want to talk about before we begin implementing this component is this URL in the router link. Since we're not starting with a leading slash, this would be a relative path, and since our search results are available at any place in the application, because they're in the nav, we might already be down in some subdirectory, and that wouldn't work out for us, so we'll want to start from the route, and to do that we're going to need to add a leading slash, so that way, no matter where we're at, it's going to go to the root of our website /events/event ID, and let's save the changes here. And let's begin implementing this simple-modal component. I want this to be in the common directory, because it's a component that I might use anywhere in my application, so a new file here, and I'm not hyphenating the simple-modal. I'm using Camel Case here. A lot of times you'll see the recommendation is to use a hyphen, simple dash modal like that, and I have a personal preference to not use the hyphens, but either way you want to do it is just fine. And to start off with of course, we're going to be creating import component, so let's import component, and be sure and give the casing right on your import statement, and now let's create our component. We'll give it a selector, of course of simple-modal. And a template, and here in the template, we're going to need a div, and I'll give it an ID, because using the Bootstrap modal component, you have to know the ID of the modal itself, and well just give it an ID of simple-modal. And we give it a class of modal, which actually tells Bootstrap and jQuery that this is a modal, and we want to class of fade, so that when it closes it'll fade out, and a tab index of negative one, and inside of that, we have another div, which has a class of modal-dialog. This is for styling. And then within that, a div with a class of modal-content. Again, for styling, and within that, a modal header with div, and inside the header, I want to close button, so I'm going to create a button, of type-"button", and I give it a class of close, and I'm going to add a data-dismiss tag, to say that I want to dismiss my modal, and then within that, I want a span that shows a little x, so I'm going to use the ampersand times HTML character, and then close my button, and because of the styles, that button will be shifted over the right. I also want an h4 with a title in it, more styling classes there. And we'll bind to that title property that we set inside of our NavBar HTML right there. Close up that div, and we'll add another div for the modal body, and here is where we're going to project our content, so I'll just add in the ng-content tag. Close up this div, and that div, and another, and finally our last, closing div. So there's our HTML. Now we've got to style this up a lot, so I'm going to include some styles in this component. I want these styles to apply to this component only. And we're going to add a modal body with a height of 250 pixels, and an overflow-y of scroll. The rest of the styles are specified in the Bootstrap CSS file. Now we've got the styles for our component, and we can create the component class itself, and that has one input property, which is the title, which is a string, and let's go up and bring in the input, and that will be all that we need for the modal component itself. We're still not actually calling an opening of the modal component, but we'll do that in the next section, so let's save this, and go into our index file, and add that line. And again, you can see here, the recommendation is often shown to use a hyphen to separate words. Here we've done Pascal, so you can make your own decision. Do you like the hyphenated better? Do you like the Pascal case better? I myself have a small preference for Pascal case, but the hyphenated case is starting to grow on me. And with that in the barrel file, we can go into our app module, and add in our simple-modal component, and with that we can save our changes, and then we of course add it down to our declarations. We're not yet using jQuery, but we will be in our next section, so let's go ahead and add jQuery to our providers list as well. And with that we'll save our changes.
-
Fixing Template Parse Errors
Now we still haven't yet completed our functionality. Our modal is still not showing up when we search. We're going to add that in our next section, but what we can do is test to make sure that we haven't broken anything. So let's go back to the browser, and we'll open up the console, and we're going to hit refresh, and uh-oh, we're getting an error. Template parse errors is what's showing. Now you can open this up, and start drilling around into the call stack as to what's going on, but it's really not going to lead you to anywhere interesting. So this is one of those times where just knowing that template parse errors means that Angular 2's having a trouble parsing one of the templates for one of your components, so let's go back into our code, and the likely place that we've got a template error would either be in the NavBar, where we added in the simple-modal, or in the simple-modal component itself. And we could do a whole bunch of diagnosing this by taking sections of HTML and deleting them and then refreshing, and when we do that, we're going to come and find out that we closed our span here incorrectly. Here's the open span tag and the closed span tag. And I would diagnose something like this by just deleting a section of HTML, saving, refreshing the browser, and seeing if the error went away, and it would lead me to this line of code, so let's fix that with a close span. Let's save, and then go back and refresh our browser, and now everything's back to normal again. No more errors.
-
Creating Directives - The Trigger Directive
We've got our simple-modal component created, but now we need to show it when somebody searches. So we need a trigger and a way to open up that modal. The way that you open up that modal in code, Let's just go and do a random piece of code here, would be with jQuery, and in jQuery you call the jQuery selector. You have some DOM element that you're selecting inside of here, say an ID, and then you call the modal method, and that's essentially how we open up the modal. So we need to call this code when the user searches, so when they click the search button here. Well, what we don't want to do is go into our NavBar and go into this button and add a click event handler to this and say, hey, when the click event happens, then let's call that code. You know, we put it into a onSearch method. And we would call that modal and open it up. That's not what we want to do for one very big reason. That would tightly bind this NavBar component to the implementation of opening up a modal, right? I mean, this is really under the hoods, is having jQuery call its modal method, which is only there because Bootstrap is also part of what's getting loaded. So we don't want that. We want to hide these implementation details behind something else, and a great way to do that would be a directive. So instead of this, let's just undo these changes here, we'll go into our HTML, and what we want is on this button, we want to say, hey, when you're clicked, you are a trigger for my modal component. So maybe we could give it an attribute like modal-trigger, and that's an indication to Angular 2 that this button needs to trigger the modal. And if you know Angular 1, you'll know that we can do this with what's called a directive. In Angular 2, it's actually the same name. A component is an element, such as this right here. A directive is an attribute, such as this right here, and with a directive, we can attach new functionality to an existing DOM node. So this button, we want it to have another piece of functionality. We want it to be a trigger for our modal. So we're going to create this directive right now. It's going to be called our modal trigger, so let's go into the common directory, and let's create a new file, and we'll call it modalTrigger.directive.ts. And of course we're going to have to do some importing, and we're going to import first from @angular/core, and normally we would import component, right? What's what we would normally import here, but we're not creating a component. We are creating a directive, so we're going to import {Directive}. This is a decorator that Angular provides that lets us create directives, so it's got that import. It will create the directive. We create it very similar to how we create components. Just like a component, it has a selector, but unlike a component, we don't just feed it a simple string, so for example with our simple-modal, it was simple dash modal, that's not what we want. Our selector is modal-trigger, but it's an attribute, so we use the common CSS selector syntax, and wrap it in square brackets, indicating that this is an attribute, not an element. And now we're going to export a class, and we'll call it the modalTriggerDirective, and what we'll want to do is when this directive is created, we want to attach a click event handler to whatever element it's created on. So let's bring in the OnInit interface, and we'll have our directive implement that. And we'll created the ngOnInit method, and inside of here, that's where we're going to call that modal function off of jQuery. So what we'll need here is we'll need jQuery inside of this class. We want to inject it into this class. So let's create a constructor, and we're going to bring in jQuery. In order to do that, of course we have to import that jQuery token that we created earlier on. And that's just a sibling file. And in order to inject that, we also need the inject function from @angular/core, and then we just call @Inject, passing in the jQuery token, and we'll create a private variable, the dollar sign, and we'll just assign this to any. Because the API for jQuery is really rather complex. An empty body for the constructor, and then our ngOnInit, we can now call this.$, which is the jQuery function. And we'll pass in the ID of the modal that we want to open up, which we defined not here. We defined it in our simple-modal. It's this ID, just simple-modal. So we use the hashtag simple-modal, and we call the modal method, and we have to pass in an empty object because it expects a configuration object. We're not going to give it any actual configuration parameters, so we pass an empty one. And that will open up the modal, but we're calling this OnInit. We don't want to call this WhenOnInit. Instead we want to listen to the click event. So what we need to do is get a handle to the element that this directive is on. And listen to its click event. So in order to get that element, we need to bring in an element ref, which is Angular 2's object, which is a pointer to a specific element. And then our constructor, in addition to jQuery, we also want to inject our element. So I'll so el, and the type is ElementRef, and what this does is it tells Angular when this directive is constructed, I also want to reference to the element that it's on. So again, down here in our NavBar, this directive is on this button element, so that's ultimately the element that we want. But we don't want the element ref, because that's kind of a wrapper for the DOM element. We want the actual DOM element. So, we're going to create a new property called el, which is an HTML element, which I haven't imported that. This is just a global Java script type. And here inside the constructor, I'm going to set this.el equal to the injected el.nativeElement, and maybe my naming is a little bit poor here. This el and this el are kind of confusing, so let's rename this. We'll just call this ref, and unfortunately our wrapping has brought this curly brace down onto another line, so let's get rid of this blank space. We don't need that. Okay, so we've got the native element now, and we can now add an event listener to it, which we do with this.el. Of course we're doing this inside our InIt, because we want this to happen when the directive is emitted, and it's just addEventListener. This is just plain old Java script in the browser. We're listening to the click event, and that takes a function as a second parameter, which receives an event object. We don't really care about the event object. We just want to call our modal whenever that button is clicked. Now of course, we need to save our changes here. And in the HTML file, we need to go into the index. We need to add that new modal trigger. And then we need to go into our app module, and bring in that trigger directive, and also add it to the list of declarations. Directives go under declarations just like components do. I'll save that, and now we can go back to the browser and refresh the page, and if I type in pipe, and click search, we get our popup, our modal dialog that shows us what sessions match that search string. If I close this modal and search for something else, like Angular, you can see we get a lot more results. So our modal dialog is now working, and if we were to click on one of these sessions, you can see that we've actually navigated to that session. The URL's changed to event/1, but before we finish up, we're still logging the results out to the console here. So let's fix that, go back to the code and into our NavBar component, and we'll take out this console.log. And there we go. We've created our directive. There is a practice exercise for this clip, which you can do now.
-
Binding an ID
Now even though our new modal component with its associated trigger directive are now working, we do have a few enhancements that we need to make. So the first enhancement that we're going to handle is the fact that there's a weakness that because we're using an ID, and in our trigger, we're saying, hey, open up this modal with this ID, we could never have two different modals in the same application. We could only have one modal, which is a weakness, right? This is a generic modal dialog box. We'd like to be able to use it multiple times. But right now we're fixed to just one, because this ID is here, so if we place more than one on a given page, we'll have two elements that have the same ID, and therefore we're going to have a problem. So let's fix that. Let's make the ID customizable. So what I want to be able to do is tell this component what its ID is going to be. And I'd like to do it like this. I'd like to go into the NavBar component, and where I've created the simple-modal, in addition to the title, it'd be nice to just set its element ID, and we'll just call this one searchResults, but an entirely different modal that we might create, say maybe on the NavBar, maybe somewhere else, it might have an element ID of say, userList. Whatever. So here's our property that's set. Since this is a component, this is an input property. Let's go back to our simple-modal, and we're going to create an input property for the element ID, and now we've got that input property, and we can bind it here in the template. So instead of setting this to simple-modal, we'll bind this to elementId. Now we've got that set up, so that the NavBar's template is setting the element ID, and the simple-modal is using that element ID that it's given, but the trigger is still hard coded to this value. So we need to tie these two together. Now there is an easy way. I can't just globally select for that somehow or somehow inject this into the modal trigger, but what I can do is go back to my template where I'm defining the modal trigger, and I can pass in the same ID as the value of the attribute. And this is actually really nice, so here I've set the element ID to searchResults, and here, the modal trigger, I'm saying, you are the modal trigger for this particular modal dialog box. That's really great because now on the HTML, it really tells me what's going on and it adds that sort of binding between the two things, so if I have an entirely different modal dialog box down here with a different element ID, then somewhere else I have a different trigger set to that ID, I can tell which button triggers which modal. Now when we created this attribute, the element ID, we just created an input property. Here, this is a directive, not a component, but we implement it the same way. We go into our modal trigger, and we are going to add an input property, so we got to bring in @Input, and the name of this input is the name of the attribute, which is just modal-trigger. And fortunately, modal-trigger isn't going to work as an identifier name, because dashes aren't allowed. So we've got to alias this. And there's an easy way to do that. Take this, and we put it inside the input as the parameter, saying hey, this is the variable that's coming in, or this is the attribute value that's coming in, but I want you to assign it to a property named, and we'll call this modalId, which is a string, and now that I've got this modal ID, I can use that instead of the hard coded string simple-modal. So, instead of con cat ______, let's just do a little bit of fun, turn this into an ES6 interpolation string, and we use this.modalId. So now our resulting string is going to be hashtag, and then the modal ID, and let's save our changes, and in the template, and we can go back to the browser, and refresh, and let's search for pipe again, and our modal dialog is still working. We can still use it to navigate to the right session.
-
Routing to the Same Component
The next feature that we're going to add is to actually fix a bug. Now this is not a bug in the modal dialog component we created. It's the modal dialog that's exposing a bug that has already existed in our event details page. Let's look at the bug first. Then we'll talk about why it exists and how we handle it. So I'm going to search again, but I'm not going to put in any search term. I'm just going to click the search button, which will search all sessions. We can see I've got every session here, so I'm going to click on the first one, which is a session for the Angular Connect event. And you can see the URL is /event/1. Now if I click on the same one, it just stays there. That makes sense, because it's still the same event. In fact, all this first set of sessions belongs to the event that has ID of 1. So let's go down to the bottom. These sessions down here belong to a different event. So I'm going to click on one of those, and you'll see at the top that the URL has changed. Notice that it's now /event/4. Yet, we have not navigated away. We are still looking at Angular Connect. So we are still looking at the event that has an ID of 1. If I were to go up here and just hit enter, and navigate to the fourth event, we can see that it's actually the UN Angular Summit. The event with ID of 1 is Angular Connect. The event with ID of 4 is UN Angular Summit. Yet, when we try to navigate away, like I just did right now, the URL changes, but the event does not. This is a bug in our event details component, and let's talk about what's going on. We'll go look at our code, and we're going to look at the event details component. I'm going to close these down. Here in events, in details, the event details component, down here in the OnInit, we set the current event to be equal to the event service getEvent, but we're passing in this.route.snapshot, and then the parameter 'id,' so the ID parameter from our route. That's great, the first time this component initializes. But when we navigate from /1 to /4, what Angular does not do is reset the entire state of the component, and reinitialize it, reconstruct it. No, it stays there. It leaves the same component initialized. Instead, all that happens is that the param for the ID changes. It's taking advantage of the fact that parameters for your route are actually exposed as an observable. Here we're using the snapshot, which is not an observable. We've told it, just grab whatever the current route's parameters are, create a snapshot of that, a fixed copy, and we'll use that, but we're not subscribing to any changes, so if that ID parameter changes, we don't know about it. And that's the bug that we've got to fix. That's how Angular 2 does its routing, When you're on a parameterized page, so a page that like our event details page, that has an ID and the URL, and whatever is displayed changes based on this ID in the URL. If we can navigate from this page to itself, to a different ID, then we got to listen to the route parameter subscription. That's the way we deal with that in Angular 2. Now it didn't matter when we were going from all events to any given event, like 2 is different than 1, because we're navigating from a different component to the event details component. But when we're in the event details component, we navigate to itself just a different URL, then the route parameters are changing, and we're notified of that through an observable. So, in order to fix this bug, we've got to subscribe to the route parameters, so we're going to completely change how this code works. I'm going to put the code right here so we can refer to what the code used to look like. Instead of setting this.event equal to something, we're going to class this .route.params.forEach, and we're getting in a params object, which is of type params, and we have it inject that params type, so let's go up and inject that. That's actually on the router, and the callback function will actually do the setting. This.event will be set equal to this.eventService.getEvent, and here we can just call params 'id,' but we want to make sure and convert that from a string to a number, so we're going to use our little shorthand version. And now we can get rid of this code, and if we save our changes and go back to the browser and refresh, we're now going to get correct functionality. If I go down to a session for a different event and click on it, you can see that we've actually navigated and are displaying the data for the correct event. Now it's important to understand how this works and what's going on. We are not resetting this component, so therefore its state is not getting set. Here I'm resetting the event property, but this component actually has more state than just event. For example, the adMode. That's another piece of state. You can see if I go in here and click Add Session, I'm now in the adMode. So adMode is set to true. If I were to search, and go to one of the sessions for a different event. I've now navigated to the different event, but I'm still in adMode, which is probably not what I want. So in addition to setting the event, I probably need to reset back to my default adMode, which would be false. So it's very important to understand that whenever you are subscribing to the route parameters, and basically using that as navigation to a different page within the same component, you need to keep track of all the different pieces of state that exist in the page, in this case adMode. It's often easy to tell just by going up here and looking at what properties exist. Normally your state's going to be in these properties, so the event is a piece of state. AddMode is a piece of state. How we're filtering and sorting, those could also be pieces of state as well. We might want to reset those as well. I'm not going to do that, but again, that could be something that you might want to reset. So it's important to keep track of all this state, and reset it whenever you subscribe to and react to a route parameter change. And of course we could be resetting so much state that instead of calling this code, we might create new method called set state or reset state, and put the code into that so it looks a little bit more descriptive. So let's save our changes, and verify that our fix worked. Refresh. I'll turn on the adMode, go to a different event, and you can see adMode is now off. There we go. We fixed our bug, and we've learned how to deal with routing a component to itself. There's also a practice exercise for this clip. Feel free to go ahead and do that now.
-
Using the @ViewChild Decorator
In this section, we're going to address the fact that when you open up the modal dialog, and click on a session, it doesn't close the dialog down for you. So what we want to do is add a click event handler to this main body of this dialog box here, and listen to that, and close the modal when that's clicked. So we'll go back to our code, and it's right here in this modal body. That's what we want to listen to. Also there's a mistake here. This is supposed to be modal-body, which should also fix some styling issues. We want to listen to this div, and whenever the user clicks on it, we want to close the dialog box. So, let's implement a click event listener here. And we'll just call it closeModal. And of course we're going to need to implement that method, and inside of here, ultimately we're going to call that jQuery method modal, and we'll just pass in the string hide, and that will close down the modal dialog, but we've got to pass in the actual DOM node, the raw DOM node, that is the modal, which is going to be of course this div right here, that has the modal class. So how do we get a handle to that? Well, we could do the same thing that we did in modal trigger. We could get our element ref, and just get the native element off of it, and that would be fine, but we're going to look at a different way to get access to that same DOM node. And this is through using a view child. So I'm going to go up to my @angular/core, and I'm going to import ViewChild, and down here, I'm going to call that, it's a decorator @ViewChild, and you pass into the view child a string that indicates an Angular 2 local ref variable, so you may remember previously that if you want to refer to a specific element, you can put in a ref like this. These refs are available inside of code, so I could for example pass it into my closeModal, but it's also available as an indicator for a view child. So if I pass in the string modalcontainer, and I'm going to call this my container element, which is an element ref, and I need to import that element ref type for @angular/core. This object here is going to be initialized with this component to point at this specific DOM node. Again, it's just a wrapper to that DOM node, but it's going to point to it, so it's kind of the same as injecting an element ref in the constructor, as we did here for ModalTrigger, but it's a little bit more versatile, because we can put this ref on any node that we want to, and it'll just give us a handle to that node. In this case, we just want the wrapping node, so that's fine. It's going to be the same either way, but we're going to implement it using a view child, and that'll show us how those work. So I don't have jQuery yet. I need to import that as well, and we'll have to create a constructor. @Inject(JQ_TOKEN), and we didn't import inject, so let's import that. So now we got jQuery, and of course it'll be this, and now we want the raw DOM element that this container El wraps, so we call this.container.El.nativeElement, and we're getting the underlying DOM element that this container El points to, which is found by looking up the ref for modalcontainer. So that's another way of accessing a specific DOM node. And it's a much nicer way to get ahold of a specific DOM node, rather than getting the parent element ref, and then perhaps drilling down through something like getting elements by class name, for example. So let's save our work, and we'll go back to the browser and refresh, and now if we click on a session, it does close the modal dialog for us. Now, in addition to view child, which we're using here, there's a few more variations on this. There's view children, R-E-N, which we didn't import, but is one of the options. View children would be used in case you have a list of elements that all have the same ref, for example, if we had an ngFor that was iterating over a list of items, and we wanted to get a reference to that entire collection, we would use view children for that. Then, if we wanted to access a child that is inside the content, that's projected into this component, then we wouldn't use view child. We would use content child. Now, the problem with content child is you have to trust that whoever implements the content that's getting projected is going to put in the elements that have the ref on them. So if they don't do that, then your content child is going to be empty. And in the same way that view child has view children, content child has content children. And that does the same thing as view children. If the projected content has a list of refs that you want to get a hold of, then you would use content children. And so those are ways that you can get access to a specific DOM node, without starting at the top and drilling down. And there is a practice exercise for this clip, if you want to do that now.
-
Creating Settings on Components
Now the feature that we've added to our modal, where the dialog box is closed if somebody clicks anywhere in the body of that dialog box might be a feature that you don't want in all cases. There might be times where you have a modal dialog box that you don't want it to close when somebody clicks in the body. Maybe it's not displaying a list of links anymore. Maybe it's just displaying some information. And you want to be able to click a button or something like that, and not the dialog box close. So let's make this a setting. And what we want it to look like inside of our Nav HTML, where we define the modal, it'd be nice if we could just say, closeOnBodyClick="true", and now it's going to close the dialog box if we click on the body, but if we don't include this attribute or set it to anything else, then the dialog box won't get closed when we click on the body. So let's save that, and we'll go into our simple-modal, and we're going to add another input property, and that'll be closeOnBodyClick, the attribute that we just created, and that's a string, and then now all we have to do is go down into our close modal method, and say, if(this.closeOnBodyClick, and let's lowercase that to make it easy, and we'll compare it to the string "true," and if it's true, then we will close it, and if it's false, we won't. So let's go back and change this to false, and go to the browser and refresh, and we open up the modal dialog. If we click in the body, it's not going to close it, even though it still does navigate, but if we set this to true, and try it again, then it does close the dialog box when we click the body.
-
Summary
In this module, we created a modal component and an associated directive. We saw how we can create more advanced components and directives, and how they can work synergistically in order to create a good developer and user experience.
-
More Components and Custom Validators
Introduction
In this module, we are going to continue our work with components and also learn how to create custom validators. We'll start by creating a voting component. This component will have a lot of interesting functionality and teach us several things about input properties and also how to deal to with a component that has different functionality whether you're logged in or not. After that, we'll create a custom validator. Custom validators are a special kind of directive, this will be applying what we learned in the last section about creating directives in order to create this validator. When we're done we'll have seen a couple more typical scenarios for custom components and directives.
-
Creating a Voting Component
Our modal dialog box is now complete. We've got a fair amount of functionality with it. It's very stable, it does what we need it to do and it's quite versatile. So we're going to turn our attention to a new component. We're going to implement the functionality of voting on sessions. Here in the details page for an event, we have a list of sessions. Off to the left we'd like to put a little voting icon that lets people vote for a session to indicate that they like it. So we're going to go to our code and we're going to go into the session list component inside of events into the HTML. And we've got this div that is a column of 10 out of 12 sections wide. We're going to add a new one. We're going to add a div with a class of col, size medium, we'll just make this one 1. And inside of here, we're going to put our voting component. I'm going to call this component upvote. And this upvote component is going to need three things. We're going to have to tell it one, how many votes the current session has, two, whether or not the current user has voted, and finally we need to know when the user actually clicks on the voting component to vote. So we're going to need to listen to a vote event. We'll call a method toggleVote because we want the user to be able to click to vote, and if they've already voted, they can click again to unvote. And we'll pass in the current session so that we know which session they're voting for, and that matches this variable here. We've also got to give it a count of the current votes, and we access that from session dot voters dot length. And then we need to tell it whether or not the user has voted. We'll call our method userHasVoted, passing in the current session. And that will return out whether the current user has voted. There's just not a very easy way to determine whether the current user has voted, so it's much easier to call a method to determine that. And then we'll close this up. So, there's our upvote component HTML. Let's go ahead and create that component. So I'll create a new file, it'll be called upvote dot component ts. And we're going to need a few things from angular core. Of course we need Component. Since it's got an even that it raises, we're going to need the EventEmitter. We've got two input properties, so we need Input, and that event that it raises is an output property, so we've got to import Output. Now we can create our Component, which has a selector of upvote and its template will look something like this. We'll start off with a div, it has a class. We'll put in some styling classes, votingWidgetContainer and pointable so that the mouse is a pointer when we hover over it. And we want to know when they click on this div so we're going to add a click event and we're just going to call onClick. And inside of that div we have another div with a class of well and we'll add a votingWidget class, that'll be used for styling. Inside of that another div with a class of votingButton and this will be the display inside of here we're just going to put a couple of glyph icons. And one if they have voted, which will be the heart icon, so we'll add the ngIf, and if they voted then the class will be glyphicon and will be the heart. And then again another icon and this one if they haven't voted, and the class is also glyphicon and this one is glyphicon heart empty. And then after the voting button itself, we're going to need to show the count, so I've got another div, another class of badge and badge inverse, these are bootstrap classes, and we'll also give it our own custom class, voting count. We'll close that up, close up our other div. And now we can create our component class. It's to be called upvoteComponent. And we have those two input properties, count, which is a number, and voted, which is a boolean, and we have one output event, which is named vote. And that will be a new EventEmitter. And the only other thing we need to do is implement the onClick method, in which case we're going to emit the vote event and we don't need to give any data, so we'll just pass out an empty object. All right, our component's implemented, we've actually got to style this up quite a bit in order for this to look decently. So I've already created a bunch of CSS to handle this and I'm just going to create a styleUrls property and that'll point to app events event details upvote dot component dot css. And let's create that file. And I'm just going to paste in a bunch of CSS that will style this correctly. You can look through here and copy the CSS that I've pasted in here, or you can just grab this file from the github repo. And that will style your element up correctly. So I'll save that change, and I'll go into the index file and add that new component. We'll go into our module and import the component and also add it to the list of components for the module. Of course, I do need to save my changes to the index file. And if we try to run this, it's going to error out because we haven't gone into our session list and implemented this toggleVote method or this userHasVoted method. Those aren't implemented yet inside of our session list but we'll implement those next.
-
Adding Voting Functionality
We've got our votingComponent created, now let's implement the functionality. So we've got to react to this vote event through this toggleVote method and we need to implement this userHasVoted method as well. So let's go to our session list component and we'll start off with the toggleVoteMethod. I'm going to put this down here after the onChanges event. That takes a session, which is an Isession and in order to toggle the vote we need to do one of two things. If the user's already voted, then we need to unvote and if the user hasn't voted, then we need to vote. So we're just going to ask if the user has voted which we've got pull up that method anyway so we can call it userHasVoted, passing in the session. And if they have voted, we're going to undo their vote. Now I don't want to implement the functionality here to deal with the data. I'd rather create a service that handled voting functionality, so I'm just going to call that voterService and I can call deleteVoter. And of course, it will need to know the session and it will also need to know the user name of the current user. If we go into our event service, you can see that the data for a given session has a voters array, which is just the user names of users. So we're going to need to know the user name because when we undo their vote, we need to find it and remove it from this array. In order to get that, down here in our user folder we have the auth service. And the auth service has a current user, and that current user has a userName property. If we go back to our session list, we can say this dot auth service, I'm just going to call it auth dot current user dot userName. And now if they haven't voted, then we have to act differently. We need to vote for them, so in this case we're going to call voterService dot addVoter, again, same parameters, the session and the userName. And finally, since we're changing the number of votes, if they're sorting by votes, we want to update the sort. So we'll say if this dot sortBy is votes, then this dot visibleSessions dot sort, and we're going to sort by votes descending. All right, so there's our toggleVote method. The first thing we need to do to make this work is to implement the userHasVoted method, which, again, belongs to this same component. That takes in a session, which is an Isession and we can return this dot voterService dot userHasVoted, passing in the session and the userName. So now we need to bring in the AuthService and our voterService. The voterService hasn't been created yet, so let's first bring in the AuthService. So I'll go up here and import from up two directories user slash auth dot service. We are in event details, which is underneath events which is a sibling to users, so we had to go up two, and inside of there is the AuthService, and then we'll need to add that to our constructor and inject it, so let's create a constructor. Create the AuthService and we'll also need the voterService but we haven't created that yet, so let's create the voterService. And we're just going to put that in the same directory as event details, since this service is unlikely to be used by anybody else. Since the service you want it to be injectable, meaning we want to be able to inject other services into it, so I write import Injectable from angular core. We'll call that Injectable decorator, create our class, and it has to have the three methods that we referenced back in our session list component, deleteVoter, addVoter and userHasVoted. So deleteVoter will be our first one. It takes in a session which is an Isession and a voterName which is a string. We need to import this Isession type, from the model which is up in shared slash event dot model. And now to delete a voter, we're going to want to adjust the voter's array, so we're going to update that. And we're going to do that by filtering it. We get the voter, which is the name of the voter, and so we're going to filter on if the voter or their username does not equal the voter name that was passed in as a parameter. So for whatever element in that array has that voter name, it's going to get filtered out. Everything else will be included so the new array that's created through this filter method will be all of the voters except for whatever one matched the username. Add voter will be a little bit simpler. Same signature, but this time we just do session dot voters dot push voterName. And finally, userHasVoted, same signature, and in this case we will return a boolean variable. And we'll just call this some method, which is the method on an array that will return a boolean whether there is or is not at least one element that matches a specific condition, and that condition will be that the voter is that same as the voterName passed in. So if the value of the element of the array, the current element of the array is the same as this, then we're going to get a true, and therefore this whole method will return true. It will keep scanning through until it finds at least one record that returns true. If it gets to the end and none of them are the same, then it will return false. So now we've got our three methods implemented, we can go back to our session list. And we need to import that voterService. Grab that, and we will inject it into this class, and of course, since we just created that service, we need to add it to our index file, so let's save our change here and go to the index file and add that service, and then go to our module, and we'll import the service and add it as a provider, to the whole module, and now if we go back to browser, I'm not going to refresh, I'm going to go to the events page and then refresh, and there's a reason for that. And that is that voting functionality requires a current user. I wasn't logged in, and even if I was, when I refresh, the client loses the knowledge of the currently logged in user and you're logged out again. And so the details page will break, and we can see that here if I click into the details page. Notice it's completely broken. So let's go back to events, and I'll log in. And now if we click on an event, the voting functionality is there and I can click on it and we go from the empty heart to the filled in heart. If we go from three to four, then we get the flame icon next to the session. So this is the basics of the voting service and in the next session we'll address this issue about having to be logged in.
-
Hiding Functionality Before Authentication
Now let's quickly fix the issue that you cannot get to this page if you're not logged in because the voting functionality breaks if there isn't a currently logged in user. So we're going to go back to our code, and inside of the session list component, way up right here, in the HTML, we've got this upvote component. And this just shows up all the time. So instead of having it show up all the time, let's have it not show up if the user isn't authenticated. And we can do that easily by wrapping this in a div, with an ngIf, and remember we've imported the AuthService into our component. So right from here I can call auth dot isAuthenticated. Now just a quick side note here, your editor might complain that auth is a private variable. This may or may not give you problems, because we're now accessing the auth variable directly in the template. At the time of this recording, that doesn't cause any problems, but in the future that could change. To be safe, you can go into the component and change the auth variable from private to public where it's declared in the constructor. Back to what we're doing, we're calling isAuthenticated here. That is a method inside the AuthService, we'll go to the AuthService. We have this isAuthenticated method that returns true or false based on whether or not the current user is authenticated. So if we wrap our upvote component inside of that div, then if we go back and we refresh the page, we're no longer logged in, you can see that, that the page doesn't break, it just doesn't show the votingWidget.
-
Using @Input Setters
Now I'll make one small change to our votingWidget. Right now it goes from an empty heart to a full heart when you vote, I'd like to change that to go from a white heart to a red heart when you vote. So let's go back into our code, and it's here in the votingWidget, this upvote component right here, where we need to make that change. So here is where we got the glyph icons, whether it's heart or heart empty based on whether they voted or not. I want to change that, I'm just going to use the glyph icon of heart, I'm going to get rid of this heart empty, but I'm going to bind to the color property of style, so really what I want is, I want style to have a color of red if they voted, and have a color of white if they haven't. So therefore I want to be able to set the color property of the style for that icon. And I can do that by binding to style dot color. And I can set that equal to some value that exists inside of my component. I'll create a new property named iconColor and that way I can set iconColor as a string to either white or red, and I would want to set that based on whether or not they had voted or not voted. Now I could do this by adding an onChanges lifecycle handler like we've done in the past, and since it's an input property, every time it gets a new value, I'll see the value and I can react to it, if it's voted I can change it to red, if it's not voted, I can change it to white. But there's an easier way to do this. We can use a setter on input properties. I'm going to get rid of this iconColor, and for this voted property, I'm going to add the word set. And then this now becomes a function which takes in a parameter which is called val. And that has a body, and I can set this dot iconColor equal to val, if it's true, then we go to red, and if it's false then we go to white. And I do need to create that iconColor property but this would be public not private. And I want to initialize it here, because it get initialized here on this line. And since the icon is visible all the time, we no longer need this ngIf, we can get rid of that. And we can save our changes, go back to the browser. We'll refresh, of course, need to log in. And now if we click on the heart, it changes from white to red. And that's how you can use input setters in order to create a derived value from an input property.
-
Creating a Custom Validator
Before we finish up this module, we're going to create one last directive. Remember in our createEvent form, we have an eventLocation and an online URL. Now when the user's creating an event, they either need to give the location, including all three fields, or give the online URL and that's what makes the form valid. Right now there's no validation around these fields, you don't have to fill these in. The other fields are required, but not these ones. So we want to create a custom directive that will validate this, make sure that they've either filled out all three of these fields, address, city or country, or they filled out the online URL. This is not something that angular will handle by default, so we've got to do this ourselves. Let's take a quick look at the HTML for the createEvent form. Here inside of events, it's down in this create event component dot HTML. Close this other stuff up. All right, here is the HTML for our form and the relevant part is down here. We have this model group called location and that wraps around the event location which is the address, the city and the country. That's this model group here. And then we've got a different form group, which is the online URL. Now we need a custom validator that will apply to both of these. Unfortunately, since they're sibling nodes, it's not going to be easy to create this custom validator, because it will be put on of those nodes, probably this node up here, but it will need to access both of these nodes. So this is going to be a fairly business-specific validator. What we want to do then is to add the validator to this node as a directive, something like validateLocation, and once we add that validator to this node, then we want it to apply the validation, these custom validation rules we're talking about. Let's go ahead and get started with that by creating our custom validator directive. Since this is fairly business-specific, I'm not going to put it in the shared folder, instead I'm going to put it right along side the create event page itself. And we'll call this a location validator, it's a directive, and so we're going to need to import directive from angular core, and now we can create our skeleton directive, and of course, we need to give our directive a selector, and it's going to be the attribute validateLocation, which we saw over in our HTML. That's the attribute we want to use, save that file. And we're going to add another piece of this directive in a minute, but I'm going to leave this alone for the moment. We're going to export our class. I'm going to call it LocationValidator, even though it's a directive, I'm not going to add the directive suffix, just the validator suffix. And since it's a validator, it can implement the validator interface, which comes from angular forms, so let's bring that in. There's our interface Validator, we'll implement that. And that means that this class needs to implement a function called validate, which takes in one parameter which is a form group, and I'm going to call that control, and the type is FormGroup, which we can get from angular forms as well. And that method returns an object, and that object can have keys, which are strings, and the value of any key can be anything. And inside that function we need to get a handle on our address, city and country and our online URL. And to get that, we'll start with the address, which is a control, so we'll suffix this with control, and that will equal our control, which is our form group, dot controls, and we want the address. I'm accessing the address property through the indexer syntax like this, which is really the same as doing it like this, except for, because the typescript won't see it that way, and it will give us this error, so instead we use the indexer syntax. And I really don't like this name control, let's call this formGroup, all right. And we also need the city and the country, and finally the online URL. So city's next, then country, and finally the online URL and in this case, since our validator is on a sibling of the online URL, I'm going to actually go up a level by going to the root, and then I want to get to its controls. Unfortunately the type of root, it's just an abstract control, but I actually know that the type is a form group of the parent if we go and look at this model group, its parent is a form, which we can access as a form group by giving it a type, and now that has a controls property, and I can get the online URL. So that's how we go up a level from the node that we're at, which is going to be again, this node here, to get a sibling node, so we have this model group right there, and we want this node right here, and to get its online URL. You'll remember, even though we are looking at nodes in here, it's really a tree structure of controls that we're dealing with when we're talking about the form group and its controls and going up to its root. This is tree structure controls, it reflects the DOM tree, but it doesn't include all of the nodes in the DOM tree. Now that we've got all our controls, we want to write a statement that says either the address, city and country have to be filled in, or the online URL control has to be filled in. So in that case, we write in our if statement and then we want two clauses to that, so we add another set of parentheses. And the first clause is going to be the address, city and country are filled in, and the second clause is going to be an or the online URL's filled in, so another parentheses for that one. That one's a little bit easier, we'll do that one first. The onlineUrlControl has to exist and its value has to exist, so it has to be filled in. And we'll do the same thing with the other clause, which is address, city and country, so addressControl and the addressControl's value has to exist, and the cityControl, cityControl dot value and finally country, and it has to have a value. So there's our if clause, and if this is true, then we want to just return null. Returning a null tells the validation system that this validator is passing, there's no problem. If it's false, we want to return an object with validateLocation as a key set to false. And that matches the type that we defined up here, where key is a string and the value can be anything. And we have a value of false in this key which is a string. So it's going to return either null or this object. If it returns this object, the validator is going to be failing, and we'll get a validation error. And there's our validation control. It's still missing one very important piece before it will work as a validator, but I'm going to talk about that in the next section. So instead let's just add this validator to our dependency injector, by going into the index, and exporting it, and we'll save that and go into the app module and this'll be inside of the events so, find it there. And it's a directive, so I need to add it to the declarations section. And there we go, we can save our changes in both files. We've got our validator created, again it's still not working, we've got more to do, and we'll see that in the next couple of sections.
-
Adding a Validator to Angular's Validators
And we've created our directive, it's a valid directive, and our module knows about it, so it can be used as a directive. In order for that to happen, we've got to add this validator to angular's list of validators. And that list exists here in angular forms as ng underscore validators. This is an opaque token, if we were to inspect this variable it would be an opaque token. What it represents, of course, is a list of every validator that angular supports. If we want to add a new validator to our application, we need to add it to this service, essentially, this ng validator service, and there's a very special way that we do that using some syntax that we haven't seen before. Now if you remember, and this is how it works. We're going to go down into our directive and we're going to add a providers key. Now if you remember in a previous section, we talked about dependency injection and how every component has its own dependency injector. We can register services with the dependency injector on a given component, and those services will be available to that component and its children. That works for directives just like it works for components. So this allows us to use this providers key here just like we do in the app dot module file. But what we're going to do is we're not going to create a new service. Instead we're going to add this validator, the location validator, to this list of ng validators which is essentially a service. And we do that by giving an array to providers. And we use the same syntax that we saw before when we were adding new providers using the long form of provide and then we give it a token, so here we use our ng validators token, and we're going to say use existing, and that will be our location validator. So again, I'm giving it the same class that I'm creating right here. Now you might look at this and say wait a second. What you're actually doing is overwriting the ng validators with the location validators, so whatever was there is going to be gone, and instead all we have is the location validators, so all the built-in validators would not work anymore. That would be true if I left it exactly like this. Instead I'm going to add a third key to this object called multi and set it to true. When I do this, what it does is that it tells angular hey, this service here, which is a validator, we want to add it to this list of services because we're setting multi to true. This ultimately is a collection of services, and we're adding one more item to that list. I know this sounds a little bit weird, and it's not really important that you understand this from a level that you'll want to do this on your own, because this would be a very unusual case, but for adding your own custom validators, you need to know the syntax so that you can do it yourself. And rather than just showing the syntax, we're going to explain it a little bit, that what this does is that it adds another item to this list of services that's underneath the ng validators. Once we've done that, we've now registered our validator with angular so that it's available as a validator in a form. And again, even though this is a little bit deep, all you need to know is that this is a list of validators and we're adding a new one to it by setting multi to true.
-
Implementing a Multi-field Validator
We've now created our validator directive and added it to angular's list of validators. Let's save this change. But before we head to the browser, we need to check and possibly fix one item. Here in our CreateEvent HTML, way down at the bottom, way down here, we need to double check that the disabled attribute is being bound to the correct button. Here you can see that I've bound to the disabled attribute on the cancel button, but that actually needs to be on the save button. So that's where you have this disabled binding. We need to move it off the cancel button and up here to the save button. And we'll save that change as well, and now we can go back out to the browser. And refresh, the validator is going to be running, but it's not going to work correctly quite yet. Let's open the console and just keep that open and let's, I'm just going to use some garbage values in here. And now since none of these are filled in, this should not allow us to save, and we're seeing that's true, so let's fill in just the online URL, and we'll go back to our button. It's not valid yet, if we leave it's still not valid, so let's go in and add an address. And now our button is still not valid. So we've got a problem, our validator isn't quite working right yet. We need to fix this by giving the validator a little bit more information. Going back into the code, I'm going to go into the HTML. And up here where we created the validator and added it to this div, I'm going to mark this div as a model group. We've already got the ngModelGroup properties set, but I'm going to add that to a local ref variable called locationGroup. And I set that equal to ngModelGroup. So the syntax is a little bit weird here, here we're adding a property called ngModelGroup and setting it to location, and here we're adding a local ref locationGroup and setting it to ngModelGroup. What angular is doing is taking the model group and adding it to this ref so we can access it. Let's save that and we'll go back to the browser and refresh. And we'll try this again. Put in an address, and now you can see that our button is enabled. If I delete one of these values the button's disabled, if I put it in, the button's enabled. But what happens if we don't have an address and instead we have an online URL. Notice that the button is still not enabled. So our directive still isn't working correctly. And the reason for this is that the directive is on this address, which covers these three fields, and not on the online URL. So it has no idea when the online URL's value gets changed. We need to fix that. And there's a fairly simple way to do that. We're going to go down into our input box, and we're going to add an event handler on the change event. So I want a list of the change event, which is fired whenever the value of the input box changes. And what I want to do is take this location group that I've created up here, this ref. Refs are available not only in the node and its children, but also in sibling nodes. So here I can refer to that ref and I can access its control property which has an array of controls. The locationGroup refers to this div, this model group essentially, and the control would be the model group's control. And that has the control's array, which would be all of the three different input boxes, the address, the city and the country. And I just want to access one of those three controls. So I'm going to access the address control, and I want to tell it to reevaluate its validation. And I can do that with the updateValueAndValidity method. I just call that right here, and what this will do is every time the value of this input box changes, it will go up to the address control, which is right up here, this input box right here, and tell it rerun your validation which will then reevaluate the validation not only for this, but including this validator that we put up here, because that applies to this input box. It will revalidate it and now if this is valid, then the form will now show it's valid or invalid. So if I save that, we go back to the browser. Refresh our change, again more garbage data. Notice the save button's disabled. And if I tab out, that fires a change event, and now the save button's enabled. But if I go back in and remove this, and then tab out, the change fires, the save button's disabled, but I'm not given any information about what's going on. So let's add a little message that says hey you've got to have either the address or the online URL. So our validator's now working correctly but we just don't have feedback. That's pretty easy to fix. Here on the address, right after the event location, we're just going to add in a little emphasis tag, and we'll only show it if the location group, and that's this ref I created up here, let's make it nullable, it's invalid property is set, and the location group has been touched. We don't want this to show up if we've never touched any piece of the location group. So again, nullable, and then we'll put in an error message. Save that change, go back to the browser and refresh. And now I'll test this by giving it a value and then remove the value, and now we're getting our error message. You must set to either the full location or an online URL. So there we have it, we've got our error message working. We've got our validator working. It's a very strange and complex validator. We've had to touch a lot of different pieces of the form in order to make this work. There are other ways to skin this cat as well, but this is certainly one that works and it's a way to create a complex validator like this.
-
Summary
In this module we created our voting component. This component had to do some interesting things with input properties, and it also had to hide itself when the user wasn't logged in. After that, we saw how to create a custom validator. This validator involved several moving parts, but it showed us how we can build validation functionality that isn't already part of the angular framework.
-
Communicating with the Server Using HTTP, Observables, and Rx
Introduction
In this module, we're going to learn all about how to communicate with the server using HTTP. In Angular, this means observables, and RxJS. We'll start out with an introduction to RxJS. Then we'll learn how to communicate with the server. In this module, we're going to move all of our data storage from the client to the server. We'll go through the event service and the voter service and change their functionality to store their data on the server. Along the way, we'll learn a lot about how Angular works with RxJS. Finally, we'll move our authentication to the server and implement the logout feature while we're at it. HTTP communication is key for most web applications, so this is a module that will be critical to learn. It's important to understand the basics of HTTP communication so that we can see where RxJS fits into the mix. Here's a simple diagram of HTTP. The client sends a request to the server, and the server responds. This is an asynchronous communication pattern. The time between the request and response could be several hundred milliseconds, or more, so our client continues to process while this is happening. In the old days, we handled this with a simple callback shown here. We made a request and some kind of callback function handled the response whenever it returned. Then, along came promises. Promises were nice because we could arrange our handling of async operations a bit better. Multiple pieces of code could listen to the result of a single call, and we could avoid nested callback hell, and now we have RxJS, and their main feature, observables. Looking at the code here, you can see that the basic handling hasn't changed a whole bunch, but the feature set really has. First, let's clear the main difference between promises and observables. Promises represent a single value that comes back sometime in the future. Observables on the other hand represent zero or more values that come back either immediately or in the future. Right off the bat, we can see a major difference between the two. Promises are limited to a single value, where observable are not, and promises are asynchronous, where observables are either synchronous or asynchronous. And there's plenty of other features that observables support that promises do not. Observables are often referred to as a stream of observable data, or in essence, any value that changes over time. Consider for example mouse clicks. Each happens at a specific time and has an x and y coordinate. Observables are excellent for handling data like this since not only can you observe this data and respond to each mouse click as it happens, you can also manipulate the data stream. Let's say for example that I wanted to just react to each x position, not caring about the y position. I can manipulate the stream so that I'm only dealing with the x position, like so. This is generally done through a map operation where you map an incoming value to a new value. With observables, this is a core activity and very easy. This comes in very handy when dealing with HTTP responses since the data that comes back is often way more than we want, there will be status information, headers, et cetera, when really all we want is to return data. So mapping a response to just the return data is very natural with observables. Observables have a lot of other features that are very valuable as well. They can be synchronous or asynchronous, they have improved error handling over how promises work, they can be closed independently of returning a value, and they can also deal with time in a way that promises cannot. There's a bunch of advanced operations they have as well such as mathematical aggregation, buffering, so that you get several results together in batches, or debouncing, so you don't get too many results if they happen too quickly, you can only deal with distinct values, or you can filter the values down to just the ones you want. You can also combine multiple observables into a single observable. And they have a built-in retry mechanism so that if the first try fails, they can retry again. So now we know that observables are the answer we've all been looking for for every problem we've ever faced while programming. Therefore, we should purge all knowledge of the despicable technology we call promises from our brains, right? Well, not so fast. Remember in our HTTP communication model where that communication consists of a request and a response, unless we're using sockets, each HTTP request can only represent a single value, so being able to deal with a stream of data does us no good. Unless we're using sockets, each HTTP request only represents a single return value. It might have multiple pieces of data inside that single value, but in the end, it's one response that comes back, so being able to deal with a stream of data does us no good. For most purposes, a promise works just fine when doing HTTP communication. Of course, Angular's HTTP library returns observables, so this really doesn't matter, does it? Well, yes it does, because of one key feature that observables in Angular support, and that is the toPromise conversion function, which will take any observable, and convert it to a promise. So if you decide you can cast any HTTP request down to a promise, and you can even do it after you map the data from the response to extract just the data you want from it. That can make your handling of HTTP requests benefit from the features of observables, and the simplicity of promises. Now that doesn't mean that this is a recommended way to deal with HTTP, it just means that observables aren't necessarily the only way to handle things. You can choose for yourself whether or not to leave all your HTTP communication as observable, or convert them to promises, it's up to you. In this course, we'll only use observables so as to get the most familiarity with them. Just don't write off promises in your code. And with that, we'll finish up our introduction to RxJS. There's a practice exercise for this clip, if you want to do that now.
-
Preparing to Store Data on the Server
In this module, we're no longer going to be gathering our data from inside of our application, we're going to be communicating with the server. But in order to do that, we need to have a server to communicate with, and we have built a small little server specific to this course that looks and acts like a real server, lets us gather data and save data, but it doesn't really have all the features of a real server. Getting into actual server-side technologies is way beyond the scope of this course. We're just going to be dealing with how the client interacts with the server. Let's start by installing that little server, I'm going to go to a command line, and I'm going to type in npm install ngF -server, and I'm going to save it to my dependencies with a -S, and now that that's installed, we'll go back to our code. We're going to open up the package.json file, and now if you look right down here on line 26, we've got our ngF server installed. We need to run that service, we're going to need a new command for that. I'm going to name this command server, and that will call node, this is a little node server, and we'll add a parameter to that which is the location of the server file, which is inside of node_modules/ngf-server/server.js, and we'll save that change, and back to the command line, we can run that command with npm run server and that will launch our custom little server that we can talk to to save and request data from. And we are still going to be using the CLI to serve up our files. So we need to tell the CLI's web server that when we make certain requests to certain URLs that it actually needs to talk to our little custom server. This is only for dev mode. We do that with a special proxy file, so I'm going to create a new file, I'm going to name it proxy.conf.json, I put that in the root, and inside of here, there's a JSON file, so we start with an object. I'm going to tell the CLI that whatever I make a request to any URL starting with API that I actually want it to talk to my own server, which is running on port 8808. So it's going to be target, and we give it the url http://localhost:8808. And finally, we're not running an SSL server for this little server, so we can tell it secure, false. I'll save that change, and back in my package.json file, whenever I launch the CLI which I do with the start command, I need to tell it that I want it to use that configuration file for the proxy settings. So I'm going to add on a proxy-config with a setting of proxy.conf.json, which is the path to that file. Save that change, and now in a different command line, if I was already running a server, I would stop it at this point and restart it, and just start it again with npm start. If I'm not running a server at this exact second, I'll just start it back up. And starting it again is now going to run it with that proxy-config setting. And there we go, our server is running on Port 4200, so we go back out to the browser and refresh, and our application is still running correctly. It is using that proxy server and will talk to our actual server, but we aren't doing anything yet, so there isn't any communication going on quite yet. Now this arrangement is complex enough that I want to spend a minute and just look at a couple of diagrams to explain how this works. This is again only for development. When we're talking about going to production, the arrangement is going to be different. This is how our servers are arranged in development. We're going to be running both the CLI server and our new HTTP server that we installed just a few moments ago. When our browser makes a request for something like /home or a CSS file or something else along those lines, anything that the routes file would answer to, pretty much any URL that doesn't start with /api, then the CLI server is going to respond to that request and serve up whatever file is appropriate. If it's a route, it's going to serve up the appropriate index file. If it's an actual file like a CSS file or an image, it's going to respond to that image. But any other scenario, where we are going to make a request to something like /api/users, again, this is based on the configuration we did in that proxy configuration file, the CLI server is going to see that request and pass it on to our actual HTTP server that we spun up and installed just a moment ago, which is running on localhost 8808. That server's going to respond based on its own internal rules. So for example, if we ask for /api/users, it's going to send back JSON that lists all the users. So that response comes back from the server, the CLI server then receives that response, and sends it back to the browser. Again, this is only how it works in dev. In production, it's more your typical scenario where your server actually handles all requests whether they're to /home or to /api/users. And so it's time to start using HTTP. We're going to start that, we're going to be moving all of our data communication to using HTTP. And to start off that process, I've got to make some changes to my app module file. I'm going to close these files down and open up app module, and in here I'm going to tell my module, my main module, that I now need to work with HTTP. I am going to import from @angular/common/http and from there, I'm going to import the HttpClientModule, and then down where I create my module, I'm going to add that to the imports. And that has now imported the client module and all of its relevant classes and utilities into this module so we can now begin using HTTP. Save that change, and in the next section, we'll actually start accessing some of our data over HTTP.
-
Moving Data Storage to the Server
And we're going to start with our events service. Let's open that up, event.service, and this class right here is providing access to our events, for example, the getEvents method right here. This accesses our events. And if we scroll down, it's this local events data right here that we're actually accessing. We no longer want to access this local events data, we want to access the events data that is held on our server. We want to access it through that new little web server that we installed. So this class now has to utilize HTTP. We start off by adding the HttpClient, we'll do that with a constructor, and I'm going to create a private variable. I named it http, and the type is going be HttpClient. Let's import that, again, from the Angular common HTTP library, and we'll start by converting our getEvents method, so we can take all of this code right here and just get rid of it. We like the signature, the signature's good because it's an observable IEvent, but we no longer want to grab it from this local array that's just here in this file. Instead, we want to talk to the server. So we're going to return this.http, and the HTTP class, that HttpClient class has a method for every type of HTTP interaction we can do. We're gathering data, so we want to make an HTTP get request so we will call .get, and that method will make a get request. This is a typed method where we tell it what it's going to be returning out, and it's going to be returning out again that IEvent array, and it takes in a set of parameters. The only primary that we really need is the URL. So our URL to request the events is just /api/events. Notice how we're starting with /api, in our proxy config file, we told our server that any request that come to a /api/anything are going to actually get forwarded on to that new server that we got running on port 8808, so we got to make sure that any new URLs that we use start with /api, and correspondingly, that little server we set up so that if any requests come from /api/events, it's a get request, it's going to return a collection of every event that is in the server's data set. Now at this point, I'm getting my data, and it's going to be typed correctly and everything is fine, but what happens if I have an error? I want to handle errors. So I'm going to create a private method called handleError, and that has a type of just T, it's a generic type, and the operation is going to default it to a string operation, and it has a second parameter, which is a result, which is an optional parameter, which will be that same type T that is the type parameter. And from within here I'm going to return an observable of T a function that takes in one parameter error, which is of type any, and that function returns an observable of that same T type, whatever that type is. And that function is going to call console.error, so it's going to report out to the console that there was an error and we're just going to report out that same error object, and then we're going to return an observable of the result cast as a T. This is some kind of deep typing with the signatures and stuff using the generic type of T. Really digging in and understanding all of this is a little bit beyond the scope of this course because this course is not specifically about RxJS or about typescript, but this gives us a nice template for just basic error handling, and anything you want to do to handle the errors, we can just do right here. So if you want to just copy and utilize this code as is but you want to customize it a little bit, you just need to add a little bit more code here where we have the console.error. Maybe you want to log out to a database or to an actual error logging system, whatever it is, you can customize it there, but this code can pretty much just be copied and pasted. As you get more into RxJS and more into typescript, you can learn how to deal with this code and do specific things that might work better for your situation. But for now, this code is going to be a nice template that we can just utilize to handle basic errors, and to utilize the error handling with RxJS, we're going to pipe this get method. So if I typed in a period here, we can see the pipe is another method on what's getting returned. So I'm going to put it on a new line so we can have a little bit more visually clean code. We'll call pipe, we're going to call a special RxJS method called catchError, and catchError is going to be imported, we're going to import that from rxjs/operators, and that's a method where we pass in that handleError method that we created. And that method has a type parameter which we're going to set to IEvent array, because that is the return type of this getEvents method. And then when I call it, the first parameter it wants is an operation, and that's a string, and that's just the name of the method. This is for error reporting purposes. So I'm going to pass in the same getEvents, that's the name of this method, so if this method has an error during the HTTP call, when the error is reported, the word getEvents will get reported out, and I can pass in a default result, so I can pass in just an empty array. And now with that, we're calling the get method, and we're handling any errors with a very basic operation just logging out to the console. So let's save that change, and now if we look to see where this getEvents is called from, we're going to find that it's in the EventListResolver. And it's right here, that's where we call getEvents, and notice this map call is just mapping events to itself, we can actually get rid of that, so let's delete that map call, and this is a resolve function, and we need to note something here. A resolver automatically subscribes to an observable call that it gets, so getEvents, if we go back to our event service, notice the return type is an observable. Angular has built-in functionality that in a resolver, any observable that it gets, it will subscribe to itself, we don't have to make a subscribe call, but if we were making a getEvents call ourself somewhere else like just in a component or another service, we would need to subscribe to this observable with a call to .subscribe. The reason we need to do that is with HTTP observables, the HTTP request does not get made until somebody subscribes to that observable. So if nobody ever subscribes, that HTTP call will not happen and that is a common gotcha that can with RxJS is you create an observable, you return it from a method, and so then when you call that method, you might assume, "Hey, this observable is going to happen "because it's a call to get on the HttpClient." But that HTTP call will not execute until somebody subscribes to the observable. Again, we don't have to do that here, that's done automatically, so I'll delete that code. We'll save that here, and now we can go back out to the browser, the CLI has already refreshed the page for us, but let's go to the network tab and let's refresh it again, and we're going to look at just XHR requests, and notice we've now got this call to /api/events. Let's open this up just a little bit here and we will zoom in a tad, and look at the response, let's do the preview. We can actually see that we're getting back event data. This is what the server is returning to us whenever we call /api/events. This is the data that's getting returned and our client is now grabbing that data, and that's the data it is getting displayed here in our page it's these five pieces of data. If we had different events coming back from the server, then we would be displaying different data. And now we created our first HTTP call using RxJS and Angular's HttpClient. There are practice exercises for this clip if you want to take a moment and do those now.
-
Listening to Resolved Data Changes
We've got getEvents converted to use HTTP, let's now convert out next method, which is getEvent down here. Now I don't want the handleError method to be right in the middle, I'm going to move that to the very bottom, so I'm going to cut that out, and on the very bottom after searchSessions and put the handleError call there, and now getEvent. The getEvent has a problem because the signature's wrong, right now it's just returning IEvent directly, rather than an observable. So let's start by changing the signature. The signature will now be an observable of IEvent. And of course, this code no longer works. Instead, let's go ahead and just copy this code right here, but we don't want to be returning an array of events, it's just a single IEvent, and when we catch the error, we no longer want to get a default array. But at this point, we're calling to the same endpoint, which is api/events, which is going to just return a list of every event, we only want a single specific event, so we're going to add on to this URL the ID that, of the event, which is the parameter that we receive in, so we can just add on id, and that's going to make a call to /api/events/ and the id number of the event, and we're going to return single event from that HTTP call. Now we need to go out to where this call is being made which is in the event details component. And down here in the ngOnInit is where that's being called. And we can see that we're getting an error. We no longer can handle this code this way. Instead, we need to call the getEvent method and we need to subscribe to, we talked about this in the last section, this HTTP call will not actually get executed unless we subscribe, so let's subscribe. We're going to pass in a function. The function is going to take in the value that comes back which is a single event, which is of type IEvent, and inside that function, it will handle the results of the getEvent call. So we want to set this.event equal to the event, and of course, we set addNode to false here in the ngOnInit. You probably only want to do that if the call is successful, so let's move this inside of our subscribe call. And that's all we've got to do, if we save this change and go back out to the browser, we can click on a specific event, and this is the data from that event that's actually coming an HTTP call. But we have a problem and that is that this event details page is actually being guarded by an activation guard. If we look at the event route activator service, this service calls the eventService.getEvent and checks to see if the event actually exists. Now that call no longer works because it's returning out an observable and not an actual event, so this guard is no longer valid. So let's switch this up a little bit, let's no longer guard the route. Instead, let's create a resolver on this data, and we'll resolve the data before we actually hit the page and we won't even guard this route anymore. So let's start by deleting this file. I'm going to send this over to recycling bin, and let's create a new resolver. Down here right next to the events list resolver, we're going to create another resolver, which will just be our event resolver. We'll name it event-resolver.service.ts, and let's just go into the events list resolver, and copy this code and we'll use it as a template. Now of course, we want to change the name, we no longer want to call this the EventListResolver, we're going to call this EventResolver, and we're no longer calling getEvents, we're going to call getEvent instead, but that takes in a parameter, and that parameter is the ID that we want. In order to get the ID that's being accessed when we have a resolver on a route that has a route parameter, we will get ahold of the activated route snapshot. So here in our resolve method, we can actually pass in an activated route snapshot. We'll call it route, and the type is ActivatedRouteSnapshot. Notice that's being imported on line two. And then within the call, we can call that route snapshot. It has a params property, and that params property, we can access the ID property of that, we'll use the array access instead of the .access. And we'll access it like this because the params type doesn't know what the name is. We can name a route parameter anything that we want. We named it ID in the route. If we go into the route and we look at the events ID route, we have named that parameter ID, so that creates a property on the params object. We normally would access it as .id because that is the name of the property, but that type doesn't know about it. We could also have named this Joe, in which case, we change this name here to Joe, but we did name it far more appropriately ID, so we'll keep those two things as just ID. So let's save that change, and we need to add this resolver to our index. We'll save that change. And now I need to add that resolver to the modules, so let's go into the app module, and we'll go down to the providers, and we no longer want the EventRouteActivator, this doesn't even exist anymore. If we were to scroll up to the very top, we'll see there's an error, because that doesn't exist, so we'll take that out, and instead down here, through the EventRouteActivator, we're going to use our EventResolver, and we will import that and save that change. And finally, back to the routes file, we were using this EventRouteActivator which no longer exists, we'll cut that out, and we'll change this canActivate to just resolve because we are going to be just doing a resolve instead of an activate guard, and this will be our EventResolver, and we'll import that. And of course, save that change. And now back to our event details component. This is where we need to get ahold of the event that is now being resolved, and essentially, it gets loaded before this component actually gets executed. So we need to get ahold of that data from the resolver, and we have a pattern that's already doing that. In our events-list component, we're doing that same exact thing right here in the ngOnInit, so let's grab that line of code, and inside of this forEach method, instead of calling subscribe, and want to set the event equal to the call to the snapshot data, and we'll delete that, and it's not called events, it's called event. And we can find that name by going into the routes. And this is where we set the name. We didn't set our name correctly, we just listed the resolver, we actually need to follow the pattern that's given right up here. So we change this array to be an object, and we name it event, and we fix that back into our event details component. We now have an extra closing curly brace, let's fix this indentation and this extra line. So let's save that page, we'll go back to the browser. No such file or directory event-route.activator.service. So we got a problem, it's in the event details index, it's still referring to that activator service we deleted. So let's go into that event details index file. It's right here, we're still referencing it. We'll cut it out and save. Go back to browser, and there we go, it is selecting the data for that one event and populating our browser. If we check the console, look for any errors, we're not getting any errors, but before we finish up, let's go back and we're going to clean up one small issue in our code here, our event details. Notice that we're actually subscribing to the params object. The activated route snapshot has a list of parameters. It also has a list of all the data that's been resolved. So we can simplify our code quite a bit by changing this from params to just be data, and the value that's received, we'll just name it data. And now we access data as a property not of the snapshot, but of the callback that's being passed into the forEach function. So that's cleaned up our code a little bit, we'll save that, and there we go, we're now listening to resolved data changes. If we look in our browser, and we are on the Angular Connect event, we do a search and scroll down and do something else, it's routing to the correct event.
-
Using POST and PUT
You've now converted getEvents and getEvent to use HTTP. Next, we're going to do our saveEvent method. Now, saveEvent's going to be different because we need to send data to the server, which means we need to make a post request instead of a get request. So let's come on down here to our saveEvent, and we are going to get rid of its implementation, and since we're making a post, let's just check out what post looks like. Call this.http.post, and let's look at the parameters. Notice the first three parameters. The first is the URL, just like with get, but then we have a body parameter, and then third, an options parameter. Those parameters are all required. The body is the data we're sending up, but the options allows us to do things like set our HTTP headers, which can tell the server what kind of data we're sending up. That's exactly what we need to do here, we need to tell the server that we're sending up JSON data. So let's create an options variable. That's going to be an object that has a headers property. And that headers property is going to be a new HttpHeaders. And let's import that. That just imports from that same common HTTP. Inside of here, we pass in an object where we can specify any headers we need to set. We only need to set one of the content type, and that property is going to be set to the string application/json, and that tells the server we are sending up JSON data. Now in the post call, we need to set the type of data that's going to be returned by the post, and not in every case do you care what comes back from the post call, but sometimes you do. If we're going to create a new event, we might want to get an updated copy of that event from the server because the server might for example set the ID of the event, and that actually happens in our case. So we're going to give it a type of IEvent, and inside of our call, we're going to be using the URL of /api, of course, /events, and because it's a post method, the server knows that we're creating an event, then we need pass in the body that we're going to send to the server. In our case, we're just going to pass in that event. And finally, we're going to set our options. This is of course an observable, and we want to catch any errors, so we'll go up here and grab the catchError method. And we can use the same exact one, except instead of getEvents, we're going to set this to saveEvent. And notice we missed something in the last section, getEvents should here be just getEvent. So there's our call to http.post, we want to return that observable. That way whoever calls the saveEvent method can then subscribe to the observable. Let's save those changes, and we'll head over to the create event component, and here is where we call the saveEvent method. And right down here where we call saveEvent, this is now an observable that we need to subscribe to. And it does return the event from the server, but we don't really care what's coming back, we just want to know that the call has been successfully made. So once that call returns, we're going to set our dirty flag and navigate a way to events, but we won't actually do anything with any data that's returned from the server. So let's save these changes and we'll go out to the browser and we are going to create a new event. Let's open the console just so we can watch for any errors. And let's make that as small as possible. Go to the create event page, and we'll call this ngAntarctica, and that will be held on January 1st, 2040. It will start at 9:00 a.m.. The price will be 500. The address is 123 South Pole Circle at the South Pole, country is Antarctica, and we don't really have an image to use, so just use /Antarctica.jpg, which is going to return a 404, which is fine. And let's save that event, and here we go, we've got our Antarctica. We didn't get any errors on the console. We did get a 404 out of that image, but that's fine, that's not anything we weren't expecting. And if we click into it, we can see the data that it's got. Now before we finish up and go back to our code, we're going to look at the next method, which is the updateEvent method. Normally when you update an object using a RESTful server, you would make me put call instead of a post call, but in our application, the only time we're updating an event is when we are adding new sessions to an event. So the server that we've created has actually got a smart endpoint, the /api/events endpoint, notice that if you send up an event that actually has an ID, then you want to do an update. If you send up an event that doesn't have an ID, then it knows you want to create that event. So in our case, we don't actually even need the updateEvent method because the saveEvent will do both things and serve both purposes. We're going to delete this, but we will talk about the format of the put method. If we did need to make a put request, an HTTP put request, we can change post to put, and the signature's actually identical to the post request. Takes the same exact parameters, the URL, the body and the options. We'll leave ours as a post, but now we need to go into where we call the updateEvent method. Let's save that change first, and we make that updateEvent call inside the event details component. It's right down here, is where we call updateEvent. So instead of calling updateEvent, we're going to change this to be saveEvent, and since this is an observable, we need to subscribe to it, but in our case, we don't care about the return data, we're not doing anything about that. We could listen to the subscribe and make a callback that sets the addNode to false inside of the callback of the subscribe, but in our case, we're going to be a little bit more optimistic and just leave it there, so we just have an empty call to subscribe. So now we can save that change, and we will now be both creating and updating events on our server.
-
Using QueryString Parameters
The last method that we need to convert is our searchSessions method. Now the nice thing about this conversion is that all of this code here in the searchSessions method is going to go away because the searching itself is going to be done on the server, we just need to make a call. So let's go back up to one of our getEvents here, and we are going to paste in that code and replace the entire set of code of searchSessions, but of course, we need to make a few changes. What's different about searchSessions is we actually use a query string, which is thankfully extremely easy. Let's change our URL, instead of api/events, it's going to be the api/sessions/search?search= and then we'll append in our search term. That's where we'll use a query string in the URL, we do need to change this return type, it's no longer an IEvent, it's actually an array of ISessions. We still want to catch the error, but again, it's not an IEvent, it's an array of ISessions. And it's not getEvent, it's searchSessions. And we can go up and add in a return type, which is an observable of my session array. And we'll save those changes. And since we actually haven't changed the signature, we really don't need to worry about going to wherever it's called and updating that. We're still returning out the same exact thing, which is an observable of an array of ISessions. If we just check out where it's being called, which is in the navbar, down here in searchSessions, we can see that this is exactly right, we're calling searchSessions and we're subscribing to it and setting the foundSessions equal to the return value. We are logging out the foundSessions, we probably don't need that, we could take that line of code out. We'll save that and go back to the browser and it's already refreshed for us, but let's go ahead and search and make sure that our search is still working, and it is. So using query strings is extremely simple in Angular. There's a practice exercise for this clip, if you want to do that now.
-
Using DELETE
We're now finished converting the event service to use HTTP. If we wanted, we could come in here and delete this whole EVENTS const. I won't bother with it, but you can if you want to. Let's move on to our next service to convert, which is the VoterService. We've got three methods here, deleteVoter, addVoter, and userHasVoted. Let's start off with addVoter, because that's going to be a post, which we're familiar with already. Let's leave that line in there, but instead, also add in our post. We're going to need the HTTP service, so let's create a constructor and we'll bring in a private HTTP of type HttpClient which we got from calling HTTP, and then after the push here on the session voters, we're going to call this.http.post. Our URL's going to be kind of long, so I'm going to create another variable just for that, and I want to do a little bit of string interpolation, so I'll use the back tick. It could be /api/events/ and then here we need the event ID, I'm going to interpolate that in. I don't have it yet, but I'll grab it in a minute. Then sessions, the session ID, again, I'll interpolate that in, and then voters, and then the voter's name. That's the URL that we need to use, and that's what we're going to post to whenever somebody actually votes for a session. So we can add that URL, we need a body, and in this case, because the URL actually has all the information in it, I'm just going to pass in an empty object as the body. Normally we would send some data up, but just posting to this URL actually causes the session we voted to, all the information is in the URL. The final thing we need is the options. Let's create the options. Again, that's an object with the headers property passing in an object that has a content type property which we will set to application/json. It does use the equal sign here, and then we can import the HttpHeaders, and we can pass in the options. We do want to catch any errors, so we'll pipe a call to catchError and import it, and we need another error handler, so let's go back to our events service and let's just copy the handleError method here. I'm going to put it after userHasVoted, we'll bring in the observable, and we'll call this.handleError, and the name of the method is addVoter. We're still missing our event ID so let's add that to the signature. We're going to pass in the event ID, we'll add that first, it's a number. And finally, since this addVoter method is going to get called, we're going to pass in the data, and we don't really care what comes back, I'm just going to make this post method subscribe to itself right here. Instead, we could have returned the observable and let whoever calls it subscribe to it, that way they can get notified when the call gets finished, but I know that in our case, that's not necessary where we call it. It's actually called in the session list, and if you look at where it's called, right here, we don't really care to be notified when it returns, so we're just going to have itself subscribe. So let's save that change and go back to the session list, we need to add the event ID. Now, the session list doesn't know about the event, which is where we get the event ID, but ID the session list is used on the event detail page. Right here in the HTML is where we use this session list, and this page actually does have the event, which has an ID. So if we just add a property called eventId, and we can bind that to the value of the event, which we want to be nullable, and its ID property. Save that change, we have to go back into the session list, and we need to add a new input parameter, and that's a number, and now down here, we call addVoter, we can just pass in that eventId. I bet it's this.eventId. And now we are correctly calling addVoter, passing in the proper eventId. Let's save that change, and that finishes up our addVoter method. Let's do deleteVoter now, deleteVoter's actually going to be easier than addVoter. We still want to update the client side, so we're going to keep that line of code in, but we're going to notify the server that we deleted the voter. So we call this.http.delete. Again, we want the same URL as we used before, so we want to grab that URL, paste it in, same URL, and of course, I want to catch any errors, so we'll add in the pipe. And finally, I'm going to also make this self subscribing because where it's called in the session list, which is right here, we don't really care to know when it finishes. So save this change and we need the eventId, so we're going to add that to the signature as well, and that is the eventId, which is a number, and back in here, we call it, we just pass in this.eventId, and that correctly calls deleteVoter with the correct parameters, and that's how you do a delete. Just like we did with post and put, we simply call the delete method that's on the HTTP object. In the case of delete, all we have to do is give it the URL. So let's just quickly head over to the browser, make sure we haven't broken anything. We're going to log in, and we'll go into this one and vote. We can see the vote is registering. Let's go ahead and look and see the network traffic. We want to unregister, and we can see here that we have made a delete call. There's our delete call to that URL, we gave it a delete vote. We're also getting a post. And that is how to implement the delete method.
-
Integrating Authentication with the Server
We're now storing our event data and our voter data on the server. The last type of data that we need to move to the server is our user and authentication data. Up until now when we log in, we're only logging in on the client. We want the server to also know that we've logged in, so we want to send that authentication to the server. In fact, we want to authenticate on the server. We don't even want to check the username and password on the client, we want to have the server check our username and password. Now different servers are going to deal with authentication differently, and that's beyond the scope of this course. Our server is a simple little node server that uses a library call Passport to deal with authentication, but how we interact with the server is going to be fairly similar from one Angular application to the next, and from one backend to the next, we're just in the end going to be making HTTP calls. So we are going to make those changes by going into our auth service, and we're going to start by implementing the login method here. It's this login user method that we need implement. Right now, it's just doing sort of a dummy log in. No matter what you do, it just sets the current user. It's important to note that we do set the current user, the login user method, its main purpose is to set the current user when somebody gets logged in, so we do need to set the this.currentUser property, but we're not going to set it like this. So I'll comment out this code just to remind me that this is the functionality I need, but instead, I'm going to make an HTTP call. Of course, we need the HTTP service, we need a constructor, private HTTP object of type HttpClient, and let's import that from Angular common HTTP, and then in our login user method, we will start by calling this.http.post. That's the method I need to call in order to log in. The URL is /api/login. Again, I'm designing the server with this URL to log in when somebody makes a post method to this URL. How I design the server is completely up to me. How you design your own servers are completely up to you, so the URL is likely to be entirely different. The method probably could be a post for a log in, but you might decide to do something different on a post. That's really up to you and your interpretation of what is the best way to write a server. So don't look at these aspects like what the URL is and what method is and assume that this is absolutely the best way that every server should be written exactly like this. Again, the details of the server and how you write them are up to you, but you do need to know what the URL is on your server for the endpoint you're working with, and you need to know the shape of the data you need to send up, or the shape of the data coming back, so that you can interact with them on the client. Now in our case, we need to post up some log in info, so let's create a loginInfo variable, and that's going to be an object. It looks like this has a username property, and we'll set that equal to the username that's passed in, and notice something very important here. The property that I'm creating is username, all lowercase. I have to do it exactly that way because of the server that I'm using, which is using Passport, is expecting a property that's called username, all lowercase. If I accidentally do username like this, my log in is not going to work, so that matters. In the server that you write, it might be like this, it might be something else. It really depends on the server. Then we need to pass the password as well, so we'll create a password property, and send in the password, and there's our loginInfo object, which is going to be the body. And of course, we need to create some options, so we'll create an options, and that's an object with the headers property. We'll create an HttpHeaders, which we will bring in from Angular common, passing in an object with a content type, that has been set to application/json. So there's our options and our loginInfo, we pass in the options third. So we're now posting this data. Again, remember we need to set the current user. So when this call from the server returns, I want to set the current user equal to whatever data comes back, but I don't want to do it in the subscribe method, because I don't want to subscribe here, I just want to make a side effect, and I can do side effects by piping in, calling the pipe method, a special RxJS operator called tap. Tap is the way to tap into the stream and take an action when a piece of data comes through the observable. We're not manipulating the value in any way, so we don't want to do something like map, we want to use the tap method, it lets us just see the value that comes through and we can take an action if we want based on that value, but we're not manipulating what's going through the observable stream. So let's import tap, and then I'm going to pass into that a callback function which receives the data item that is going through the observable, and we'll take an action based on that, and in our case, we want to set the current user, which is what this method was doing. So we'll set this.currentUser, and we're just going to take that data object, we're going to grab its user property, and we do want to cast that to an IUser. So we've now set this.currentUser, which indicates a successful login. I can delete this commented out code now. Of course, we do want to handle errors because the user might have typed in an incorrect username or password, so in that case, the server is going to return an error. So let's catch errors, and let's import that. Now before, we had this complex method to deal with reporting out errors, but in the case of a failed login, we don't want to report that out to the console, and instead, we want to change the value in the observable stream, we want to add a false value. So I'm going to create a callback method, and I'm going to return the of function, which I'll import from RxJS observable of, and I pass in false. What I'm doing is I'm creating an observable of false, and that's what's going to happen if there is an error. So for example, the server might return a 403 HTTP error, and that's when this method would execute and put a false value into the observable. So let's return out this observable, and I'm going to save that, and let's go into our login component, and here is where loginUser is called. So now that's an observable, so I'm going to subscribe to that observable, and that passes back some kind of response from the server. I don't really care what the response is, so long as it's not the value false. So we'll pass in a callback function that receives a response, and we'll say if the response was false, then we know that our login was invalid, and we'll do something about that, otherwise, the login was valid and we can navigate over to events. We'll put our call to navigate right there, and if it's invalid, we need to do something, we need to show a message to the user. We don't have that functionality yet in our application. So let's create a new variable on our login component called loginInvalid. We'll default that to false, and here, we'll set it to true. And now we can show something to the user if their login was invalid. So let's save that and head over to the login components HTML, let's go way down here to the bottom, and after our form, let's add in a br tag, and then a div that we will only display if loginInvalid is true. And the class will be alert and alert-danger, and we will give the message Invalid Login Info. Let's save that change, and we'll go over to the browser, and we'll see if this now works. So let's put in something invalid, I'm just going to type in random letters, click login, and we do get the message Invalid Login Info. I'm going to log in with something valid, and I know that one of my usernames that is valid is johnpapa, and the way that the server's implemented, it doesn't actually care what you put in for the password, you just have to have the username correct, and that returns true. Obviously, a real server wouldn't do this, it would actually check the password, but in this case, the server we wrote doesn't care about that. So I'll click login, and lo and behold, it logs in, and we have the Welcome John message up here at the top. And there we've implemented login on the server. To sum up, it is very important that you understand how your server operates. I know exactly how this server operates, what data I have to send to it and what methods and URLs I have to use to get different things to work. You have to understand that about your server, and how you write your server can determine what URLs you're using, what methods you're using, and what the shape of the data going in and out is, that is all beyond the scope of this course is how you make your server, we're just focusing on how we interact with a server that has known URLs, known behavior, known data.
-
Persisting Authentication Status Across Page Refreshes
You've got authentication happening on the server now, but unfortunately, our client is not persisting login information across page refreshes. If I refresh, the client thinks I'm no longer logged in, and it wants me to login again, but the server actually does, the server's tracking that, and on the server, I'm still authenticated. So let's change our client so that it asks the server for the current authentication status whenever the user refreshes the page. Now there's a lot of different ways to do this and how you do it may depend somewhat on your server. We're going to go with the simple method for our application. Back in the code, what I need is a way to check the authentication status of the current user. Are they logged in or are they not? A reasonable place to do that is in our app components as that's sort of the core of the entire application. Let's open up the app components, which is events-app.components.ts. And here we've got a simple template and no actual code. We've got this title property that isn't being used anymore and let's delete that, and what I want to do is get ahold of the authentication status for the current user. Anything that I do with authentication should go through that auth service, that's the logical place to put this type of code. So let's inject the auth service here in our events app component. We'll create a constructor and we'll inject the auth service and of course, import that as well. And now that we have the auth service, we can check the login status of the current user when the application is initialized. And we'll do that in the init event for this route app component. So I'll add ngOnInit, and inside of here, I'm going to call this.auth, and now I need a method that checks the authentication status. I don't have one currently on my auth service, so we're going to create a new one. Let's name this checkAuthenticationStatus, and all that will do is check the current authentication status whenever the application is initialized, which is one of the things that happens when we do a hard refresh in the browser, and let's create this method now in our auth service, saving that change, going to the auth service. After isAuthenticated, let's add that checkAuthenticationStatus method. And we need to make another HTTP call, so we'll call this.http, and in this case, I've got an endpoint setup that is at the URL /api/currentIdentity, and this returns no value if the user's not logged in, but if the user is logged in, then it returns their current identity as an object. And that's a get request that that endpoint is set up for, and when that method returns back, I want to check the data that gets returned back and do something if the data is of a certain type. So there's a couple of ways to do this, and this code right here doesn't need to do anything when the call returns. Instead, it's more about setting the current user here. We could just subscribe, and then with the data that comes back, we can determine what to do. Another way to do this is to actually use that same tap method we used previously so that we can take an action that is a side effect. So let's look at both of those implementations. The first one will just subscribe, we get back some data in the subscribe, and we know that it's going to be null if the user is not logged in, or an object, a user object if they are. We can just call if data is an instance of an object, then we can set the current user. And we set it to that data property, but it does of course an IUser, let's cast it. This is one way to handle this. Another way we can do this is basically the same thing, but using tap. So we pipe and then call tap. And of course, we need to import tap, and we'll grab all that code we wrote into subscribe and cut it out, and paste it up here, and close this up. These two methods for dealing with return data function exactly the same. One benefit of doing it this way where we do it inside of the tap method instead of doing it inside of the subscribe is that later on if we want to actually return the observable and let the consumers of the checkAuthenticationStatus actually subscribe and take an action based on when the data comes back, we can simply just delete this line and do a return, and of course, go everywhere that checkAuthenticationStatus is called and call subscribe, and that way, they get the same data, they get access to that user data that we have access to here in the tap, but these consumers can actually take an action and deal with the correct data. There isn't really a right or wrong answer in this case, I just personally like this better because I like how this call looks a lot better than how it looks when we add the subscribe. So I'm going to save that code without the call to subscribe back into this code here. I'm going to take out the return call, and then save this call as well. As is often the case with programming, there's several ways to accomplish something, and it's oftentimes a very subjective call to determine which way it should be done. So let's now go back to the browser and refresh. After we go to events, it doesn't make any sense being logged in if we are authenticated. And notice that we're seeing that we are authenticated. So now the client is getting the authentication setting from the server.
-
Saving User Data to the Server
Another feature of our application is to update the current user. We have the ability on the profile page to change the user's first and last name. This method in the auth service, the updateCurrentUser implements that functionality. Right now, it's only persisting to the client, we want to persist that change to the server. Again, the server itself is already set up to handle this functionality through a URL of /api/users/ then the current ID of the user, and if we would do a put to that URL, we will update the user that corresponds to a given ID, say, three or four. So let's implement that. We do need to set the content type and then come up here and grab this options, and just paste it in down here. Then we can call this.http.put, setting the URL. And we want to include the ID of the current user. Let's use string interpolation here. And we'll get this.currentUser.id to interpolate in the user's ID. And the body that we want to send up is going to be the current user. We've updated the current user here, so the current user has now got the correct value, so we can just send up this.currentUser. And I know that the endpoint expects an object that looks just like my current user looks like on the client, so I don't need to format it in any way, I could just send up the actual current user object, and the third parameter of course is the options. So we're now issuing our put, but let's look at how this is called. It's called in the profile component, and it's called right here, and after the current user's updated, we call toastr.success to give the user a message that the profile has been saved. I would like this toastr message to happen only once the server returns back and says that the user has been saved. So let's go back into our auth service and let's return this observable, and then here in our saveProfile, we can call subscribe, and we'll do an empty return function, or we can call the toastr.success, and that will let us only show the toastr once the user has been updated on the server. So I'll save my changes here, and back to the auth service, we'll save our changes there as well. And let's go back to the browser. I'm going to refresh just to make sure we have the current code. Let's go over to the profile. Let's change John to Johnny, and we'll save. We did get the message that the profile has been saved, but we don't know if it's persisted the server for sure unless we refresh and see if the values are still the same after cutting a round trip from the server. So if we refresh, and our first name is still Johnny, so the server has persisted that data change. We're going to set it back to John and update the server. And there, we have successfully updated the server with the user data whenever it gets changed.
-
Implementing Logout
There is one more piece of functionality that doesn't even exist yet in our application, and that is giving the user the ability to log out. Right now, there's no logout functionality anywhere in the application. So let's add that, we'll start with our UI first. And it's in our profile component, we're going to add a logout button. So we'll go to the HTML of the profile component. And way down here on here at the bottom, after this cancel button, we want to add a new button for logging out. Now I want to change this a little bit, I don't want this to be a default look, I want this to be a warning look, and I'd like it to be visually separate just a little bit, so I'm going to float that to the right. That'll separate it out some from the cancel and save buttons. Of course, you don't want to call cancel, let's call logout, and finally, the text should be logout. Save that change and go into the profile component and let's implement our logout function. Let's just come right after save profile and create logout. And the actual functionality of logging out a user should be inside of the auth service. So we'll call this.authService.logout, but we want to know when the user has been logged out successfully on the server, because then we want to redirect the user to the login page. Keeping them on the profile page doesn't make any sense at all once they've been logged out. So let's make this an observable that we can subscribe to, and once they have successfully logged out, we'll call this.router.navigate, and we will navigate to /user/login, which is the login page. Now of course, we're getting the red squiggly underneath logout, because that function doesn't exist yet in the auth service, so let's save our change here and go to the auth service, and we will create a logout function. Now this is going to be an HTTP request, let's set the content type to application/json, and remember, we're subscribing to that in the profile component, so this needs to return an observable. And in our case, I've already got an endpoint set up on the server, which is /api/logout, and have to post that, so we'll call post, /api/logout, and a post needs a body, but it doesn't make any sense to have any data in the body, so we're just going to pass in an empty object. And then finally our options to set the content type correctly. Now again, this course is not about server design, about RESTful API endpoints, or anything like that, though you are seeing some choices that we have made when we created the server. For example, to logout, you need to create a post request to /api/logout, which logs out the current user. We could have made that a put or a get, but in our case, we made it a post. A post requires a body, and we're just passing in an empty object. We could have passed the current user across the wire, but we really want the server to just figure out who the current user is and log that person out rather than worrying about like a user ID that we send across the wire. So these are some choices that are made that you might decide how you want this server to look and it might look a lot different than the server that we created. It's beyond the scope of this course. It's just important to know that when you are interacting with your server on a client, you need to know what your API endpoints look like, what methods they require, what the URLs are, and what data they accept and return. Finally, to implement logout, we have now logged out the user on the server, but we haven't logged them out on the client. To log them out on the client, all we have to do is set the current user equal to the value of undefined. And now the client will know that the user has logged out. So let's save that change, and we'll go back to the browser, and we now see the logout button. Let's click that, we've been logged out. If we go to events, and even if we refresh, note that we are logged out, we have to log in, if we want to have any login functionality. We have now finished all the functionality for our application, with one small exception. This events dropdown was actually meant to show a list of every event that exists on the server, but right now, it's just hard-coded to show one event. So this is a little bit of homework you could do on your own. You can wire up this dropdown to list every event so that users have quick and easy access to a specific event. We're not going to show you how that's done, we'll leave it up to you, but with what you've learned so far in this course, you certainly have all the tools you need in order to implement that functionality.
-
Summary
In this module, we learned all about using observables. We did this by moving our data storage to the server, and we even integrated our authentication with the server. Server communication is a key piece of most Angular applications, so learning it is very important to becoming a competent Angular developer.
-
Unit Testing Your Angular Code
Introduction
In this module, we'll learn how to unit test our Angular code. We'll see how to test services and components. There's quite a few options when testing your Angular code, so in this section of the course we are going to take a look at each kind of test and see an example of it, but we're not by any means going to exhaustively test our code. That would be well beyond the scope of this course. Let's look at our agenda. We will start with an introduction to unit testing and some of the concepts that matter when testing JavaScript and Angular. Then we'll look at Karma, the primary tool used to test our code. After that, we'll see to write tests for services. Then we'll move onto components and learn how to write isolated tests for components. Isolated tests just test the component class and not its integration with the template. In the next module we'll look at the counterpart for isolated tests, integration tests, and by the time we're done, you'll have a very good understanding of the various ways to test your Angular code. One of the first things you must understand is, What is a unit test? A good definition is the one shown here. A unit test is a test on a single "unit" of code. Of course, the definition of unit is purposefully vague. A unit can be whatever you define it to be, but generally it should be as small as possible. Most developers consider a unit to be a single class, although a few related classes can also be considered a unit. There are two types of tests we often talk about, unit tests and end to end tests. It's useful to see the difference between the two. Unit tests have the following attributes. They are fast; unit tests should run in milliseconds. They involve isolated pieces of code, so only a very small portion of your entire system should be entire system should be active during any single unit tests, whereas end to end tests will tend to be slow, usually several orders of magnitude slower than a unit test. An end to end test will exercise the entire system, so you will need to have you complete system up and running in order to run your end to end tests. Due to the speed factor, we generally write many unit tests and few end to end tests. Now in this course we are not going to go through how to write end to end tests for our application because for the most part writing end to end tests doesn't depend at all on the tools, libraries, or framework that you are using, and there are several great tools and courses published that discuss how to write end to end tests. So if you want to learn how to do that, you can check out one of those courses. Let's cover some of the attributes of a good unit test. First, a good unit test should be fast. If it takes a long time to complete a unit test you won't run it very often and it will lose its value. A unit test should be cheap to write, meaning even though it might take a while to set up your first unit test for a given unit of code additional unit tests about that same piece of code should be fairly easy to write. Unit tests should cover a single state change, meaning you put your system in a given state, you make a change to that state, and then you test if that result is exactly what you expected. If you're making multiple state changes within a single unit test, you're probably doing something wrong. A unit test should assert one thing. That doesn't mean that you only have one line of code for your assertions, but that you're just checking that one specific thing happened. A good unit test doesn't cross process boundaries, so it shouldn't call out to a database, or out to an HTTP server, or anything like that. Finally, a good unit test should be reliable. This means that a unit test should never fail due to external conditions such as timing of other tests, race conditions, or anything like that. It should always pass or fail based on if the code itself is correct or incorrect. When we talk about the structure of a test, there are two terms that we should know. The first is AAA. This stands for arrange, act, assert. The first part, arrange, is where you create the initial state of the unit test. This can be as simple as constructing a class or a complex as creating starting data and setting 10 different state flags. Then comes the second A, act. This is the part of the test where you change something, perhaps you call a method on your class, perhaps you change a variable. In any case, you act on the system and cause the state to change. Finally comes the last A, assert. In this section of the test, you check that the expected result happens given the initial state and the change in state. The second term is damp. Although some people have created an acronym out of this word, it is more useful to simply consider the meaning of the word damp. You may have heard of the term DRY in programming. If you haven't, DRY is an acronym which stands for don't repeat yourself. This is the principle that you should never duplicate code. Any code duplication is a problem and should be fixed. Although that is true for the code we write, when it comes to tests, we don't have to be quite so draconian. In fact, we say that our tests don't have to be DRY. They can be a little damp. Not wet, we don't want lots of duplication, but a little bit is okay. The reason for this is that a good test should tell a story. We'll see how this affects the amount of duplicate code that we have later on. First, let's look at some code to understand the AAA structure. We're going to write a simple test to test what happens when we add a friend to the user object. So first, we do our arrangement. Here we create a new user and initialize their friends list to an array with a single item, Ralph. Then comes the action. We call it the add friend method, passing in the name John. Finally, the assertions. We check to see that the expected result happened. In this case, the friends list should have two items in it. And that's all there is to it. Notice that the various pieces of the test are easy to identify and they come in a specific order. This should be true in all your unit tests. Now let's talk about the second term, damp. In this case, we have written a very similar test to the previous one. In fact, the first two lines of code are completely identical to the last test. So we might think, hey, let's move those two lines of code into a function and then call up the function in our test. This is what we would do with normal code, right? Well, there's a problem with that. In this test we are adding a new friend, and it's the same friend that Sally already has, so the result is that we should only have one friend in the list. Without knowing the initial state, we wouldn't understand why we got this specific end state. It's important to know that Ralph is the one friend who was already in the list, so that when we try to add him again nothing changes. This is the story of the test. It makes it readable. It has three parts: the initial state, or arrange part, then the state change, or act, then the final state, or the assert part. In some cases we can remove some of our duplicate code, for example the construction of the user object might be moved to a sub procedure, but parts of the test that are important to the story should stay in the test. We'll see more of this later. Another concept you will need to know is mocking. Mocking is the mechanism of replacing the dependency of a fake piece of code that does less than the original code did in a meaningful way. We do this for a few reasons: speed, ease of testing, or to just avoid code that we don't want to test. Let's assume that we have a test of an order object. This is the code that we're testing. Furthermore, that order object uses an HTTP object to save changes to the server. Now of course in the unit test, we don't actually want to make any HTTP calls, so we need a mock HTTP object such that when the order object calls the post method on the HTTP object, it simply returns immediately. The test can still be run, but we won't be using the real HTTP object. In this case, we want to mock the HTTP object. So we replace the HTTP object with a mock HTTP object that has dummy methods just for our test only. This is mocking. Jasmine is the unit testing framework we will use with Angular. There are several testing frameworks out there, but Jasmine is one of the most popular ones. It's very straightforward. It uses four main functions: the describe function, which is the containing function for a suite of tests, then there's the beforeEach function which gives us the place for a common setup code. The main function we deal with is the it function. Each one of those creates a separate unit test. And finally is the expect function, which is how we assert that the test passed. In our expect functions, we use what's called matchers. Matchers are what allow us to express what we are expecting. For example, we may expect that certain value is three. In that case, we can use the toBe matcher, where we specify that we expect the value to be three. Jasmine includes a lot more matchers, such as the toContain and toBeDefined matchers, and we can even add our own. Karma is a command line test runner. It's the tool that actually causes the tests to be executed. It's complex in execution, but simple in concept. Karma launches the browser, and then causes that browser to run your tests. Karma can run your tests in multiple browsers based on how you configure it, and when finished it will report back the result of the test run to you. In this module, we'll be learning about two ways to test your components, isolated unit tests and integrated unit tests. Neither one is inherently superior to the other, and each has benefits and trade-offs. First, let's look at isolated tests. An isolated test tests the class only, so if you're testing a component, it is not testing the component's template, only the code. In an isolated test, you call the constructor of your class or service directly. Isolated tests are simple in comparison to integrated tests. They generally require less code and are quicker to write. Isolated tests are the preferred method for testing services and pipes. Since these parts don't interact with the template, you will usually use an isolated test for them. Isolated tests are also appropriate for components and directives if your priority is to just test the code and not worry about the interaction with the template. By comparison, integrated tests are quite different. They test both the class and the template if you're using a component or a directive. In an integrated test, your component or directive or service is actually constructed by the framework, not by you. Integrated tests are more complex and require quite a bit more setup code to get them running. Integrated tests are mostly used for components and directives, and not quite so much for services and pipes, although they are occasionally used to test services for various reasons. Finally, there are two types of isolated tests, deep and shallow. Deep tests will test multiple components by comparing to child component to work together, whereas a shallow test will only a single component. We've gone over a lot of concepts in this introduction section, so let's get to the code and actually see how all this works.
-
Installing Karma
We're going to learn how to run tests in our Angular applications. Before we actually do run a test, though, we're going to take a quick look at our package.json file so we can see how we actually run tests. Here in the package.jsonfile in this "scripts" section we've got this test script that is created by default by the CLI. Notice that the script that actually executes is ng space test. So this is NPM calling the CLI. This is how we run our unit tests. So if we go out to our command line and run npm test, that is the same thing as if we were to run ng space test, and of course this only works if we have the CLI installed globally, but normally running npm test is a bit more standard because that way we can adjust the script instead of just running ng test we might do more than just that, and what we type in on the command line doesn't change, it's always npm test. So let's type that in and go ahead and execute it. And that is going to call Karma to actually run all of the unit tests that it finds inside of our source code. So let's look at the test that's run, and we can see this message shows that we have executed zero out of zero tests, and it is saying that there's an error, but that's because there are no tests to run. The other thing that Karma has done at this point using the default configuration is it has opened up a Chrome window. Let's go ahead and look at the output of that window. This is what the Chrome window looks like, it's using Jasmine, and it gives us this nice little UI that says we've got zero specs total and zero failures. So in running the test we can look at both the command line and at the browser window that's opened up. Normally we just watch the command line. That tends to give us a little better information, although as you can see it's a little bit chatty, and it can put a lot of unnecessary information into the window, but occasionally this becomes useful to us. Now let's actually create for ourselves a test so we can see what it looks like when there is a test that's running. I'm going to go back to the source code, and we're going to in the root of our app directory, I'm going to create a new file called test.spec.ts. This file name with the .spec.ts, that is the type of file that Karma will look for. It will run any spec.ts file it finds inside of our source code directories. So let's write ourselves a little bit of a Jasmine test here. We're going to start with a Describe and we'll give it a description and then a callback function, and within that we're going to call the it function, and that takes in a description as well, and I'm going to type in 'should be true.' What I'm saying is that I expect that some value should be true. And a callback function again, and then within here, I'm going to expect that true is using the toBe function, true. So notice the name of the Describe is 'first tests.' Then we have the it function that then should be true. When writing tests, it's good to use very descriptive text inside of these first parameters, so that when we string them together, for example, in the first tests, it should be true. Obviously it doesn't need perfect grammar, but it should be a kind of description that tells us what the test is about and the section of test that we're in, in this case the Describe first tests, it tells us where in our application we're dealing with the tests, and then the It block tells us what should be going on. It's always common to start the description within the It block with the word should. So let's save that test, and we're going to go back out to our Karma, and notice that it still said zero zero and then it finally picked up the test, and now it says "1 of 1 success." So Karma is watching the files, it's looking for new files, and when it sees new test files it picks them up and runs them. If you do have any problems with Karma watching when you create new test files, sometimes you have to stop and restart Karma. Just hit Ctrl+C, and then rerunning NPM test will let you relaunch Karma in case it doesn't pick up your new test files. Let's go back out to that Karma browser window and we can see that we've got one spec with zero failures, meaning that there was one test and we didn't have any failures. The first test should be true, it's passing. Let's go back into our code and make this test actually fail, we're going to say false here. We save our change, go back out to the command line, and now we're getting an error. You can see here it's printing out the entire stack trace, which is a little bit chatty, but right here first tests should be true FAILED. And then it says Expected true to be false. So we expected a true value, but instead we got a false value, which is exactly what our test was showing. We were expecting a true but we actually passed in a false. And if we go down to the bottom, we can see that it executed 1 of 1, but 1 failed. And let's look at the browser window, and here we get a little bit more readable and usable UI, where it says we have 1 spec total but 1 failure, and then it shows us the failing test and it's highlighting it in red, and it prints out our stack trace for us. So this is really good feedback and it helps us track down what is our problem in our tests and deal with it. Now let's go back and fix that test. And let's write one more test. It'll be very similar to this one, 'should be false' instead of true, and this will let us see what it's like when we have more than one test. We're going to expect(false) toBe(false). And we're going to save that change back to the command line, and now we're getting Executed 2 of 2 SUCCESS. Notice that it's printing this out twice. That's a very common type of thing, which is why oftentimes the command line is so chatty that it's a little bit difficult to pick out the actual important information in this window. The browser oftentimes is a little more concise and readable, but sometimes the command line gives us context that the browser doesn't actually show. And here our browser is saying that we have 2 specs with 0 failures, and then it lists the describe, and then it gets underneath the tests it, it lists the tests and ones that are passing are shown in green. And that's how we run our unit tests using the CLI and Karma.
-
Unit Testing Services
We're going to write some real tests for our code, so we'll remove this test.spec.ts file, going to right-click and select Delete, and we're going to add our first real test. We're going to be testing a service, and the service that I want to test is the VoterService. That test is here under events, under event-details, here's that VoterService, so I want to create a new file right next to it called voter.service.spec.ts. Now just like any regular file that we'd use, we're going to start off by importing some stuff. So first I need to import the voter service itself. Next, I want to import the ISession from the event model. We're going to use that in our test. And finally, I want to import Observable, and that's from rxjs. And again that's something I'm going to use in my test, and we'll see that in a second. Then of course we start with our autoDescribe, and I'm writing tests for my VoterService, so I'll just put the name VoterService in my describe, and in order to test the VoterService I need to actually create an instance of it. This being an isolated test, we created the instances ourself. Angular isn't creating instances of services for us or components. We're doing that ourselves. And of course when you want to create a new instance of a class, you do that with code something like this. And it takes in one parameter which is the HTTP service. So we're going to need to provide it a mock HTTP service. And of course we don't want to give it a real one, because make any HTTP calls during unit tests, so we're going to need to provide a mock HTTP service. We haven't created that yet, we'll do that in just a second, but before we do, notice we're creating our service here. When we write our tests, let's say we wrote three or four tests. One here, one here, and finally one here. Each of these three tests would use the same instance of the VoterService, so if we have these state issues maybe we changed some property on the service in our first test, then in the second test we'd start off with a mutated state. We probably don't want that. What we want is a fresh copy of our VoterService. That why we use beforeEach when we initialize anything, we do it inside the beforeEach function. That way we can get a fresh copy. Now of course, we can't even reference this variable because we defined it inside of beforeEach, so we need to define it outside the beforeEach, and I want to give it a type of course of VoterService, and finally I also need my mock HTTP, so I'll define that here. I'm not going to give that a type, since this is a mock object, it's going to have a different interface than its underlying class, so therefore tying to give it a type could be problematic. Now I've got to create that mockHttp object and I'm going to do that in a beforeEach, and I'm going to use Jasmine here. Now I'll set that equal to jasmine.createSpyObj. And first I need to give it a name, and then I can give it a list of its methods. These are the methods that I want Jasmine to create spy function to create on this object for me. If I go back into the code that we're actually testing, voterService, and look at the HTTP object, you can see it occurs twice, right here where we call delete and right here where we call post. And that's the only time the HTTP occurs. So those are the only two methods I would need. So I add both of them, 'delete', 'post', and that way this SpyObject is now going to have a delete method and a post method. They'll be empty methods that don't do anything else but define the functionality, but they will exist so when my VoterService calls them, they won't error out. Next, I'm going to create a Describe for the function that I'm going to test, and we're just going to test the delete voter function, doing that in a Describe, note the name of the method, and now within here I can write my tests. So my first test is going to test that the deleteVoter function actually removes the voter from the list of voters. If we go back to the code we can see that the first thing that it does is filter out the voter from the list of voters. We want to test that functionality, so that's what our test is going to do. We're going to write a little sentence here that it 'should remove the voter from the list of voters.' First we're going to need a session with a list of voters in it. Sessions have a lot of properties, but the only property I care about really is the voters property, which is an array of strings. So I added two, "joe" and "john," and going back and looking at our code we can see that it filters out the voter based on the voterName that's given, but it also requires an eventID and a Session. Notice that the Session itself has two properties that are used, voters here, but also the ID properties used here. So we're going to go back. We need to give this session an ID property. We'll just give it an ID of six. So our session is created, and that way when we call deleteVoter we can pass an eventID, we can pass in that session that we created, and we can pass in the voter name. Now I'm getting an error on the session, and that's because the type doesn't match, so I'm just going to forcefully pass this to the ISession. That is why I imported ISession up here, just so that I could forcefully pass this session even though it doesn't have all the properties that an ISession should have. And now that I've got my call, I can write my expect statements but I need to check my code and make sure that not only will this line of code actually execute my test, but will this line of code execute. And what's going on here is when we call delete passing in this url, then it chains on this Catch call and the Subscribe call. Therefore, what was returned from Delete needs to be an observable. If it's not, then this line of code is going to error out on me. And we can see that if we just save this test, we are getting the error Cannot read property 'pipe' of undefined. Again, in our code, the Delete method should be returning an observable of some kind. That's what the HTTP delete call does, but in our test, our mockHttp delete call is just going to be returning an undefined, that's the default return value. Fortunately, it's easy to tell these mock objects to return a different value. We can do that by calling mockHttp.delete and then we can call .and.return value, and that tells the mock object that whenever this delete method is called, this is the value that should be returned. We want an observable, any observable will do. Let's just create an observable of False. We'll use the Of function, we'll import that, and we'll just return an observable of the value false. And now if we save our changes and go back to our tests, the test is now passing. But the test is not actually checking that anything is happening. It's just executing code, and since there are no failures, it's reporting that it was successful, but we really need to actually test something. We'll need expectations for that. So first, we will check that the Voters array has the right number of elements, and after the delete that should be one. We started with two, we deleted one, so we'd expect there to be one afterwards. And let's also double-check that the one that's there is the one expect to be remaining, so we'll look at the Voters array, and we'll look at that first element with index zero, and we'll expect that is the word "john." And we'll save and go back amd look at the resulting tests and it does indeed say that we have Executed 1 of 1 and our tests are still successful, even though we are now actually running some expectations. Of course if we'd gotten this wrong, like we'd look for "joe" instead of "john," but we deleted "joe," then our tests are going to tell us that we are failing and that's the message that we get. So let's fix this and get our tests back to the passing state, and we have finished that unit test. And there are practice exercises in this clip if you want to do those right now.
-
Testing Mock Calls
With our first test up and running we can now write our second test. This test is going to test the same function, called om the same exact way, but it's going to test the other thing that that function does. In our VoterService, our deleteVoter method actually does two things. First, it removes the given voter from the list of voters for that session, then it calls http.delete. Our other test covers this line of code to make sure that it's working correctly, but we want to know that this piece of functionality is correct. We want to know that we're calling delete correctly. To know if we're calling deleted correctly, really what we need to know is that the right url is being called. So the eventID should be correct, the voterName should be correct, the sessionID should be correct, and they should all be put together in this string in this format. And luckily since we got Jasmine spies, we can actually test exactly that. Going back to our code, I'm going to say that it 'should call http.delete with the right URL.' So I'm going to copy and paste this code here, but of course I'm not going to repeat the same expectations. Now you may be wondering why we're repeating this code, duplicating this code here and here. It's the exact same code. Should we maybe move this up to a beforeEach before a deleteVoter, or even up to a common beforeEach? This is a little bit of a subjective call, but one of the things that a test should do is tell the story of what the test is doing. And part of that is any necessary setup that applies specifically to this test. So in our case, seeing what the session is that we pass in and how that mockHttp returns an observable, that could be useful. So for me, it's okay that these two lines of code are duplicated in these two tests. It makes our test, instead of being DRY, that acronym D-R-Y, don't repeat yourself, we do have a little bit of duplicated code, but tests are often damp. So they're not perfectly DRY, they can have a little bit of duplication. And I'm okay with this duplication. So after calling this, I can make my expectation, and I want to expect that mockHttp, its delete method, has been called with a specific parameter, so I can use the toHaveBeenCalledWith method, and I want to check that was called with a string that matches exactly what it should be. So in this case, the string is going back to our original code, I can copy this, I paste it in, and then replace the eventId with the eventId that was passed in, which is that three right here, the sessionId is six, which is given right here, and finally the voterName is "joe," which is this parameter here. So I can save this code and go back to our Karma command line. And now we have two tests executing successfully, so our delete function is being called correctly. Now what if we have multiple parameters, as in this case down here on this post, especially when we have parameters that are complex like this request options object. We can handle those, too. Let's write a test for the addVoter. I'm going to create a new Describe, and I'm not going to test that the add functionality works with this push. I'm just going to check with these Posts right here, I'm going to check that that's called correctly. So I'm going to copy and paste this test, except that we want to check that it calls post with the right url, and in this case I want to start out with not having two voters, just one, and I'm going to add one when we call addVoter. The signature is completely identical. We have an eventId, we have a session, and then we have a new voter, and now we want to check that mockHttp.post was called with the parameters that we expect. So again, the url is exactly the same. Go back over to here and see, this url is formatted exactly the same the deleteVoter. Api/events, the eventId, the sessionId, api/events/$(eventId)/sessions/$(session.id), then voters and voterName, matching up here, but the post method has the url, then an empty object. So that's easy, I can just add another parameter, an empty object, and then finally it also should be called with this options argument. Now there's a problem. This options argument is created inside of the code here. It creates a new requestOptions. I don't have a handle with that same exact object to make sure that that was the object that was given and passed in as the third parameter, but I can sort of punt on this. Instead of checking that this exact argument was exactly what was created up here, I can just assume that this is correct. This is not a very complex line of code, creating a requestOptions, so I just want to check that at least something was passed in some kind of object, and I can do that by using a helper method on Jasmine. I'm going to call any, and then I pass in the type that I want, and I want it to be any(Object), and this will just check that that third parameter was an object of some kind, not a specific object, just any object. So let's save those changes and go back to our Karma command line, and we see that we're getting an error, and the reason is we told mockHttp's delete to return an observable, but now we want post to return that observable. Save those changes and back to our command line, tests are re-running, and everything is correct. And that's how you can check that a mock method was called the way you expected. There's a practice exercise in this clip, so now would be a good time to do that.
-
Testing Components with Isolated Tests
We just saw how to test services, and now we're going to learn to test components. We're going to start off with the simplest way to test a component which is an isolated test, just like our service tests. In an isolated test, we simply construct the class of the component like we did with the service, and check the mock services that it requires, and then exercise the component's class. This works just great for testing the functionality the component has, although it does miss out on testing the interaction between the component and its template. What we're going to do is test our ngOnChanges function. There's a lot in this component we could test. We're just going to stick with the simple stuff. Now just a quick note: If you do have a filter parameter on this ngOnChanges method, go ahead and remove it. It's not necessary, it's not used at all in the method, so just go ahead and remove it. This ngOnChanges does quite a bit. It filters sessions and it sorts sessions, so there's a couple of good pieces of functionality that we can test, and it will be a good place for us to start and see how to do isolated tests with components. So of course, I've got to create my spec file, and I'll name that session-list.component.spec.ts. And as always, we need to start with our imports, and we'll import our SessionListComponent itself, and we're going to need that ISession interface again from the event model, and then we'll move on to our outer describe, and we're testing the SessionListComponent. Just like the service list we tested before, we've got to construct our component ourselves, so we'll create a variable for that. And the SessionListComponent requires two services, the auth service and the voter service. This list defines the mock services for both of those. And finally our beforeEach where we'll create the component, and we've got to pass through the mock AuthService list and the mock VoterService. And now we can move on to the actual tests. We're testing the ngOnChanges event, so there's that describe, and our first test will be to test that the filter works correctly. The setup we'll need to do is to initialize a few variables in our component. Going back to our component, we can see that it's got four different input parameters: sessions, filterBy, sortBy, and event Id. So let's set those four variables. Now I'm going to need an array of sessions here, and since we have to filter and sort, I'm going to need both the name and the level. Filtering is done by level. Sorting is done by name, if we use just a name sort. So we'll add in those two properties for each session, name and a level. And we don't want just one session in our list. We want a few, so we're going to copy and paste this. And we'll change the level on one of them, and change the names so they're each a unique name. Now we're still getting an error, and this is because of the type mismatch, so we just need to cache this to an array of ISessions. Okay, and now we need to set the filterBy input property, and we'll filter to 'intermediate.' So in our test, we're going to check that we've only got these two sessions after the filtering is done, and not this session. We do need to set the sortBy input property, otherwise the algorithm will throw an error, and then the eventId. This isn't actually used in the test. Nowhere in the ngOnChanges is the eventID used, but we'll set it just to be complete. And now we can take the action that this test needs to take, which is to call the OnChanges function. So this component is actually running in production. The OnChanges event happens automatically, but here in the test we've got to call it ourself. And now we can set our expectation. So what should happen given these sessions and this filter here to the component? Well, the filterSessions ultimately sets the visibleSessions property, the sessions themselves remains unchanged. It's the visibleSessions that gets changed. So go back and set an expectation that our visibleSessions length is two. And of course, we could write more expectations to make sure that the beginner session isn't one of the two sessions. We won't do that; this expectation will be good enough. So we'll save our changes and go back to our command line, and all four of our tests are executing successfully. So there's our first isolated component test. While we're at it, let's write a test to check that sorting works correctly. We're just going to copy and paste this test, change that to sort, and we want to set the filter to all, so that nothing gets filtered out. We are sorting by name, and so what should we check in our expect statement down here, to make sure that the sort has happened correctly? Well, these three names, session 1, session 2, and session 3, are very easy to put in alphabetical order. So let's mess up the order in the initial version, and then check that the order is correct in the resulting version. So let's change session 3 to be the second one and session 2 to be the last one, and now we can set an expectation that our component.visibleSessions, the last element, and we'll ask for its name, should be Session 3. If these were sorted correctly, then the third element, which is the element it is next to, should be session 3 and not session 2, which is what is in the Sessions array. So let's save that and go back to our command line, and we are getting five tests succeeding. So then we have a couple of simple isolated component tests. These are great for checking functionality like this, and of course there is some functionality that they can't test that we need integrated tests for, and we'll see that in the next section.
-
Summary
In this section, we learned how to use Karma and Jasmine to test our Angular code. We saw how to test services and how to mock dependencies, and then we learned about isolated component tests. In the next module, we'll build on this and learn how to write integrated tests for our components so that we can test their templates as well as their code.
-
Testing Angular Components with Integrated Tests
Introduction
In this module, we will learn how to go beyond isolated unit tests, and instead, test both our component, and it's associated template using integrated tests. Integrated component tests are the only thing we're going to cover, but there's plenty to learn here. We'll start by looking at how to set up our integrated tests. It takes quite a bit of work to get our system set up to run these kinds of tests. After that, we'll see how to actually test a component and its template. Then we'll look more into mocking services, and then we'll take a look at the difference between deep integrated tests, and shallow integrated tests. In this section, as we covered integrated tests, you're going to see that they are much more complex to set up than isolated tests. There's good reason for that. When doing an integrated component test, we're not just testing the class of our component, we're testing it in a very close approximation of running in our live application in the browser. This is accomplished with a very important utility called the TestBed. Unlike isolated tests, where we construct our component ourselves, in an integrated test, the angular framework will construct our component for us, using the TestBed. It will also integrate the template for the component, and wire everything up. The other thing it does, is construct a module for us to be used in the test run. This is required for the component to operate in a realistic environment. You'll see all this in the code that we write in this module. As we do this, and you see how complex this can get, you may wonder, "Why do this at all? "Aren't isolated tests good enough?" Well, in many cases, they can be. Testing simple bindings can often not be worth the effort, but there's certainly cases that we get templates that are so complex that testing them with the unit test is very valuable. Also remember that the test will fix the functionality in place, that way as we fix bugs or add new functionality we can be sure that we don't accidentally break any of our template bindings. This is both good and bad, since it may make it more difficult to change our code, but it can also make it more difficult to introduce bugs. You'll have to decide for yourself how valuable this is for your project, and indeed for each component.
-
Setting up for Integrated Tests
We've written some isolated tests for our sessionless component. Now it's time to write some integrated tests so that we can test that it interacts with its template correctly. Normally, on a component, you will either write isolated or integrated tests. You could write both, in which case you'll need to worry about how to name your files. If you are going to write both, my recommendation would be that you name the files isolated.spec.ts, and integrated.spec.ts. Although generally you won't be doing both. So in that case, just spec.ts works fine. Since we're writing both, I'm going to rename this file to isolated.spec.ts. That way, you'll have both kinds of tests in your project, so that you can refer to how the different kinds of tests are set up. We'll create a new file, and I'm going to name this .integrated.spec.ts. Again, I'm only doing this because we have both kinds of tests. As usual, we'll start out with some imports. But this time, we're going to be importing a lot more stuff than we did before. We're going to start with some imports from a special library that we haven't seen before. And this is angular/core/testing, and this has quite a few things that we want to bring in. We're going to bring in the TestBed. This is the utility that we use in order to set up our whole integrated test. We're also going to bring in async, which is a helper function that we'll use when we're setting up our test, and ComponentFixture, which is a type, alright? We've also got to import the DebugElement, which comes from just angular/core. This is another type that we'll be using in our test. Of course, we need our SessionListComponent, and remember, our SessionListComponent has two injected services, AuthService, and VoterService. With an integrated test, we've got to import both of those types. We won't create real instances of those two services, but we need their types. And VoterService. I also want that Isession interface again. And one last import is going to be a utility function. Which I'm going to get from angular/platform-browser, and that is the By utility function, and we'll see this later on. So there's all the imports that I've got. Already you can see that this is a lot more complex just with the number of types, and classes that we're using, than an isolated test was. Here, the setup was so simple, we just needed the component, and maybe any types that we might be using. Whereas, to set up an integrated test needs a lot more pieces. Let's move onto our outer describe. And there's several variables that we need to create. We need a fixture for our component. The fixture is a wrapper around the component, it's not the component itself. In our isolated test, we just created the component. But in an integrated test, the fixture's a wrapper around it and gives us some access to a few pieces about the component that we wouldn't have normally, such as, it's change detection, or it's dependency injector, those types of things. The type on this is a component fixture of type session list component, which matches with the component that we're creating. Then we've got our component itself. We've got the native element, which I'll just call element, and that's an HTML element. That's not a type that we imported because this is a global type. And finally, we need a debug element, and that's that debug element type we brought in. This is a wrapper around the native element, that again, gives us some functionality that isn't just available on the underlying native element. And now we can write our before each where we create our testing module and our component. Now when we set up our module we're going to use a function called TestBed.configure testing module which is asynchronous. We need that to run synchronous so we're going to wrap this in this async function. This is a utility function provided by Angular that utilizes the magic of zone.js in order to make this function execute synchronously even though it is inherently asynchronous. How that happens is beyond the scope of this course but just understand that this is a necessary piece when setting up our module and creating our component for an integrated test. Inside of this before each we first need to create our two services, the AuthService and the VoterService. Again we're going to use mocks of these, so I'm just going to create a couple of variables. And I'll just use empty objects for now as they require functionality we'll give them functionality. Do the same thing for our VoterService. And finally we can configure our testing module. This is where we use that TestBed utility that we brought in. And we call it configureTestingModule function. The configure testing module function works very similar to how we created our module. If we go to our app.module file we can see that when we create a module we have imports, declarations, providers for services and then this bootstrap. We don't need this bootstrap section in our testing module but the other sections we can utilize in setting up the testing module. And we do it exactly the same way so we create imports, which is just an array. Right now we're not going to import any other modules but if we were testing say a form, we would need to import the forms module. We've also got declarations and since we're testing our session list component we've got to put that into our declaration section so that this module will contain that session list component Then finally providers for services. Now we have two services that we need this module to know about so that it can provide it to the SessionListComponent when it gets created. Going back to our SessionListComponent, that is the AuthService and the VoterService. So we've got to create providers for those. Of course we don't want to use the actual AuthService or VoterService because those make http calls and we don't want to make http calls. Instead we're going to utilize what we learned previously in this course and set up this module to provide something different when the SessionListComponent asks for the AuthService and the VoterService. We do that with the long hand form of provide and when somebody wants an auth service we are going to give them the mock AuthService. This is exactly the way that we provided third party components to services and now we can do that here when we're creating a testing module. The same thing for the VoterService. And one more section that we can configure into our module is the schema section. We're not going to use that now but we're going to create it and just leave it as a blank array. So our testing module has now been configured. It's created a brand new module that has only one component, our SessionListComponent. And only knows about two different kinds of services, the AuthService and the VoterService but of course it's providing mock values for both of those. This before each is done but now we still don't have a handle to our component that's been created. In our isolated test, we initialized the component inside the before each. We're going to want to do the same thing. Although we don't want to use this before each. This before each is asynchronous and it needs to complete before we create the instance of the component. So we're going to put that into a separate before each. Unlike the other one we don't have to wrap this in async but here we can actually create our component. And we create a component by calling TestBed.createComponent and pass in the type of component we want to create. And what that produces is a component fixture. So we're going to assign that to our fixture variable. Once we have the fixture we can get a handle to the component. And that's the component instance property of the fixture. We can also get the debug element. And we can get the native element. And now we're ready, we've got a component created. We've got a handle to the fixture, the component, its debug element and its native element, we're ready to write tests for it.
-
Testing Components with Deep Integrated Tests
We've got our set up ready and now we can write the first integrated test for our component. Of course we need a describe to wrap it and we'll just call this initial display. We're going to be testing that the initial display of our component is correct. And the first test I want to write is that one of the initial bindings is correct. Let's look at our HTML for our session less component. We see that what we've got is a list of sessions and each session has a well title and a well body, and inside the title is the name of the session. So let's write a test that this binding right here, session.name, is correct. We need to initialize the sessions for our component. Just like with our isolated test, we've got these four input values, the sessions, filter by sort by an event ID, we need to set those as well in our integrated test. So we'll set the sessions property. In this case the session that I want to create should have pretty much all the properties that it needs, since we've got the real component and it's displaying data out to the HTML. A lot of this data is displayed, presenter, duration, et cetera, and if we don't include it we could get some errors. So we'll create a session that has all the session properties. We have an ID, a name, a presenter, a duration, remember duration is a number, one, two, three or four, a level, an abstract, and then an array of voters. We've also got filter by, we'll just filter to all, sort by, we'll sort by name, and event ID. And there's the initialization for our component. Again should we keep this inside of the test or should we put it in some kind of a before each? This is really up to you as to how the test that you're writing work and what you think makes sense to put into the beginning of your it function. Then we're going to change the state of our component. In the case of our SessionListComponent, all the magic happens in ngOnChanges, that's where the visible sessions property gets set and without that visible sessions property getting set, right up here, there's not going to be any HTML displayed because this ngFor iterates over the visible sessions and that's the containing div for our entire template. So we've got to have visible sessions, therefore, we've got to have the ngOnChanges event fire. Now this works a little bit different when it comes to an integrated test. If this was the ngOnInit event, then this would run automatically when using an integrated test. But this is the ngOnChanges event, and that event fires when one of these input properties is changed. But here's what's tricky about that, you may think that because we're setting those input properties here that Angular will know about that and fire that event. Unfortunately, that's not how it works. For those input properties to be changed and fire the ngOnChanges event it has to be changed from a parent component setting a child component and that's not what we're doing. We're setting these values directly so Angular doesn't know that they've been changed therefore, it's not going to fire the ngOnChanges event for us. So we've got to call that ourselves. And that's simple, we call ngOnChanges. Again if the magic was happening in ngOnInit, you wouldn't have to call this on your own. Now that we've got the changes event fired, we want Angular to render out this template. Now that visible sessions has some values it could render all these divs out. In our case, we only have one session, so we're only going to see one div. But until this got called, Angular didn't have any sessions at all, the visible sessions property was empty So we want Angular to update the HTML and update the bindings and we do that with a call to detect changes on the fixture. And what this does is it runs through the change detection cycle taking every value that the component has, such as these values, and the values that were set when we called ngOnChanges, which would be the visible sessions property, and then re-render all those changes out to HTML. So Angular will render those changes for us and we can now set expectations on the results. Of course what we're looking for is that our session title is correct. So we want to ask the native element, which is going to be the wrapping element, this element is a handle to the root level dom node, and what we want is this div right here, the div with an attribute of well title. But since this is an HTML element, it's got a query selector This function allows us to query this element for one of its children, and we're looking for the element that has an attribute of well title. And with that selector we have a handle to the very first element that matches and we can get its text content. The text content is going to be the contents of this div as text. So therefore, it's going to have the session name in it. And we can check that that text content contains our session name, which is session one. We use to contain instead of to be because this text content might have more pieces then just this binding. And it's just a lot simpler to use to contain instead of to be. Makes our test a little less brittle. So let's save these changes and we'll go out to the karma command line and see what happens. Okay now we're getting an error, and this is very important. Let's look at what the error is, we're going to scroll to the top. And it says it can't bind to count since it isn't a know property of upvote. Let's go back into our template and we can see that we've got this upvote component. Well in our integrated test we only told the module that we have a session list component, not an upvote component. So even though this error is saying that it can't bind to the count property, since it's not a know property of upvote, what it's really saying is, it doesn't know about the upvote component. We need to bring in that component and declare it so that it can be rendered along with our SessionListComponent. So going to import the upvote component and then we'll go down into our module and we'll add it to the declarations section. We'll say the changes go back out to karma and we're still getting an error but now it's changed. The pipe duration could not be found. Well again inside of our template we're using this duration pipe and since we created it Angular doesn't know about it if we don't add it to the module. So once again we've got to bring that piece in as well. And we'll add that to the declarations, save back to karma, we've got one more error, this time it's collapsible well is not a known element. This is more typical when you've forgotten to include a component in the module. And we'll bring that one in as well. And we'll add it to the declarations and save and back to karma. Now you may not see what I'm seeing, if you're getting some strange behavior like your karma is reporting that it's disconnecting or showing you zero out of six tests run or something strange like this, then you're experiencing a problem with karma in conjunction with the CLI. It's really a bug that's likely specific to just a particular combination of a version of the CLI with a version of karma. In that case, we need to go to our package.json and we need to add this flag to our NPM test command. I'm going to add the - - sourcemaps=false and then stop and re-start our tests. Basically what's happening is the source map setting is actually hiding what the real error is. Once we re-start, at that point you should see this error somewhere in your karma output. It's a new error about our AuthService but it's not about a missing component. So we've brought in all the components that we need. Bringing in these components like this, adding them to the declarations for the test module, having components that are more then just the component we're testing, this is what makes this a deep integrated test because our session less component not only uses an upvote component and a duration pipe, it also uses the collapsible well component. So this has several child components that it uses. That makes this a deep integrated test. We're using the live versions of these child components. If we didn't want to use the live versions of these child components it would be a shallow integration test, and we'll show how to do that later on.
-
Creating Mock Services
So let's go back to our error and see what the error is. Type error is authenticated is not a function. And that's on the AuthService so we need to go back into our test and we need to make our mock AuthService have an is authenticated function. Now I'm just going to have this function return true every time That just makes it simple. So I'll hit save and we'll go back to karma. Now we're getting a different error, cannot read property userName of undefined. So let's figure out where is userName utilized. If we do a find in here for userName, we don't find it. So let's go to our component and find userName and we can see that it's utilized in the toggle vote method, it's also utilized in the user has voted method. User has voted itself is called inside the HTML as a binding on the voted property. So what we're doing right now is we're just figuring out what's wrong and making a fix and adding support for that functionality to each service. We could go the other way, we could just walk through and see what each service does and write out fake functions and properties for that. So we could look at the entire signature of our AuthService, we could see where it's utilized and what properties are being used on it, what methods, et cetera, fill those in and then run our test, or keep running the test and solving the problems as they pop up. How you do it is really up to you. So this needs a userName but the user name again, is on the current user property, so it needs a current user, which is an object, and that object needs a userName property. So while we're at it, let's look at our VoterService and see where it's utilized. Okay here the delete voter and add voter methods are called but those are only called when we do a toggle vote and we're not toggling the vote in this test, so we don't have to worry about that. User has voted, it's user has voted function is called and we know that this is called in the template just in the plain bindings, so we need to implement this user has voted method. And if we scroll down those are all the methods or properties on the VoterService that we need to implement. So let's go back and implement a user has voted method. We'll save those changes, go back to karma. And now our test is running successfully, verifying that yes indeed this title is set to Session 1. There's a practice exercise for this clip if you want to do that now.
-
Using DebugElement
Now I want to show you something very quickly before we finish up with this part and that is that here we're using the native element to find the element that we want. There's another way to do this, we can use the debug element instead. The debug element has a query function and that query function takes in a predicate. That's why we imported the by function right here from platform browser. So we call by. and then here we're going to use css and now we can give it the same css selector, well-title, and this will find from our root element an element that matches the css selector given here. Once we have that we still want to check that text content property, what this gives us back is a debug element so we've got to get the native element and then we can ask for its text content. And the rest is the same as our expectation up here, we just call toContain Session 1. Let's comment this out and save and go back to our test. And we still got success. This works exactly the same way using the debug element as it does with the native element, whether you want to use the raw dom query selector or use the Angular utility query with the by function, which lets you select off of a few other things. We can query by css, we can also query by directive and get an element that has a specific directive on it, that's sometimes very nice. Those are the two that you'll typically use. So it's really up to you how you want to find the right element when checking it's title but both methods are available to you.
-
Testing Components with Shallow Integrated Tests
We've created a deep integrated test, whereby we had to bring in the upvote component and the collapsible well component and we had to use the live components. This is fine for our specific scenario here but imagine a scenario where the upvote component or the collapsible well component or some other child component actually did a whole lot of work and really messed with things and you didn't want that going on inside of your test. You just want to be testing this session less component and not it's children. In that case we want to write a shallow integration test. There are basically two strategies for writing a shallow integration test. The first one is to replace this upvote component and the collapsible component and any other child components with fake components. In that case we would go up here into our describe and we would create new components to have the same API but don't have any functionality. And we would do that of course by just importing component, creating a component and the class for it, and we wouldn't import it of course since we're creating our own class. Unfortunately this can be kind of troublesome. Let's look again at our HTML and see the upvote component. It has a vote event that it binds to, a count property that it binds to and a voted property that it binds to. So already we've got to create quite a bit of API to make this component look the same as the existing component. So creating this fake component isn't super simple but that's certainly one method that we can do. The other method is just to tell Angular to ignore this component, this upvote component and treat it as a dead HTML element. Normally Angular won't do that, it'll throw an error when it finds HTML elements that it doesn't recognize, but we can force that upon it. So let's delete this code here, and we're going to go up to Angular Core, and we are going to import another symbol which is no errors schema, this right here. And if we go down into our configuration for our module and in the schema section we add that value, we have now told Angular don't worry about HTML elements and properties that you don't recognize. Therefore we can get rid of the upvote component, we can get rid of our collapsible well component and Angular won't complain at us when it goes and compiles this template. When it encounters this node and this node, it's not going to say, hey these are unknown nodes. Now I'm going to leave the durations pipe in there because that's used to render out the date and so I might want to check that the date is being rendered correctly so therefore, I do want the duration pipe. Generally pipes are so small and simple that including them is rarely going to cause a problem like a child component might. So we're going to leave that in, we've commented out these two components, they're not going to be part of the module anymore, but because we've added this no error schema it's not going to complain at us. I'm going to save these changes and we'll go out to karma. And it's picked up the changes and ran the test again and our tests are still successful. We're not getting the error messages that we were getting before. So that's what this no error schema does. There is a drawback to this. If you are test driving your code, you might be authoring in something into your template. Let's say for example that I was going to build a new piece of functionality and I wasn't running my application live, I was simply using my tests in order to guide building the application. And I came in here and added say an input box, and I wanted to set ngModel on it. Well that's fine but my problem is, I haven't imported my forms module which is where ngModel exists. Because I've set this no error schema Angular's not going to complain at me and say, hey I don't know what the ngModel is. If I was running my application live or running a test without this no error schema I would get an error here that says, ngModel is not a known property and that would prompt me to remember, oh I've got to bring in the forms module. So be careful when using the no errors schema because it can hide other problems. But it can be very valuable when doing component tests because it makes it a lot easier to test just the component and not its children. Now we're going to finish up our section on unit tests but before we do, we want to discuss and compare a little bit between integration tests and isolated tests. Notice that right here, for this integration test, I spent quite a bit of effort just to check that my title was correct. Now if I really want to check that my initial display is correct, not only would I want to check this title, I'd want to check that the presenter is correct, the duration is correct, the level is displaying correctly, all of these bindings. So therefore, I would either have to write a whole bunch of these should have the correct blank or I might write a test that says, should have the correct initial display and then I'd be writing five, six maybe even 10 expectations, just to check and make sure this initial display is correct. That's a lot of expectations and a lot of code and it makes this one test very big, very complex and therefore, very brittle. So be careful when writing integration tests that you don't overdo it, that you don't either write too many tests or write tests that are just too complex and have too many expectations on it. What's right for you could be different then what's right for somebody else and it might require some trial and error to find the right level for you but keep that in mind, especially when dealing with integration tests that it can be very tempting to test everything, every little tiny piece of the HTML and therefore, end up writing way too many tests. Like so many things in programming, writing good unit tests is not a simple process, it requires a lot of thought and judgment. So keep that in mind when writing your tests.
-
Summary
In this module we saw how to test both the class and the template of a component using an integrated test. Integrated tests require a lot more set up than isolated tests but they're the best way to test your components working with their template. We saw two types of integrated tests in this module, deep tests and shallow ones. Deep tests let us test both the component and any component it uses in its template, plus directives and pipes. A shallow test favors just testing a single component in isolation. Both of these types of tests are useful depending on the situation.
-
Taking an Angular App to Production
Introduction
In this module we are going to learn how to take our Angular app to production. This is a critical, final piece of this course. To take everything that we've done and be able to put it some place that our users can actually get to the application and benefit from it. The topic of going to production covers quite a few different things. First we're going to look at linting. Linting is a common task we'll want to do to our code as we're building our code and certainly before we take it to production. We're going to look at how to use linting with Visual Studio code, which is by far the most popular editor for Angular and how to do it just from the command line so it works no matter what editor we're using. After that we're going to talk about how to actually create a build using the CLI. We're going to look at build flags and the ahead-of-time compiler. Then we'll look at actually deploying the build we created to a production environment. Finally we're going to talk about optimistic module downloading, which is an important part of tuning our applications for production use.
-
Linting Overview
In this section we're going to take a brief overview as to what is linting. First, let's define the word linting. Linting is essentially finding the lint in your code and the little tiny bits that don't necessarily cause major problems, but might cause small problems that might build up into bigger problems. That's a nice little definition, let's actually really dig into what is linting. Linting is pointing out and fixing potential problems in your code. Mostly it's the coding style changes, rarely is it going to be something serious because most editors will point the serious issues out. In the case of JavaScript, because there's so many ways to construct your JavaScript, there's actually a lot of value in linting. For example, it might point out to you missing semicolons. JavaScript often works without the semicolons, but there are a few places where not having the semicolons can get you into trouble. It'll point out double versus single quotes if you're using them inconsistently. Linting lets you pick a standard and decide that we are only going to use double quotes in this project or single quotes. It'll point out lines of code that are far too long and it'll find a ton more issues that are very similar to things like that. Lots of small, even somewhat nitpicky issues. Let's look at linting in action. We've got a little bit of code here that declares a variable and then logs out to the console. If we were to run this through a modern JavaScript linter, like Yes Lint, it would find a few problems. For example this bar could actually be a const. The x is never reassigned or changed to other values, so we could set this as a const. That would be slightly safer than declaring it as a variable. Next, we've got a mix of double quotes and single quotes and most linters default to using single quotes. Of course you can configure that and then it would point out that the single quotes that we use later on in that same line are the problem. Finally we've got a missing semicolon right here. This is what a linter would point out to use. Some linters will just point out the problem, some linters allow us to actually automatically fix the problems, depending on the linter and the settings that we give it. Now of course you might look at linting and feel like it's not really very important, it's just pointing out code style problems and who cares about that. But there are a few benefits of linting you should consider if you're not already doing it. First it generates consistency across your team. Second it can actually catch some potential errors. Third it has automatic error fixing which really handles a lot of these issues for you. Finally, you can set up CI or commit hooks so that linting is actually automatically done to your code or commits are rejected if they don't pass linting. There are a few other things you should be aware of when talking about linting with Angular. First off, there is a difference between linting JavaScript and linting TypeScript. TypeSCript is a super set of JavaScript, so that means that there are more things going on in TypeScript than a typical JavaScript linter is going to know about. For that reason, when dealing with TypeScript, use a linter called TSLint, which is the TypeScript linter. It still handles all your typical JavaScript linting issues, but also has additional ones specific to TypeScript. Finally, a lot of people complain about linting because there are so many choices to make, there are so many things you can do to your linter to say do support this, don't support this, point out to me when this happens, but not when this happens, et cetera. There's been a recent push to use more opinionated tools than your typical linting tools and one that has arisen and become very popular is called Prettier. You can use Prettier with TypeScript and with your Angular code. We're not going to talk about Prettier specifically in this course, but everything that we say about linting applies to Prettier, other than the fact that Prettier handles all the fixes for you automatically and has very few things that you can configure, but otherwise the process is basically the same.
-
Installing TSLint in VSCode
This course isn't necessarily about VS code, but VS code is one of the most popular editors used to write Angular, so we're going to look at how to install TSLint in VS code. I'm going to open up the extensions view, which is right here, and we can see our list of installed extensions and a list of recommended extensions. I want TSLint, it happens to be the top recommended extension, but if it's not, you can just search and find it this way. Then I'm going to click install. Once it's finished installing we're going to need to reload VS codes, so I'm going to click reload and now we've got TSLint working.
-
Using TSLint with VSCode
Let's open up our session list. All of a sudden, over on the right hand side, we see a whole bunch of red marks. If we scroll around we can see these red marks indicating problems in our code. We can figure out what the problems in our code are by hovering over these little, red, squiggly lines. Here we see that we're missing a semicolon, we can fix that very easily, and then the squiggly line goes away. We can actually put our cursor here and it pops up this little light bulb. If we click that, it will show us, one, we can disable this rule or show documentation. For the component selector, it doesn't like this component selector. I'm not going to fix that right now. Here's another issue, missing white space. Let's click this and we're going to say we want to fix the missing white space issue and it automatically fixes that for us. Some of the issues can be fixed automatically, like white space. Notice we've got the same issue, both here on line 29 and on line 34. If I click the light bulb and select fix all, missing white space, it fixes both issues. Again notice on the right hand side, the red marks are going away. Now let's scroll down here and go to the next issue. This one is if statements must be braced, curly braces, it doesn't like the non-curly braced if statements. This is an example, just like the component name rule, of one of the drawbacks of TSLint, which is I have an opinion that I like this code and TSLint has an entirely different opinion. If I click here and click the light bulb, this is actually the missing semicolon issue, but you can see I can fix all missing semicolons. Let's do that and I've still got the issue of if statements must be braced, I click here again and click the light bulb. Now I can either disable the rule or show documentation, it doesn't have an automatic fix for this. Again, some issues have automatic fixes, some don't. In VS code there is a keyboard shortcut on windows, it's control period, I believe on Mac it's command period, which gives you the same menu. You can select which option you want from the list of possible options. Again control period and now I can do the fix or I can disable the rule. Let's show what disabling the rule does. It actually puts in a comment, so that TSLint will ignore just this one instance of the rule. Of course I don't want that, instead I want this fixed, so I'll fix it. Now let's take just a quick minute and talk about the warnings like these. This warning that if statements must be braced, which I have my own personal disagreement with. Of course, depending on what your team decides, you should follow those guidelines, but if you don't like a particular issue, like this or there's a rule that says lines should not be over 140 characters or maybe you don't like semicolons and you don't want TSLint to put semicolons in. You can actually configure TSLint using a TSLint configuration file which is "tslint.json". That's created by the CLI when it creates a project for you. When you open that up, what you're seeing here might be slightly different depending on what version of the CLI you're using, but this note here, this rules not, that's where we have all of our rules. Let's find the curly brace rule. We go back in, we hover over that and we see, "tsline else statements must be braced" and in parentheses it says curly, let's look for curly. We see that we've got a curly rule that says true. If I change this to false and go back in, notice the warning is now gone. TSLint no longer cares about that issue, so we can configure TSLint and if you go back and look into the TSLint file and just scroll around, you'll see that there are lots and lots of different rules and lots and lots of customization options. This is both part of the power of TSLint and also a common complaint that there's so many choices to make, which is why some people prefer a more opinionated tool that has fewer choices, such as Prettier. Getting to know which rules you like and which rules you don't and how to turn them on and off is way outside the scope of this course, but it is important to know that there is where the configuration file is and, for the most part, it's pretty straight forward to come in here, look around, figure out what something means, and make the changes. Often times you don't even need to go to the official documentation, just like with that curly rule.
-
Linting from the Command Line
Now that we've seen how to use TSLint inside of VS code, let's learn how to use it for the command line, which will work with any editor. There is a very simple CLI command that will actually Lint your code using TSLint and that is ng space lint. If we execute that, we're going to see a lot of linting warnings. Notice that a lot of these are missing semicolons. If you scroll through our current state of the project, there are lots and lots and lots of TSLint errors. Fortunately, a lot of these can be fixed automatically by TSLint and we can get the CLI to do that for us by simply typing in ng space lint space dash dash fix. Now a warning before we execute this command, this can take quite a while, up to several minutes because there are just so many linting issues that it's going to take TSLint a long time to do this for our whole entire project, which is a good reason why linting is something you should do when you start and you should keep up on. Let's go ahead and execute this command and now that it's completed we can see there's a whole bunch of output. For example, it's telling me what the actual errors are, identifier session is never reassigned. It's giving me the same linting errors, but it's actually fixed a whole bunch of errors and if we scroll around we can see, for example here, that it has actually fixed the errors in these files. Now, when we run TSLint, we're going to see a much, much smaller set of issues and if we open up our project, we'll see a much smaller set of issues in each file. That's how to run TSLint from the command line.
-
Going to Production - Overview
In this clip we're going to take a brief look at an overview of what it means to take an Angular project to production. Taking an Angular project to production centers around one tool, which is the CLI's build command. We have to build our application in order to take it to production and that's very easy to do with the CLI's command, ng build. Let's talk about the ng build command and all of the things that it does. From a high level, what ng build does is produce a deliverable code package. Just like lots of other languages and technologies, with JavaScript we've reached a point where we've got lots of JavaScript files and a nice deliverable code package and make optimizations for them as well so that execution while in production is both fast and takes a minimum amount of resources. Let's look at what those production optimizations are. First, it turns development mode off. Development mode is simply a little setting, in Angular, that double checks that you aren't doing anything strange with change detection. For more information you can check out the official documentation. Next, ng build will bundle up your code. We'll look exactly at what that means in a second. It also will minify your code, we'll look at that as well. It tree shakes your code, we'll also look at that. It will do dead code elimination, which is related to tree shaking. Next is asset inlining. This is a process where small assets, such as css files and images are actually bundled up together and inlined into your JavaScript code so that they require fewer requests and have less overhead and therefore your application is, again, a little bit more performant. One of the major jobs of the ng build command is to execute the ahead-of-time compiler. With newer versions of Angular, this is actually happening even when we're running in development mode, but it's an important step and we'll actually look at what happens when we run the ahead-of-time compiler later on in this course. Let's start by looking at minification. What exactly is minification? Well we start here with an example piece of source code. A little order service class that has a couple of methods. Notice that there's lots of white space. A minifier will actually take this code and remove all things that don't matter for the JavaScript compiler, such as the browser, to actually process and understand the JavaScript correctly. What we actually can do is turn that code on the left into the code on the right. We've gotten rid of white space, we've smooshed it all together. It's a lot less readable to a human, but to a computer this is just fine. There are even some aggressive settings you can turn on with a minifier that will do things like rename your parameters to use shorter names and rename identifiers and other similar issues, but of course that's way beyond the scope of this course. It's just important to understand what minification is and that the ng build command actually minifies your code. Next we have bundling. This is another step that will optimize applications for production. In development we deal with separate JavaScript files. We might have one for the order service, one for the order component, and one for the line item component. Of course this is just for programmers. Computers don't need all of these files to be separate, so bundling is the process of taking all these JavaScript files and sticking then all together in one big file so that we have less requests going over the wire and less overhead as we deliver our code down to the browser. Finally we have tree shaking. This is one of the more recent additions to code optimization for production in the JavaScript world. The basic concept is to remove code that isn't ever called in your system. Imagine a system where you've got a service called the user service and it has two methods. Get users and get user. Now further imagine that in your system, this service is only used in one place and only the get users method is called. The get user method is never called anywhere. You wrote it, but you never used it. There's no reason for that method to even exist on the class anymore. For all intense and purposes, you can remove it. But say that you think that sometime in the future it may get used or perhaps it's in a library that's used by multiple applications and other applications use that method. So you can't just delete the code, but when you deploy to production it would be nice if that code didn't even make it into the production bundle. That's tree shaking. Tree shaking can actually remove methods and properties that were never called. Now I also mentioned dead code elimination in a previous slide. Tree shaking and dead code elimination are two aspects of the same task, trying to only get the code that's actually used into your output bundle. The difference between the two techniques is a little bit academic and not really worth worrying about. But suffice it to say that the CLI is doing its best job going through and trying to get rid of any unused code so that your delivery bundle is as small as it possibly can be.
-
Angular's Ahead of Time Compiler
Before we actually create our first production build, we're going to talk a little bit about what is Angular's ahead of time compiler. This is conceptual stuff that you don't have to understand in order to work with Angular, but it is nice to know about. As a simple definition, the ahead of time compiler is a process that runs during Angular's build that handles turning templates into JavaScript code and checks to make sure that the components that use those templates aren't doing anything that would cause problems in production. There are a lot of benefits to the AOT. First off, it makes rendering faster. The AOT turns your templates into optimized JavaScript functions. Doing that during the build time means there's one more process that's not happening during run time, which means that rendering out your templates and rendering out your UI to your user is going to be faster. Second, it produces a smaller download because the parts of Angular that need to go down to the browser are reduced. Third, it will detect template errors. There's some things that you could be doing in your template that could cause problems and the AOT will detect those. Finally, it's better security. The AOT plugs some security holes. The details of which are beyond the scope of this course, but it does improve the security of your applications. The biggest job of the AOT is compiling templates. When Angular runs in the browser, a big piece of what goes on is the template. Ultimately, the code that you write needs to show something in the browser so that the user has some interaction. That means manipulating the DOM. We author templates in HTML to let Angular know what needs to be done. We use bindings, events, directives, and components in our HTML templates. This HTML is useless as it is, as we author it. It has to be turned into JavaScript instructions so that the browser can actually manipulate the DOM and render something to the user. That process is called the Angular compiler, which takes the HTML from your templates and turns it into the JavaScript instructions that manipulate the DOM. This is the main job of the ahead of time compiler.
-
Creating your First Build
Let's look at how to run an actual build and see what is produced. First we're going to look here in our package.json file. Here we can see that we've got a build command in our NPM scripts list. The build command actually executes ng build with this --prod flag. Let's not run that just yet. Instead we're going to run a build without the prod flag. Let's go out to the command line and we're going to type in, "ng build" rather than doing "npm run build" we're going to do ng build. This will run that build command without the prod flag. Again you have to have the CLI installed globally in order to do this. I'll go ahead and execute that. Then when that's done we can see that it has given us a list of assets that have been produced. These have all been put into the DIST directory. Let's go over to our project and we're going to open up that DIST directory, this is where everything was produced. We're going to look at what was actually created. Let me open this up so we can see everything. Notice that it's added in any fonts that we use and GIF icons. We've got our index page and then we've got a various number of bundles. Something dot bundle dot js and a map file for it as well. We've got quite a few of those, including a vendor bundle. Then our user module chunk. It's not really important to understand what's inside of each of these, only to note that we've got the index study HTML that's been produced and that points at all these bundles. If we open this up, we can see that it's actually got links to each of the bundle files. This is our delivery mechanism. We render this index dot HTML from production and that is what kicks off our entire site. One other directory we didn't look at is the assets directory, that's going to have our images in it and sometimes if we have other assets they'll go in here as well. So there's the result of what is produced by the ng build command, it's the entire contents of this DIST directory here. That's what we want to deploy to our production server. In the next section we'll see an example of how to actually serve that up.
-
Basic Deployment
Before we look at a del of actually serving up our build directory, that DIST directory, let's talk about what the process is when we're going to do a deployment of our project to a production or staging server. The deployment process is really very simple. Just take the result of that DIST directory, all the contents of it, and we copy that up to a target server and on that target, where we copy it to, is going to be some kind of a web server that's pointed at our DIST directory. When somebody requests a file, it will serve up the files whether it's the index.html for the default route or if it's one of the images, it will serve up the image. Of course it doesn't need to be a physical server, this could be the cloud, this could be the same machine, in fact when we do this we'll be using the same machine and the same directory. The point is, once we run ng build, we just copy that directory to where ever it needs to go.
-
Demo: Serving up a Build
Let's actually run our built directory through a web server so that the server is being run from that built directory, from the DIST directory and that's what's getting served up and those are the files that are coming down to the browser and now what's getting served up to the CLI is the development web server. To do that, all we have to do is have that web server running that we built in order to just run the API because when Jim and I built that web server we also built it to serve up the DIST directory for regular files if they exist, but only on that part. It's a little bit complex, let me explain it. First we need to start up that server npm run server. Of course this is only if it's not already running. When we launch that we can see that it's listening on port 8808. With that running we're going to go out to a browser and instead of going to local host port 4200, we're actually going to go to port 8808. Notice that our site has come up on port 8808, it's the normal, regular site. This is our application just as we had built it. If we go around and look around, all the data is going to be the same. This is exactly what would happen in a production scenario. We simply take our DIST directory and we move it to a target server. Then we have a web server that's pointed at that directory and when a browser hits that web server, it just serves up that index.html file from the DIST directory. We didn't take the step of physically taking that directory and moving it somewhere else because the web server that we've got running on port 8801 is already looking at the existing DIST directory where it's at, but that is how we would do this in production. Now let's compare what this is doing to what the CLI is doing. Go back to a different command window and I'm going to launch the CLI's DEV server with npm start, which ultimately just runs ng serve with our correct proxy settings. I'm going to wait for that to finish spinning up and we'll head over to the browser and open up a new tab, but on port 4200. Again we see our application running, same exact application. Now let's just look at the difference in what is delivered between our CLI driven version of our application and the one that is served up from the distribution directory. I'm going to open up the console and go to the network tab and I'm going to refresh this page so we can see exactly what is being delivered when we load the application. Looking down at the bottom you can see we've got 17 requests and almost 14 megabytes transferred. Now don't pay attention to these numbers, they're going to change as Angular itself changes. The Angular team is constantly tuning how the CLI works and what it's delivering, but let's just get an idea here. In this case, in this scenario for this version of the CLI we're transferring about 14 megabytes. Now let's go to our build version and see the difference. Now before I refresh, I want to go back into the console. Often times what will happen when we start our application with the CLI is if it sees that DIST directory it'll actually clear it out and delete it. So I want to rebuild the project again using the ng build command, just to make sure that we are dealing with a fresh copy of the build. Once that's done, back into the browser, open up the console, run the network tab. We're going to refresh. Notice how we only have 16 requests and five megabytes transferred. Again this isn't even completely optimized because we aren't using the prod flag, but you can see that already what gets created inside of the dist directory when we build is a fair amount smaller than what is delivered using the CLI when we're in development.
-
Build Flags
Now we ran the build using ng build without any flags, but there's actually quite a few flags that the build supports. There's something like 15 or 20 flags that are actually supported. There's really only a few that are going to matter in most cases, so we're not going to go through every flag, we're just going to cover a couple of the more important ones. The first flag I want to talk about is the dash dash prod flag. This tells the build to run in production mode and this actually sets a whole bunch of other flags to specific values. It has a converse mode, which is dash dash dev. The dev mode is the default mode, that's what we did when we just execute ng build with no flags is it runs in dev mode. Again, just like prod, that sets a whole bunch of flags to be a specific setting. Sine we usually use the CLI as our server we don't often need to build in dev mode, most of the time we build in production mode. But it is important to understand that there are the two flags and the default is dev mode if we want to produce our most optimized, most performant package we'll use the dash dash prod flag and we'll see that in the next section.
-
The Effects of Prod Mode
Now we ran our build initially without any flags, just ng build, which produces the build in dev mode. We're going to run it again, but in production mode. We can do it this way with ng build dash dash prod. Of course if we do npm run build, our package dot json file is set to do the same exact thing, to run ng build with dash dash prod. Through the command we run we're going to produce a production build. I'll execute that now. Depending on your machine, this may take quite a bit more time than your dev build takes. Now it's important to see that we actually have a lot of information that's being printed out when we run our build. Looking here we can actually see the file sizes produced in our bundles. 127 kilobytes, 700 kilobytes, 61, 125, 1.4. This output can help us tune our build as we change various settings inside of our project or split it up into different, lazy loaded bundles, which we will also see a little bit about that later on. Let's just go back to our browser now and see what the effect of that build is. Again I'm going to open up the console. I'm going to go to the network tab and I'm going to refresh this. Before I do this, remember that using the CLI dev server, we're producing about 14 megabytes, using a dev mode we're getting about five megabytes is the size, and now with the production mode build we're getting it down to almost exactly one megabyte. We can see that the tuning involved in doing a production build is actually pretty significant in comparison to the other two builds. Of course these numbers aren't going to be what your numbers are, the CLI is constantly being tuned. The Angular team is constantly working on adjusting these and optimizing these file sizes, so likely over time you'll see even better results then we're seeing here.
-
Optimistic Bundle Downloading
The final thing we want to look at is loading strategies for our lazy loaded modules. This doesn't directly correspond to making a build, but it really does affect the performance of our application, not the size of the downloads, but the perceived performance. Our application has a lazy loaded module, it's the user module. If we go into our code, remember that our user module is a separate module. The route path is actually a lazy loaded path and we do have a user module, which is this module right here. We can tell Angular how to load that module. The default way is to load the module on demand. Let's just go back, we're just using the CLI's dev server, we're not running our production build anymore or our dev build. I'm going to open up the console and on the network tab I'm going to refresh the whole website and I've loaded a bunch of bundles and other files. I'm going to clear all those out. Now I'm going to click on the log in button and notice that I immediately download this user module chunk. That is our user module. It hasn't been downloaded yet. Now if I go back to all events and go to login, it's not downloading it again. It got downloaded once and that's it. That is loading on demand. Let's change our loading strategy to actually pre-fetch as soon as possible. I'm going to open up the app module. Inside of here, inside of the for root call, I'm going to add a second parameter and that's an object, Now I'm going to give that object one property. The property is preloading strategy. I'm going to give that property the value of preload all modules and that automatically imported the preload all modules value from the Angular router module. Save that change and go back to the browser and I'm going to go to the all events. I'm going to refresh the page. Notice that even though I'm on all events, if I scroll down, I can see that the user module has been downloaded. Before we had to click on the login page to get this module to download, but now it's getting downloaded automatically. That is the preloading all modules strategy. Angular does allow you to create your own preloading strategies, so you can have some that are really optimized for your application, but if you do have some lazy loaded modules that are very large, in this case the module is very small, only 30 kilobytes, but you easily could have a much larger module. So prefetching that module when the browser is available to do so will improve the perceived performance of your application. This is another important strategy when going to production when trying to tune the performance of your application is to preload modules when you can and also to make your initial module as small as possible.
-
Summary
In this module we looked at a lot of different topics related to taking our code to production. We looked at linting and how that works versus Prettier. We talked about the less opinionated linting and the more opinionated tool, Prettier, and similar tools like that and we talked about how linting can help up with our code quality. Then we looked at deployment. We saw that deployment with Angular is fairly simple because our builds are just a folder that we can just copy to where ever our server actually reads from. We finished up with looking at optimistic bundle downloading, a nice performance technique that improves the perceived performance of our application, which is a great things to improve our users' experience. Through this module we learned how to take our code to production. Finally, on behalf of myself and Jim Cooper, I want to thank you for watching our course. We hope you get out of it as much education and enjoyment as we did.