What do you want to learn?
Skip to main content
Integrating Angular with Node.js RESTful Services
by Dan Wahlin
Learn how to build an Angular and Node.js application that can perform create, read, update and delete (CRUD) operations. Topics covered include building RESTful services with Node.js and Express, manipulating data in MongoDB and consuming services.
Resume CourseBookmarkAdd to Channel
Table of contents
Welcome to the Integrating Angular with Node.js RESTful Services course. My name's Dan Wahlin, and I'm a software developer and trainer, specializing in web technologies. I work a lot with Angular and different server side technologies, so I'm really excited to introduce this course. Now, throughout the course, you'll see firsthand how Angular and Node.js can be used to build an application that allows users to view customer data, page through it, and then perform, insert, update, and delete operations. This includes learning how to move data from a database all the way down to an Angular client, using RESTful services. As data's modified in the client, you're also going to learn different techniques for sending that data to the service, so it can be stored in the database. On the server side, you'll learn about Node.js and Express and how they can be used to define RESTful endpoints. You'll also examine how Angular services and the HTTP client can be used to make async calls to the RESTful service and retrieve data. You'll see how components can subscribe to observables and render data in the user interface, using child components. This includes discussing input and output properties and how they can be used to pass data in and out of components. The course will also discuss different form techniques, including template-driven forms and reactive forms, and highlight the differences between them. So let's get started, and jump right in.
Let's start things off by talking about the overall goals of this course, some of the key technologies we'll learn about, and the sample application that we're going to walk through. So the first thing I'll do is walk you through the prereqs that you need to know so you can maximize the learning process and get through this course and feel like you got something out of it as well. We'll talk about the overall learning goals and what you can expect right up front, and then we're going to clarify some of the key, server-side technologies and concepts that we'll be covering as well as the client-side technologies and concepts. So that'll include things like RESTful services, what that means, observables in Angular and more. From there we'll talk about how to get the sample application that we're going to be building up and running locally on your machine, and I'm also going to show you how you can get it up and running using something called Docker. So let's jump right in.
Pre-requisites to Maximize Learning
Before jumping into a book or a video course, I always like to know more about what I'm going to learn. So let's take a quick look at the overall learning goals I have for you as you go through this course. So first off, we're going to be focusing on two main areas and how we can integrate these together. We're going to talk about client-side technologies, and then we'll talk about server-side technologies. So we'll be talking about Angular on the client-side and then integrating that with Node.js, of course, on the server-side and building a RESTful service that integrates with a database, which will be MongoDB in this particular application we'll be discussing. So the overall server-side learning goals include understanding how Node.js and Express can be used to create a RESTful service. We're also going to talk about something I call convention-based routes. Now this is not a normal way to build express routes; it's something custom that I like to do that I think really cleans up the amount of code you write. So I'll show you a little bit of customization you can do. We'll also talk about how you can expose RESTful endpoints using Express and how we can write some reusable code and build some library-type code that can be used to integrate with one or more different databases. In this particular case, we'll be using MongoDB. Now on the client-side, we're going to be looking at Angular and how we can use the HTTP client to integrate with the Node.js RESTful service. Now technically we could call any type of RESTful service; but for the sample application we'll be running, it'll be based on Node.js as mentioned. So we'll look at the role of RxJS and observables and see how observables play a big role in the asynchronous calls we're going to make to a RESTful service. We're going to use the HTTP client to call different functions that can be used to interact with the RESTful service. We're going to be making Ajax calls or technically XHR, XML HTTP requests, to the server. We'll also see how we can perform complete CRUD operations: create, read, update, delete; so that we can insert, update, and delete data ultimately in the target database. And then finally, we'll also learn how we can page data in the application and how the server-side is going to play a role in that and how the client-side can then render that page data. So that's a quick look at the overall learning goals. From here, let's jump into some of the key technologies and the concepts you're going to need to know on the server-side and the client-side.
Server-side Technologies and Concepts
Client-side Technologies and Concepts
Running the Application
Running the Application with Docker
If you'd like to run the application using Docker containers, I have that available for you as well in the sample kit. So I'm going to do a real quick run through of how to do that, the software installation requirements, and those type of things; and I want to emphasize this is really intended for people who are already very comfortable with Docker. So if you're not, you can certainly skip this section and just go with the local install that's already been covered. But if you are interested in getting a Docker container up and running for Node.js and MongoDB, I'll walk you through those steps here and some of the basics. Now as far as the software installation requirements, you'll still need a local editor, of course. So I'll be using VS Code for that, and you still are going to need Node.js because we're going to be using that for some of the Dev-type tools to run npm install, Gulp, and things like that. Now, MongoDB and the actual node server, the RESTful service; those are both going to run in Docker containers though. So those won't actually be running on your local system, they'll be running in Docker containers. So, of course, to do that you'll have to install Docker. Now you can do Docker for Mac. There's Docker for Windows if you have Windows 10 Professional or higher. Or if you don't, you could look at Docker toolbox; and then you can even get Docker going on Linux as well if you'd like. So let's jump in to the application itself, and I'll show you the steps to get this going. So, again, I've opened the Read Me that we've seen earlier in this module; and in this running the application with docker section, I'm going to walk through the steps real quick. Now I've already installed a lot of the dependencies to speed this up for you, but the first thing we're going to do is once we've installed node locally and either Docker for Mac, Docker for Windows or Docker Toolbox as appropriate, then we're going to come into our development file here. So I'm going to come into config development, and this is local host by default since we'll be hitting the local version of Mongo, but we want to hit MongoDB in a Docker container; and we're going to name that MongoDB. So that's why the host is going to change here. So once you've saved that file, we can move on to the next step. Next thing is we'll have to install Gulp. So we'll come on in to our terminal window, and we'll run the global install of that. I already have it, so it should be pretty quick here; and this, again, is going to be used to copy our Angular scripts into our public static files folder. We'll be using those in just a bit here. The next thing we'll do is our package JSON has all the Angular scripts, the node and everything we need. So we need that node modules folder. Now I've already run this, but I'll run through it really fast. So we'll do npm install. That normally would take, maybe, a little bit of time; and then we're ready to go there. Now we'll also need to run the Gulp copy libs to get the script from the node modules folder into the public static file folder. I showed this in the previous clip, so I'm not going to run it here; but just copy and paste that command there, into the command window, and run it, and that'll get the scripts copied. Now the next thing is we're going to be working with code locally, but it's actually going to be running up in a Docker Node.js container. So what I'm going to be doing is linking the Docker container back to our local machine folder using something called a volume; and that means we have to get everything ready locally so we can link back to our source code for this volume. So I'm going to run the compilation step for our type script code for the Angular 2 app. We'll run that, and this one I'm going to leave up. This will sit there and watch for changes that we might make as we go through and modify the app at all. Now the next step is we're now ready to run our Docker. So I'm on a Mac in this particular example; and you can see I have the Docker whale up here. If you're on Windows, it'll be down here in the right corner in your tray area; and that's for Docker for Mac or Docker for Windows. If you're doing Docker Toolbox, it's a little different. But hit docker.com, and you can get more details on that. If you're on something like Windows 7 or Windows 8, they have a nice walkthrough for you. So I already have Docker running, so I just have to run this build command. Now this is going to get the node and the MongoDB docker images available and ready. Now I've already downloaded these local, so this should go pretty fast; but it's going to build our node image. The MongoDB image I didn't need to modify, so it's just going to pull that from Docker hub. All right, and this is already done because I had run it earlier. Now the last step is we need to run Docker compose up, and that's going to start the Node.js RESTful service in the container and link that over to a MongoDB Docker container so they can communicate; but the Node.js container itself is going to link back to our source code using this thing called a volume. So let me go ahead and try to start this up. That's going to create the containers. What's really nice about this if you haven't done Docker before is I literally don't have MongoDB installed locally on my machine. It's in a container the I could easily stop and then delete the image, and it's just gone. So if you're interested in more details, check out my Docker for Web Developers course, which is also on Pluralsight.com. But now that we've done that, we can run on back. Let's go to localhoost:3000, and there we go. So we'll see the same app. I can edit customers, do all that type of stuff. See that all works. Looks like everything's working well; and then when we're done, we can actually go back, and I'm just going to do a Control + C here to stop everything; and then I'm going to say Docker compose down, and that's going to stop those two containers and now those are done and then I could go to Docker images if I want to see the different images. There's the node image, there's the Mongo. So I could actually delete those if I wanted to and it would be as if they were never on my machine. So that would be an example of how you can use Docker if you'd like to work with a sample application, and it's a great way to go if you don't want to have to install all these servers on your local machine.
In this introductory module, we set the stage for the overall learning goals that I have for you as you walk though this course. And, really, the overall goal is I want to show how we can integrate Angular 2 with Node.js RESTful services and also talk to an actual database. So as we start moving along in the upcoming modules, we're going to see that process. We also learned about some of the key technologies and concepts on both the server-side and the client side. So on the server-side we talked about node, MongoDB, HTTP, and RESTful services being used; and then, of course, on the client-side, we have Angular, RxJS, observables and HTTP Ajax type calls are going to be made up to those RESTful services. We also took a look at the sample application and saw how you can run it locally or using Docker containers. So now it's time to jump into the actual application and start building some of the different pieces that allow us to integrate Angular with Node.js RESTful services.
Exploring the Node.js and Angular Application
In this module, we're going to take a moment to explore the Node.js and Angular application and look at things like the project structure, how Node.js and Angular's modules work and how they're configured and even explore some other aspects of Angular such as modules, components and services. So, if you're already very comfortable with how Node.js modules work and how Angular modules work, you could certainly go through this module, maybe skim it and jump right to the next one, but if you are new at all to some of the concepts we're going to cover, I would definitely recommend that you take a moment to go through it because I'm just going to provide a quick overview of how everything's put together, and that way in the next module when we start building out the code you'll understand the base behind the application that's making it possible to run. So, in this module here's what we're going to cover. We're going to talk about the project structure first off, and I'm just going to walk through some of the key files, some of the folders so you know the lay of the land, if you will. From there we're going to talk about the Node.js and Angular modules that are needed and just a quick overview of how they're used in the app. I have kind of a unique way of configuring my Express Node.js routes. So, we're going to talk about a convention-based routing technique that can be used. We'll also go into the fundamentals of ES6 module loaders. In this particular application, I'll be using SystemJS. There are many options out there though. So, I'm going to go through how SystemJS is configured. Make sure that you understand how it's all working under the covers, and then finally, we're going to create the basic building blocks that are going to be used later in the course as we start building our get and put and post requests and things to the restful service. So, I'm going to show an Angular module, a component in a service, and I'll show you the fundamentals of how all that works together if you're new to that at all. So, let's go ahead and get started by jumping into the project structure and walking through what it has as far as folders and files.
Exploring the Project Structure
Let's take a quick look at some of the different files and folders that make up the overall project structure of the app that we'll be discussing throughout the course. So, I've opened the application up in my VS Code editor and as mentioned earlier, you can use any editor you'd like, but this is what I'll be using throughout the course, and I'm going to take a bottom up approach to show you, just a quick look at some of the different files and folders. So first thing, we have our tsconfig file and this'll be used to convert our Angular to type script code down to our ES5. I'm going to be using a commonjs module resolution system in this example. I am going to generate source maps or debug capabilities, and then I have some type script and Angular two specific features in this area. So, I'm going to support decorators such as the component decorator. I'm also going to make sure that some of the folders are excluded so that the type script compiler doesn't waste time jumping into the node modules or this public lib folder. Now, we also have the Node.js file itself that acts as the server. So, this'll be our Express server. You will notice I'm importing some Express specific features, some modules here, and then I have some custom modules down here, and I'm also using some ES 2015 features such as a class here to organize my code kind of nicely. Certainly optional. I decided to throw this in to show that you could now with node, and this is the actual file that as mentioned kicks off the server that we'll be using. Now, the readme's going to have the instructions for running the app locally or through a docker. So, if you didn't go through the section earlier in the course where I showed how to get the app up and running, you'll find all the details in here, and then we have our standard package json. This'll have our Angular modules as well as our Node.js modules. Now, the gulp file I ran earlier and this was used to copy our Angular two libraries and supporting scripts into a static files folder Express uses called public. So, really one of the main things that the gulp file is used for is just to get the scripts from Angular into this folder called public into this lib folder. So, you'll notice our different Angular scripts in here. Now, if you're new to this the public folder is used by Express by default. Although you could change the name if you wanted, but it's used to serve up your CSS, your images, your job script files and things like that, and so, I'm just using gulp to copy those necessary Angular two scripts into here and that's where they'll be served up by Express. Now, it can also be used to do sass and things like that. Some minification is in here for compressing scripts, watching for changes and things along those lines, but really I'm just using it to get our Angular two script into the lib folder. So, we ran that a little bit earlier. Now, moving on up we have our docker compose file if you wanted to use the docker option that I provided and showed how to run earlier, then that's available for you, and then we have the standard gitignore for ignoring files from source control. Now, a little bit later I'm going to talk more about how routing works. I like to use a convention-based routing approach. It's not the standard approach that Express uses. It allows us to have a convention-based folder approach that I put into this controllers folder. So, this'll actually have all the functions that convert MongoDB data into json data that can go down to Angular as it makes http calls. So, I'll talk a little bit more about that later, but I like the approach. It really cleans up the amount of code you write, minimizes that and provides some nice conventions you can follow. Now, moving on up, the models folder. These are going to be the schemas and model objects that really just define the objects that'll be stored in our MongoDB database. So, this is a CustomerSchema for instance and then we have a model down here called Customer that we create, and then we also have a StateSchema and as State model, and again, these will just represent the actual data and the structure of that data as it goes in and out of our MongoDB database. Now, the Node.js modules that are actually responsible for interacting with MongoDB are shown here. So, we have our database object which is responsible for opening and closing connections. We have a config loader which allows us to easily get some configuration information such as connections strings based on the environment we're in such as development or production, and then we have a db seeder which is used to get some initial data into our database. So, this is just kind of fake data so you don't have to type it in yourself and this makes it easy to get the app up and running with some data in the database, and then finally, we have our customers repository. This is where we're going to be doing queries using something called mongoose which is a module that can communicate with MongoDB to get our data and then we're going to be converting that data to json. That'll go down to Angular. We also have a states repository which just gets our states information. So, it'd be like U.S. states for instance. So, that's going to play a pretty critical role, this lib folder in working with data in the app. Now, controllers as mentioned will tie in terms of routes. We'll get back to that a little bit later, and then we have our config which is just basic configuration info. In this case, just a connection string for MongoDB. Finally, we have our VS Code settings. These are just custom settings I have for the editor and then we have our docker file for node and this is what's actually used to create the node image if you ran the docker option that I showed a little bit earlier. So, that's a quick run through of some of the different files and folders and we'll be diving into several of these in more detail as we move on throughout the course.
Before we jump into the actual code of the app and learn about more on routing and Angular modules and things like that, let's take a quick look at the actual npm modules that are being used. The node modules and the Angular module throughout the application. So, I've opened up the package json file and you'll see right off the bat, we have the standard properties that you would normally find in type of file. So, we have the name and repository and license and things like that, but I also have few custom npm scripts I can run. So, if you run npm run and tsc, that'll just run a onetime type script to ES5 compilation. That of course will use the ts config file to determine how to compile the type script. If you run npm run tsc:w, that'll do the same thing, but it sets up a watcher that'll watch for changes to our type script file and anytime you save a file it just automatically then would compile that down to ES5, and then you have two ways you can start the server. You can run npm run server. That'll run a standard type node command or you can run npm start and that would run not only the nodemon to watch for changes to our node files and then restart the server if needed, but also behind the screens run the type script compiler in watch mode, and so there's an example of the one I showed earlier to get the app up and running locally. Now, as far as dependencies and devDependencies. We have quite a few dependencies that we need on the Angular side and on the Node.js side. Now, I've stripped out the versions because they change very frequently and wanted to focus just on the modules here, but you'll notice right of the bat here we have pretty much from here on up our Angular dependencies. So, we have the standard Angular modules that you'd need like cores, one of the critical ones. We're going to be working with forms. We're going to be working with http. We have some of our bootstrapping options that we'll get into. Routing and things like that, and then we have our supporting scripts, zone js for change detection, rxjs for observables and more. Now, everything else moving down is being used on the Express and Node.js side. Now, some of these quite honestly I could remove. I'm not really using a view engine per say. We have in the public folder an index html that's actually going to represent the entire application, but I went ahead and left this in, these modules here because if you ever wanted to dynamically serve up an Angular template or maybe the homepage is dynamically served using Express, then we could do that in this case using the handlebars data binding type of engine. It's my preference with Express, but there's many options out there. You can use Pug or many others, EJS, so on and so forth. You'll see we have mongoose and this will be our MongoDB connectivity type of module. Morgan's a standard Express logging type of mechanism and then I have a way to serve up that nice little favicon that you get when you bookmark a website. Now, we also some supporting scripts. We have concurrently is used to run multiple tasks. That'll run our tsc or type script watch and our nodemon simultaneously, kind of in parallel if you will. I'm not using lite server, but this is kind of a standard one for serving up static files. You have type script of course, and then the rest of these are just used with gulp. I can compress and kind of uglify my scripts and things like that. Now, the final two here are used with Angular as type definition files. This is kind of a newer technique to automatically install the type definition files for in this case, core js and for node and these are actually used a little bit in the Angular application. So, if you haven't seen this @types before, instead of running off to a site like definitelytyped.org or using tools like Typings, we can now get to the types for our different scripts and things directly which is really nice as you're working with type scripts. So, that's a quick look at some of the core modules we'll be talking about, and then now as we move forward, we'll start to use these modules as we move into the Node.js side and into the Angular side of the application.
Configuring Node.js Routes
Configuring the ES Module Loader
Angular Modules, Components, and Services
Let's take a look at some of the Angular modules components and services. They're going to be used in the app to ultimately communicate with our Node.js Express restful service on the back end. So diving into the public folder we have our root module called app module and this is going to be the main bucket I like to call it. It's going to hold the other modules that are reusable throughout the app. Now, before I go into more details here, let's jump back into main ts and this where we actually bootstrap app module in and that makes everything in the bucket available then to use. So, going back to app module, there's a few custom things that are being used. First off, we have a root component and you'll see that that's being declared right here and then we have some routing components. We'll go into that in just a moment and I'll show you what's going on there. Now, in addition to that we also have a core module which you're going to see is where I like to put my singletons and then any functionality that's just generally shared, but that can be created multiple times, I call that my shared module. So, I have very consistent approach I like to take. My singletons go into a core module whereas my reusables could be reusable components or pipes or directives or something like that, go in my shared module. You'll see that down here actually in the comments. So, we declare everything, we bootstrap the root component and that kicks things off. Now, let's take a look though before we wrap up here at some of these shared and core module features as well as the routing. So, let's first go kind of a top down approach. Let's go into the routing. Now, there's many ways you can do routing. This is a really simple app as far as routes go. So, I didn't make a separate module for the routes. I took a more simplistic approach that's pretty straightforward. So, I import the standard router module. I import components that are going to be used with these routes and then I have an interface that's going to be used right here for two properties and all this interface does is says that I have to have two properties, one called routes and one's called components. Now, in addition to that we define the routes. You'll notice that I have a customers root route here and then I have customers with an ID route parameter. So, really basic, but there's some other features like a customers grid and customers edit you'll see that are imported up top, and the reason I want to point that out is you'll notice I'm exporting this app routing which has these two properties. So, first off I feed these routes into my router module for root. That's pretty standard. So, these then become the root routes. I have a fall back route that if none of these match, it's going to redirect back up to my root route, but then I also export the components that are associated not only with the routes, but any child components that these may use, and I'll get back to why that's being used in just a moment. Now, if I had a more complex scenario, then I generally like to make a routing module, but in this case, it's kind of overkill, at least in my opinion. So, by simply exporting this custom app routing object with my two properties, I have everything I need and I really don't need to create a separate file just for a routing module. All right, so now that you've seen that, let's go back to app module. So, we import that routing file. We import the exported app routing item. That allows me to get to routes. So, there's my four root routes right there and then I'm also declaring all the components in one kind of statement. Now, the reason that's really nice is all the imports that were back in here, don't have to be duplicated in the module file. Otherwise, I'd have to import them and then list all those in the declarations array. By doing it this way, I don't have to do that. Now, I could've just as easily made a routing module and done the same type of thing. Then we could just import that module here and it would've declared the components. So, either way it works. All right, now the shared module. Not a whole lot in here actually, but we have a little bit. So, we have we saw earlier as the app was running a way to filter data as the user types. Well, that's potentially useful throughout an application. So, I made that shared. We have a pagination control. Now, that's also sharable. In this app it's only used once, but it could be used as the app grows if it did. We also have some pipes to capitalize data, trim data, trim off spaces, things like that. Well, those are certainly reusable, and then the rest of this is just defining these in my module. So, here's my shared module. I do all my imports of these reusable items. I declare them, and we're good to go. Now, some of these like filtered text box component uses ng model the directive. So, I'm importing you'll notice forms module because I need to be able to get to that, but aside from that we just simple declare these. Then we export these as well to make sure that any importing module can also get to these. Now, we know the importing module is our app module. So, we import that and there's our shared module. So, now all that functionality is going to be available to any of these components here. Now, the final one is where we'll spend quite a bit of time actually. We won't touch necessarily the shared code a whole lot, but if I go to core. This is my singletons. So, this is my services that I really do only want created once, and the main service we're going to start building now is data service. I won't go into to this in detail here, but this is what's ultimately going to interact with the Node.js Express restful service and so, you'll notice my rxjs imports, my http import and those types of things. So, if I go to the module for this core module here, you'll notice that I'm importing the standard suspects up here, ng module, but I'm also importing http module, because of course we want http functionality here and then all of my singletons are being defined as providers. Of course, we have to have our provider so the injector service can then inject these into the different components and maybe other services. Now, I also have something unique going on and this is a very custom solution, but I want to make sure that nobody else, no higher up level modules have defined any of these. I really want them to be singletons. So, you'll notice I have this ensure module loaded once guard, and what that's going to do is there's a little bit of code here on this core module to actually check have any of these other items already been loaded. So, it's going to check to make sure that core module is only loaded one time and that one time would be here in app module, right there. Now, what this will do is using some kind of trickery that I have in here. This will go in in this base class and I'll let you look at that one if you're interested, but it will ensure by walking up that core module's only loaded one time and that would mean if core module's only loaded one time, then our providers are only defined one time and therefore we can guarantee singletons for our different services. Now, this is certainly optional and given that this is not a super complex app, certainly overkill in this example, but in a larger scale app, this is something that's nice and it'll be available in the app if you'd like to check out some more code there, but that's all our singletons. Those get loaded back into here and that's where we're going to spend quite a bit of time is in this core module. Now, the last piece to show you that's in here is we need to load our customers. We have edit capability and we have the grid capability, and so, that's all going to be done with our various components. So, you'll notice that I have a customer edit component, a customers component which then loads a customers grid component and the filter text box and paging and things like that. So, we'll start to dive into some of these as well as move along throughout the course. So, that's how everything's organized as far as the Angular code goes. So, we've seen how the module loader works, we've seen the different levels of modules that we have. We have our root module, our core module for singletons and our shared module and then my routes and we declare all the components right here that we're going to be using. So, what we're going to do moving forward in the course is I'll start to build out the get type of data request to the server and we'll show how to build the server piece and the client side piece and the service and then use that component. Then, we'll talk about how do we update data, how do we insert data, delete data and all of those different things.
In this module, we've seen the overall project structure that was initially generated using express-generator and we learned about some of the key folders and files used in the application for the restful service and for the Angular part of the overall application. We've also taken a look at how npm modules are used on both the Node.js side and the Angular side. How the application has a more convention-based routing feature that actually looks in the controllers folder to determine the routes that are to be used for the restful service and then we also looked at the different Angular modules, the custom modules that are being used in the app such as the core module, the shared module and how those are used in the root module. So, moving forward what we'll be doing now is dissecting the individual pieces of the application and building out that code. So, we'll talk about how we can get data into the app. That'll be our next topic, and then from there we'll talk about different ways we can work with manipulating that data.
Retrieving Data Using a GET Action
This module is all about learning how to expose data through a RESTful service, and then how we could consume that data using Angular Services, and even data-bind the data into our templates and components. Now we're going to start off with a look at the different GET actions we need for the apps. We'll learn how to get customers, get a single customer, as well as how to get states. We'll also talk about how we can make GET requests with an Angular service, and we'll talk about observables again, and review how those are going to be used and the role they play. From there we're going to switch gears to using the Angular service to consume the data, get that observable back, and subscribe to it in a component, and then render the data in a grid. And then we'll wrap up by looking at some different techniques that can be used to render a single customer in a form. We're going to talk about a template-driven approach, and a reactive forms approach. So let's go ahead and get started by building out pieces of our RESTful service.
Creating a GET Action to Return Multiple Customers
Creating a GET Action to Return a Single Customer
Now that we've set up the RESTful service to return multiple customers, let's take a look at how we can return a single customer. To handle returning a single customer, we need a customer id. So we already have the route setup to handle api/customers, but I need to add another child route here that handles api/customers/, and then I'm going to make a route parameter. Now this is something, again, that's provided by Express, the router that's part of Express, and we can just put a colon, put the name of the route parameter, and then we're kind of off and running, and now later I'll show you how we can pull that off and get to it. So let's come on in and we'll map this to a getCustomer, and then I like to add the "bind" I mentioned, just to make sure the context is set appropriately. It's not actually going to be needed here, but it's something I like to follow. All right, so let's add a getCustomer that takes a request and a response, and we're going to do a similar thing here, so I'm just going to grab our logging statement to save a little typing, and once we do that, we can start to get to work here. So the first thing I'm going to do is, I'm going to grab the id, and the way we can do that is go into the request, go to the "params" property, and then go to id. Now I'm just going to assume that it is passed, but we certainly could add more code to say if it's not passed, throw an exception, or assign it to zero, or something like that. Otherwise it's possible we could get an undefined, or null reference type error somewhere, but we'll go ahead and keep it pretty simple. Now we already have our customersRepo, so I'm really after the same type of code that you'll see up here, but I'm just going to change it a little bit, so let's come on up, I'm just going to copy that down, change this to "getCustomer", and now I need to add some code to handle the id. So we already have the id, I need to pass it as the first parameter. So I'm going to pass id here, and then we'll give it the callback if it's able to find the customer. So we'll change this real quick to "getCustomer", and then I'm going to return the actual customer that we get back. So I'm just going to rename this to "customer", since that's what we're going to get, and we would return that customer. All right, so same type of premise as before, we're going to use our customersRepo to get that Customer, let me jump in there really quickly to show you that code. And we're going to use this Customer model to findById, it's one of the built-in functions on the model. There's the id that we passed in here, and then there's our callback that's going to get invoked right here, and we'll hopefully get back the customer, or we're going to get back an error, one of the two. All right, so assuming this works, we'd get back a customer, so let me go ahead and save that. Now I already have my console still running from the previous example, notice it keeps restarting. So let me open up Postman again, let's go back to all our customers again, looks like that's still working. Let's try to grab this first customer now, so we're going to say customers/, and I'm going to put the id, and let's Send that, and looks like it works, all right, so that's great. Let's just double-check, let's grab one more, let's go down to another customer, or maybe this guy right here. So we get back Jesse it looks like, and there we go, Jesse Smith, and we're good to go there. So looks like we're working, so now we have the ability to return customers, and a single customer by defining our routes, and then mapping those routes to our functions. So now we have the, at least "get" functionality, at least the core "get" functionality for the app ready to go. We're going to enhance this a little bit later, but we have enough now that we could start to move into the world of Angular, and call this service.
Making GET Requests with an Angular Service
Now that we have a RESTful service capable of giving us customers and a single customer, it's time to integrate Angular with that Node.js Express RESTful service. So the first thing we're going to do here is go into our public area, because I want to work with our data services already in this public app. So if I go into public, app, core, core again, I mentioned earlier in the course, represents my singleton objects. I only want a single instance of anything in core. You'll see I have this data.service. So the first thing I'm going to do is get HTTP setup. Now, as a quick review, if I go to core.module, you'll notice that I've already imported HttpModule, and then I've added that to the imports, and so that'll make the HTTP client object available to my data service, and I can inject it. So let's go back to the data service, and let's import Http, and I'm going to grab a few other things we're going to be using throughout this particular service as well, so I'm going to grab a Headers, which we'll get to later, Response object and RequestOptions, and we'll talk about the Http and Response now, later we'll talk about Headers and RequestOptions as we move through the course. We're going to grab that from angular/http. All right, so now that we have that, I also need to work with observables, and I talked about, at the very beginning of the course, the role of observables, and how really they're like an array of data, stream of data into the future, and although HTTP calls only give me one value, you can think of that as getting back an array of one item. So I need to setup my observables, now this is not the most fun stuff to type, so I'm just going to grab a snippet here, and we'll talk it through it. So first off I import Observable from rxjs/Observable. If you recall in public, we ran a gulp task to get this app up and running in the first place, and the gulp task automatically had the lib folder added. Now if you don't see that lib folder, then you're going to want to go back to the console, and run that gulp command, which is a copy command you'll see here, "copy:libs", and that will make sure that your Node modules get copied in to your lib folder. Otherwise, when we try to reference rxjs/Observable and @angular, if you don't have this lib folder, then you wouldn't have these different folders that we need. So I already have that, so we're pretty good to go there. All right, so I'm also importing some operators. I have map and catch, now map is going to allow us to map the incoming response to a callback function, and catch is going to allow me to actually catch errors, of course. Now I also have a way I can add a throw capability, a throw function to Observables. Now I'm going to jump down here, we're not going to talk about it a lot, but I have a little handleError, and you'll notice at the very bottom, once it builds up what the data is, and also right here, I have this Observable.throw, and this'll throw an error, but it'll throw it as an Observable, that way whoever subscribes to the Observable can also get to the error in kind of a normal way. And so that's a little callback error handler that we have, that we're going to be using here in just a moment. All right, so those are some of the initial imports I need, we have our Angular imports, and then we have the Observable, and our main things are operators we're going to be using. So now that we have that, I need to inject the HTTP object in, so I'm going to make a private option here, call it "http" of type Http, and that'll handle our dependency injection, and because we've already imported HttpModule, the provider for this object will be, of course, available for us. All right, so the next thing we need to do, is we need to work with getting customers, and getting a single customer. So I don't currently have a function for that, but we're going to add it, so I'm going to add a getCustomers, and it's going to return an Observable of an ICustomer array. So I'm going to say "ICustomer array" here. So we're going to use a generic from typescript to say "Yeah, I'm going to give you back an Observable, "whatever subscribes to this, but inside of "that Observable, I'm going to have this ICustomer "that you can actually work with and use." Now right now, ICustomer's actually not known, okay, and if I go into my shared area here, you'll notice I have an interfaces.ts, and this is my ICustomer. So you can see some of the different properties it expects, but of course, I need to actually import that. So we're going to come on in and import that as well, so I'm going to actually import several from this interfaces file that we're going to be using throughout this DataService, and we'll grab that from, move up a folder to shared/interfaces. All right, and that'll allow me to give, kind of a strongly-typed response type to whoever calls this getCustomers, so we'll go ahead and make that. All right, now inside of here I want to be able to make an HTTP call, and I could hard-code the actual URL. What I like to do though is come in and put a base URL, or something like that, that's a string, and this'll be to the api/customers. Now you'll recall that that matches, of course, exactly with what the RESTful service has, and so that's actually what we're going to call on the RESTful service here. So now I can come on in, and I can go ahead and return an Observable by calling http.get. We would like to get the baseUrl, call up to there, but because we're working with Observables, a stream of data over time, I'm going to map the response we're going to get back, and in this case we're going to just call it "res", and that's going to be of type Response, that's why I imported that earlier. And we're going to map that into a function. So let me do an arrow function here, and this'll be our map, and before I actually do the semicolon, recall that I imported the catch operator, so of course, you always want to be able to catch errors as well, and that's where I'm going to use the handleError that you saw down below. All right, so we're now setup to get to the response. Well, what I want to do is get my customers, so I'm going to say "let customers = response.json", that'll parse the JSON data in the response, and give me back the customers. But the customers have some orders, and what I need to do is, the server's not doing some totaling for the orderTotal. Each customer needs an orderTotal I want to be able to display, and that might be used as the app grows for instance. So what I'm going to do is grab the customers that we're going to get back, but I actually want to call this calculate function, "calculateCustomersOrderTotal", and as you can see, that's just going to loop through each of the orders, and update this total property, pretty basic. So what I'm going to do is pass in my customers here. All right, so we're going to manipulate the data before I actually return it to whoever subscribed to getCustomers here. All right, so that'll kind of get us setup for that particular piece of functionality, and then we can go ahead and return it, okay. So now we're starting to use our Angular functionality. So we have an Angular service called DataService, we saw a little bit of this earlier, it of course has the Injectable decorator on it so that we can handle injecting HTTP, and that it works. We do all the imports of whatever we need, in this case Http and Response, and then I'm using some interfaces here as well, in this case, ICustomer. We've also imported Observable so that I can use that as a return type, and I can use it to throw errors if I need, and then I have the map and the catch. So anytime you work with rxjs, you are going to be importing specifically what you need. It is possible to import everything it offers, I would not do that, because that imports a tremendous amount of scripts, so just import the things you need, like you see I'm doing here. All right, so there's our getCustomers. So now a component could have DataService injected into it, which we'll get to later, and then it can call getCustomers, and that'll return the customers data that we need. Now we also need the ability to return a single customer, so let's add that. We're going to do, an id would have to be passed in, of course, as a string. This is also going to return an Observable of, in this case, one customer, ICustomer. All right, now this one will be very similar to what we see above, we're going to return the Observable by saying "http.get", we're going to use that baseUrl that we defined up above, but I'm going to pass the id. And I could've done the ES2015 string interpolation here, but I think this is just as easy as that option, so we're going to go ahead and just leave it like this, and then we come in and we're going to map the response. So we'll map that Response object, and we need to, of course, get the JSON, parse that. Now this is going to be a very simple example, so I don't need to run the calculateCustomersOrderTotals in this one, but of course we'd also want to handle catching errors, so we'll just do the same thing, we'll call handleError, and so we should be good to go. So we have all the imports we need up here, we have the baseUrl that we're going to call, we've injected HTTP, and now we're going to use HTTP to actually call into our RESTful service that we created earlier, get those customers, update some order totals for each customer, and then return them, so we're doing' a little bit of clean up work before the component that's going to call this service later actually gets the data. And then we've also added, of course, the capability to get a single customer. Now before we go too much further and wrap up, let's see how we're doing compilation wise with our typescript. So I still have my console running that's loading the RESTful service, and also running the typescript compiler, and if we look, kind of down here at the bottom, it looks like some customer-edit-reactive and customer-edit-component. They both are looking for getStates on DataService. Now it says a "Property of 'getStates'", but in this case it actually means a function. So we need to add a getStates, and if you remember, I showed that we had a getStates on our RESTful service, so let's go ahead and do that, pretty easy. Now that we have already seen HTTP, it's really just the same thing. So we're going to say "http.get", I'm just going to give it the URL here directly, because we only use it once, and then we're going to come on in and we're going to map this again, we'll grab the response, and you can see the repetition now going on here. Pretty straightforward, pretty easy, and we're going to map this, just grab the json again, and we always want to handle catching the error, so we'll say "handleError". Now I want to be specific, although I don't have to return what this is going to give us, which is an Observable, I do want to be specific like we've done up here, but I have another interface called "IState". So if we go back into the interfaces, IState is very, very simple, abbreviation and name are only required. Let's go ahead and add that as the return type here for our getStates. So we'll say ": Observable of IState", and this is going to be an array. All right, and we'll be in business. So we now have a getStates, returns an Observable of a state array, and we have a capability to do that. So let's save it, let's go back to the console, and all right, now it looks good. You'll notice that compilation completed, there was a file change detected, which I just saved, and then it actually updated that, and so we're pretty much good to go. So the last major piece here is, we now have our DataService, which at least has our initial get operations, but I need to add DataService into my core.module. So my core.module is all my singletons I mentioned, so I'm just going to import DataService, we're going to grab that from our local folder, data.service, and we need to add DataService as a provider, so I'm going to say "DataService", and that way when some other components need DataService, we can inject it, it'll look at the providers here, and know what to do. So let me save that, let's go back to our console here, and everything looks good, all right. So we have several file changes occurred, and we're still good to go. All right, so that's a walkthrough of now calling the getCustomers, the getCustomer, and the getStates get operations, or actions, that are up in the Node.js Express RESTful service, and then how we can actually use Observables. So now we're going to keep moving forward and start working with this data we've now retrieved.
Displaying Customers in a Grid
Now that we have a service capable of calling into our RESTful service, using HTTP to get our customers and our states data, we need to take that data and bind it into our data grid that you saw in the application, that's what we're going to work through here. So I'm in a folder called "customers", this is my feature folder, for our customers feature, and I've opened up the customers.component. Now as a quick review in the routing that we explored a little bit earlier in the course, we had a route called "customers" that goes to customers.component, so that's where I'm at right now, is I've opened up the customers.component.ts file. Now this is a pretty standard component, there's nothing really special about it per say, we import all our dependencies that we need, we have our Component decorator, we implement OnInit, which I like to do to make sure I don't have any typos with my ngOnInit function, and then this is really what's going to kick things off. This is where we're going to get our customers from our DataService. You'll notice though that first off, I haven't imported DataService, and I haven't injected it, so that's the first thing we're going to do, and then we're going to use it to get our customers. Now the UI that ultimately is going to render the customer data, it's going to need the ability to, not only get the customers, but also handle filtering the customers. So you'll notice I have a customers property here, and I also have a filteredCustomers. Now filteredCustomers, you're going to see, is actually what's going to be bound into the UI, but if the user undoes a filter, removes a filter, I want to it to quickly be able to get back to the original set of customers. So this customers here will never be bound to the UI, but it'll store the original collection of our customers, so we can kind of undo the filter, and display all the customers. Now I could go back to the server and, when they undo the filter, make an HTTP call every time, but it seems a little bit unnecessary unless you expect that your data on the server is changing a lot. All right, so the first thing we're going to do before we move on to filling these properties, is I need my DataService, so let's go ahead and import that. All right, that'll take care of importing it, and then we need to inject it. If you remember earlier, we added a provider for DataService into our core module, so now we can inject it and use it. So we'll say "private dataService of type DataService". All right, so we're good to go there. DataService is now available, when this component gets initialized, getCustomers gets called. That's going to call down to here, and now we need to subscribe to the Observable we're going to get back from our DataService, so let's go ahead and do that. So what I'm going to do is now add in our code to call our DataService, call that getCustomers, and then subscribe, and then I'm also going to show you a little error handling along the way. So we'll say "Hey DataService, "I would like to getCustomers." We know we're getting back an Observable, if we mouse over that, you'll see "Observable "of ICustomer array" is what we're going to get back. So I'm going to say "let's subscribe." Now I'm going to get back some customers, we'll say "ICustomer array," because we know we're getting back that, and I'm going to make my callback function here, that's where I'll do our arrow function. Now I'm going to add a little extra piece. I could stop right there, but if there's an error, we probably do want to log that. So if you put a comma, you can actually add a little error handler, and I'm going to do something very basic here, and just log the error, but you could certainly add something more robust, and then if you want a function to run, kind of every time, a "Hey do this every time" type thing, you can also come down and do the following. And actually, before I do that, since I'm not going to end it there, let me get rid of that, and we'll do a console.log, and I'm just going to say, "getCustomers was called," it retrieved customers, or maybe it got an error I guess, and then we'll end our parens here for our subscribe. So now we have a way to have a success callback, that's our first area, an error callback, if there's an error as we subscribe, and then a "Hey, let's just write this out "every time to the console, so we can get "some visual feedback here in the console, if we'd like." Now what I need to do is take these customers and assign them up into the individual properties up here. So let's go ahead and do that, pretty simple. So we'll say "customers is the same as filteredCustomers," at least when it first loads, and those are both equal to customers, and that should take care of it. Now let me save, go back to my terminal window, and looks like some changes were watched for, and I don't see any errors there, so it looks like we're pretty good to go. All right, so that's kind of the first stage. We now have customers that are available. Now if I go into the template, you would probably expect that the template for this component must have something that binds to filteredCustomers, well let's go look. So this is the template, and you won't actually see filteredCustomers in here anywhere except for right here. So I could've but the grid functionality, certainly right here, but I decided to build a child component called "customers-grid", and we'll look at that next, but it has an input property called "customers", and that's actually what accepts our filteredCustomers. Now to reiterate, customers, back in the customers.component, is the raw data we get. filteredCustomers is either the raw data, or the filtered data, because the user could interact with our UI of course. So we're going to send this guy down into our child component, and that's going to have a property we'll look at in a moment called "customers". All right, so let's go ahead now and let's see what we have going' on in that component. So here is my customers-grid.component, and if I open it up, you'll notice I have an Input property, so I've imported that. And Input property, if you're new to this, is basically a way to pass a data-bound property from a parent down to the child. And so now I'm saying, "Hey, you can pass me "something of type ICustomer array," and I start off with just an empty array. Now it doesn't do a lot more than that, it does have some sorting capability, I'll show you the basics of that as we move along, but other than that, nothing' real fancy here. Now one thing I do in this scenario where I have Input properties, the only time this would ever need to be checked for the change detection mechanism that Angular2 has built-in, is if the Input property changes, I don't have an output event, I don't have anything else special, I just want to detect this and re-render the UI for changes if the customers changes. Now if the customers change, then certainly I would like to push those changes out, so you'll notice I'm actually using a changeDetection mechanism, or strategy they call it, of OnPush. Now that's certainly not the default, which is just Default if you put nothing, that would mean it always gets checked for changes. We're only going to detect it for changes in this example anyway if this property is changing the data, the array changes for instance. So that's a nice little performance characteristic you can add when you have these child components that only have Input properties, and you only want the change detector to have to worry about this if that Input property changes. Now other than that, it does a little bit of sorting. There's a sorter service I have that can sort the customers as they click on the header for the data grid. I won't go into that here, but that's all in the code. I also have something called a "TrackByService" that's being injected, and I'll talk about what that does here in just a moment as well. All right, so now that we've seen that, let's go ahead and leave it as it was. Let's go into our customers-grid.component.html, and you'll see this is a pretty standard set of table tags. Now I could've used divs or whatever, but this was tabular data, so I went with just a table-type approach. So you'll notice up top that when the header rows are clicked, I call the sort function that you saw back in here, that calls in, passes the property to sort, and that calls the sorter service, and then it actually sorts the customer data. So you'll see that in action as we move along here. Other than that, all I'm doing in the table body is simply rendering some rows for all the customers, so pretty basic. So all I'm going to do here to get the data loaded, is we need to add our ngFor directive to loop through those customers in the Input property. So let's go ahead and do that, let's do an ngFor, and I'm going to say, "let's make a customer variable "as we loop through, of customers." I'm going to add a trackBy, and we're going to be tracking by the customer. Now let me show you a little bit on the trackBy real quick, so if we go into the core, and we go to the trackby.service, you'll notice that there's this customer call here, that's the one I just wrote the code for. It always passes in the index of the item we're looping through, and then the object we're actually looping through. Now I'm saying that I would like to uniquely track each of the items being added by the customer's _id, this is something that comes from MongoDB, it's the unique identifier. So if you're new to trackBy, this is a great way, if you have data that's being added and removed, or modified in any way, in a table, or divs, or whatever it may be, by adding a trackBy you can really enhance the DOM manipulation. Instead of having to kind of delete all the rows, and then re-add them every time, it can just selectively update the appropriate rows as needed, so it adds some efficiency here. So trackBy is the object injected into my component.constructor, and then customer is the actual function I just showed you, that returns the unique identifier. I could have said the index, zero, one, two was the unique identifier, and that would work too, but since every customer has a unique identifier, we're going to use that. All right, now you'll see I'm already using this a little bit, so I render the customer.gender to render the image, and then what I'm going to do for the rest of this is just grab some snippet code to kind of overwrite what I have in here currently, and I'll paste that in, and all I'm going to do is bind in the firstName, I'm going to run that through a capitalize pipe, which is a custom pipe I have. If you're interested in that, you can go into the shared, pipes, and learn about a capitalize pipe there. I also have a trim pipe in there. But we'll write out the firstName, the lastName, the address, the city, the state name, and there's the orderTotal we talked about. If you remember the DataService would give us an orderTotal. All right, so we're kind of ready to go there. We now have said that what we want to look at, which is our customers, our customers, of course, represent our Input property. That's being passed in through the parent, really it's just filteredCustomers, and then we hopefully are off and running. So let's go back, and don't see any errors here, so that's a good start. So let's go ahead and open this up in the browser, and I already have this open to 3000, let me refresh, and it looks good. Now you'll see that all the customers are being displayed. I have no pagination, no paging at all going on right now, I don't have a lot going on for clicking on these. Let's try out the filter though. Okay, it looks like that is working correctly, and then I mentioned their sorting capability, and it looks like that's working correctly. So we now have successfully retrieved the data from the customers RESTful service that we have with Node.js and Express, we did that with our DataService, DataService was injected into our customers.component, and now we're kind of off and running. Now we're going to do quite a bit of enhancing of this, but this is a good start. Now let's move into the actual form so we can edit our customers.
Displaying a Customer in a Form
Our next step in this process is to grab a single customer from the RESTful service, and integrate it into an Angular form, and that's what we're going to look at in this particular section. So if we go back to the browser we can click on a customer currently, and if you look down at the very bottom, you'll notice that a customer ID is being passed, that's the unique identifier from MongoDB, and when I click on it, it does navigate to the route, but it looks like we have some broken aspects here, so we're going to fix this and retrieve that customer and display it, and along the way I'm going to show you some of the different form features that are available when it comes to working with Angular. All right, so let's minimize that and come back in. I've already opened up the customer-edit.component.ts, and the html. Now this is very much like any component you'll open, I import the dependencies I need. You'll notice DataService is already available in this case, because we've already seen that, and it's already being injected, so we're good to go there. But I also have some other things we're going to talk about really quickly, like getting to the route parameter, the colon ID. If we go back to the routes, you'll notice I have this "customers/:id", so that we could load CustomerEditComponent, and that's what we're going to do here. So I need to be able to grab that off of the route, we're going to talk about how to do that, and then we'll grab the customer and display it in the form, and work a little bit with a template form. All right, so if we come on down, I have an initial customer, this'll be used a little bit later, as we do insert operations. We have the DataService as mentioned, but I want to jump to here. When we initialize the component I'm going to grab the route, and this is called an "ActivatedRoute", you'll see it's imported from angular/router, and this represents the current route that we're on, and what I'm going to do is say "Hey, "let's go that route, grab a snapshot of "the current route parameters named 'id'," and then that id will be the value that we have from MongoDB hopefully, assuming that it's passed correctly, and if it doesn't equal zero or something else like that, then we're going to say this is an update operation, and that we're going to need to get a customer now, based on that id, and that's what we're going to be after. Now it also triggers a HTTP callback to the server to getStates, because those are now going to be used in the form, there's a dropdown you'll see, and that code is already hooked up, so we call dataservice.getStates, subscribe to the states, and we simply bind those to the states property, so pretty basic. So now what we need to do is we need the ability to get our customer. So we saw how to write the RESTful side of this, let's now use the DataService that has a getCustomer function as well. So we're going to come into our dataService, and say "Hey, let's get a customer "based on the id," and of course we want to subscribe to that value, so let's subscribe, and I'm going to get back a customer hopefully. I've already imported the interface for it. And then I'm going to do a little bit of tweaking of the data here, I'm going to do a little cloning, and we're going to do it a quick and dirty way, but before I do that, let's add an error handler, and we'll just log out the error. All right, so that'll take care of subscribing, and then just logging out an error if we do happen to get one. Now what I'm going to do here is, I'm going to clone the customer, so that if they kind of leave, it doesn't mess up any other parts of the system. There's a quick and dirty way to do this, which, it works. I'm going to just stringify the existing customer we get, and then I'm going to assign it up to a customer property, and you'll see that customer property right here, we're going to override the default value, you'll see right there. So let's assign that to JSON.parse(cust), and again, the only reason I'm doing this is by making a copy of the customer, now any changes I make won't affect the original one that was passed in. Now in this case it won't matter, but if I was doing dirty checking, things like that, it may matter, and this'll allow us to just get a true copy of the data. There's certainly other ways to do that, but that's a quick and easy way. All right, so now we should have a customer bound into this property here called "customer" up top, and so we're good to go there. So we have a way to get the customer based on the id that's passed in, we plucked that off the route, we have a way to load the states, subscribe to that. Of course, and I haven't really mentioned this up to this point, but because we're using a built-in Angular Observable, that HTTP does, we don't have to worry about unsubscribing, it'll take care of that for us on both of these, so we're good to go. All right, so the next phase is, let's come into the component, and we actually want to work with a form, and I'm going to show you two ways to do forms in this app. This first one is a template-driven approach, and then we'll also look at a reactive forms approach. So for a template-driven approach, you end up putting more code into your actual template, very little code related to the form actually goes in to here, we just get the customer, bind it, and then the rest of the data binding happens inside of the actual template itself. Now with the other approach I'm going to show you, it's kind of reversed. But in this case what I'm going to do is come up to the form, and we need to hook in some Angular features. So for instance when they submit it, I'm going to use the ngSubmit directive, and I'm going to bind that to a function that you would've seen back in the component class, called "submit", and I'm also going to add a local template variable, let's just call it "customerForm", since that's what it is, and I'm going to bind that to the ngForm directive. Now that's going to give us the ability to check if the form's valid, invalid, if it's been touched, if it's dirty, those types of things. Now, why am I doing this if you're new to it? Well first off, this'll make it so when they submit, it doesn't do a full page postback. This will let Angular intercept the submission process, and that's just simply going to call back to submit, and then we'll talk more about that later as we get into our put and post type operations that we'll be working with on the RESTful side. This is being added simply so we have a way to get to all the details about the status, or state of the form. Again, we can check if it's been touched, or interacted with in other words. We can check if it's dirty, meaning the data has changed. We can check if it's valid or invalid, and so on and so forth, really nice. Now other than that I'm using a bootstrap CSS class, and I'm telling the browser, "Don't do "any of your validation features." Now the next thing we're going to do, and I'm only going to do this on one of them, because it's a very repetitive process in the template form world. We have an input, but right now we don't know what property that's bound to, but you can probably guess, it's going to be First Name. So I'm going to come in, and first off we have to put a name attribute in this world, so I'm going to say "name", and we simply put the name that I want this to represent, "firstName", that's the property I'm ultimately going to bind to. Now to do the data binding we're going to do the input and output with an ngModel, so some people call this "banana in a box," or "football in a box," whatever you like to call it, but of course the square brackets mean data's going to come in, the parentheses mean data can go out, so we simulate a two-way data binding, and I'm going to bind this to customer.firstName. Now I also need to know if this ngModel, which is bound to firstName, if it's been touched or is dirty, or is valid or invalid, and the way we're going to do that is by making a local template variable called "firstName", and assign it to ngModel. So this is literally just going to be an alias for our ngModel directive, it lets us get to some of the properties and functions if I wanted to. And then I'm going to say that this is required. Now the reason we're doing the name, is that's a requirement, first off. You need to used that when you're working with template forms, and input controls. We're doing ngModel for the two-way data binding, to bind to firstName of the customer, and then we're doing this hash, or #firstName, the local template variable, so that we can get to ngModel functionality properties and functions. So an example of that is with my error message. We're going to hide this if firstName is untouched, because they haven't interacted yet with it, so there's no reason to show an error, or it's valid. Now if it's any other condition, if it's been touched, and then they find that it's invalid, then "First Name is required" is going to show. And so this'll let me get to the touched, untouched, valid, invalid, dirty, pristine, those types of things, and this firstName here? This actually represents that guy, right there. All right, so that's kind of how we can hook in. Now there's a lot more we could do, but you're going to see that all of the different form tags, let me give us ourselves a little more room here, are going to be the same. So for Last Name I have a name, I have an ngModel that binds two-way, and I have a lastName local template variable, and then it's doing the exact same thing here. And this would certainly be an opportunity, potentially, to write a reusable component or something, to show and hide the error message. Same thing here, I have gender on a radio button. We'll run this in a moment, I'm doing the exact same type of thing you'll see. The difference here is "name" is going to be the same, because it's a radio button, so I only want you to be able to select one of them. We have an email, an address that are the same, a city, and then State's a little bit different because it is a select, so you'll notice I'm binding to the customer.stateId, which is something that comes back from the database, and the name of "state", and it's required, but to actually get the options, I'm using this ngFor to loop through the states that we got, you saw the states property in the component, and then I'm going to use an ngValue directive, and we're going to bind that to the state's id, and that'll basically hook up that to the stateId here, so that if they've already selected a state for a customer, it displays in the dropdown. Now other than that, we have some functionality down here which we'll get to later, to show and hide a deletion message, and do things like that, but I do want to show the button to submit here. You'll notice it's disabled if not customerForm is valid. So this is a nice and easy way to use the customerForm local template variable I defined right here. Again, that's just an alias for ngForm, and all we're doing is saying, "I would like "to disable that button if it's not valid." Why would you be able to submit the form, you know, if they haven't filled everything out. Let's go ahead and save our work here. Let's go on back, now that we have our component, let's start from the beginning, so let me load this. Refresh to make sure we picked up those changes, and let's try to navigate to Marcus, all right, perfect. So now notice that we see the ID up top, I showed you how we can grab a snapshot of that, pluck it off the URL, we then go to the RESTful service with our DataService in Angular2, that loads the customer, and then we updated the customer property, which is then bound using the ngModel directive to each of the text boxes, and things like that. Now later we'll talk about how to make the Update, and the Delete, and things like that work, but hey, we have a good start. We have our states are loading, the selected state for each customer looks like it's working. If I cancel, I can go, this should show "California" for instance, so let's look, and it looks like that's working. So we're off and running here. So now we have all the get operation code in place. We have our customers, we have our states, and now we're able to select that single customer, so now we can start to move forward to other aspects, but before we do that, we're going to look at another option for building out forms.
Converting to a 'Reactive' Form
In a previous section I showed how we can take data from a component, and bind it into a form using a template-driven approach. Now in that particular approach we're using ngModel to do the data-binding, and get data in and out of the form, but the template-driven approach isn't the only game in town, we can also use a Reactive forms approach. And so what I'm going to show you is just a quick look at what that type of code would look like. So going back into the customers folder, in addition to the customer-edit.component which has our template driven approach for forms, I also have a customer-edit-reactive. Now for the customer-edit we used ngModel, so if I go down into our shared.module, we needed FormsModule to be imported to get to ngModel. You'll also notice that I've imported ReactiveFormsModule though as well, and this is going to give us this alternative approach for binding data, and getting data in and out of our forms. So if we go into the html, you'll notice some different things going on up top. We still have an ngSubmit, now the way the submit is being called is a little bit different, so I'll talk about that, but we also have this new directive we're binding to called "formGroup". Now this gets us into our reactive forms, so before I dive into the form in more details here, let's go into the code for the component, and right up top you'll notice that I'm importing some reactive forms objects. We have a FormBuilder, FormGroup, and Validators, then I also have some custom validators in this ValidationService. Now if we move on down, everything looks very similar, until we get right here, so first off, we have something called a "FormGroup". Now customerForm is my variable, it's of type FormGroup. Now this is an object that's going to have all the different form controls that I want to bind into the template, but we're not going to be doing it using ngModel with this approach, so let me jump on down to ngOnInit, and I'll show you. So first off, we still have to get our customer based on the id from the route. We still need our states for the dropdown, that part doesn't change, but you'll notice this call to buildForm. Now this is where we get into our reactive forms approach. So what I'm going to do is use the FormBuilder, which you'll see is injected right here into my constructor, and this is going to allow me to build a group of form controls. Now I could actually new up a form control instance, but this is a shortcut way to give an object literal, and then these are the names of the different controls. So I have my firstName, lastName, gender, and my, kind of standard things I need in the form. Now you'll notice that the value here in this array, the first value is going to be, "Do I want to assign a default value to this form control?" I do, I want to say, "Take whatever customer "firstName is, and bind it." Now when it first loads, customer firstName's just going to be empty, and you'll see all of these are empty, but once we get the customer, we'll update that. So this first value will be, what's the default value, and then you put a comma, and then you can define as many validators for this particular form control as you would like. Now most of these are just required, except for email. You'll notice email, I have an array of validators, and it's not only required, but I have an email validator. Now that just does a regular expression check, and makes sure it's a valid email address. Now the rest of this all looks the same though, you notice it's kind of very repetitive. Now what's nice about this is, I'm able to define, not only all the validators and default values, but all the form controls in code, so I could actually build these up dynamically if I needed to. Now I don't need to in this case, but I could. Maybe you have to do a questionnaire, and a lot of the form control data's in a database, and you don't want to hard-code what that is going to look like into the template itself, you want to make these form controls, and then have validators based on what the database says you should have. Well, we can do that here. Now what's going to happen is, once we get the customer, I'm going to call this.buildForm again, and now customer's actually going to have a value for firstName, lastName, gender, assuming it was found, and then we'll go ahead and use that and bind it into the form. Now we also have a way to get the value as they submit it, so when they pass in the value, this is going to pass in a FormGroup I'll show you in a moment, we can also check not only the value, and get to that, but we can also say, "Is this valid?" Now we'll talk more about this code a little later as we get into the PUT and POST operations, so we'll need to handle that separately as we build out our RESTful service. Now that's about it, this is the key part of the reactive forms, you actually build up these form controls, define any default values, and define any validators. Now what I need to do is, this.formBuilder.group, this actually builds a FormGroup, you'll see that up top there in the IntelliSense, so this is of type FormGroup, it's a group of form controls. Now what I need to do is hook that into the form, and the way we do that is using the formGroup directive, we bind it with our square bracket syntax, and then we bind it to that property I just showed you, customerForm. That is the definition of all the form controls. Now that's the first step. Now, what if things get really nice with this reactive forms model is, notice how clean my input tag is. I don't have to put a name, I don't have a local template variable, I don't have to put ngModel, I just put formControlName, and this firstName here actually is referencing that entire control, right there. It's not actually referencing the customer firstName, it's referencing this firstName, which is a control, and that references the customer's firstName, and has the validators. So notice I don't even have to put required on this, it's going to be added automatically through this reactive forms model. So this is really clean now, and that's the case for all of these, they're very, very simple. You'll notice all these form controls going on down, in fact, even the select is very simple. Now I still do have to loop through all the states and things, but other than that, it's very, very similar to what you saw earlier. And everything down below is pretty much the same. Now, what's different though, is notice if we go, for instance, to the First Name error message here, so if I scroll over a bit we have "First Name is required", now this is a little bit longer, but it's only because we don't have to put the local template variable, so it's actually kind of nice. I'm saying, "Let's go to customerForm," which is exposed to me as a variable I can get to throughout this form, "go to its controls collection, "go to its firstName control," which again is this guy, right here, which has this "required" on it, "and see if that control has been, "if it's untouched, or if it's valid." Now likewise if I go to the very bottom here, to where the button is submitted, I can also go to customerForm and see if the form, as a whole, the FormGroup is valid, and if it's not, we can disable the button. And then we'll talk about some of these other aspects as we move along in the course. So this is the reactive forms approach. You use the formBuilder to build up a FormGroup of controls, then you use the directive here, called "formControlName" to actually bind to the name of that control that's been defined, and then it kind of takes over from there, and handles the data binding. Now the last piece is, you don't have the two-way data binding here with the customer. So customer is not automatically going to get updated as they type into the text box, as it did with the template-driven form and ngModel. Instead what we're going to do is we're going to pass the customer form, the FormGroup of the controls, into submit, and then I've already shown what happens there. When submit is called, we can then destructure some of the properties it gives us access to. I can get to the actual value of the form, and that allows me to get to the different values like the zip, the first and the last name and those types of things, and I'll show how that's used a little bit later. And then I could also check if the form is valid or not. So this is a totally different approach, but this is another option in Angular2, and definitely a worthwhile option to explore. Now there's other courses on Pluralsight that go into more details, this is certainly not a forms course, but now you've seen both techniques for working with data that comes from our Node.js Express RESTful service.
We've made a lot of great progress so far. We now have a RESTful service that's capable of accepting GET requests up to the service with Node.js and Express, and we can return our customers, a single customer, and some states. We've also looked at an Angular service, and how it can provide reusable functionality, and use HTTP and observables to call up to the service, get that data, and then through observables, provide that to components in the application. And we've also looked at how we can data-bind our data into our components, such as our grid, and how we can use different form techniques, including a template-driven approach and the reactive forms approach. So now that you've seen how to get the data from the RESTful service, and integrate that into Angular, let's move forward and see how we can start to modify that data.
Inserting Data Using a POST Action
As you're working with data, you'll reach a point where you need to insert that data into some type of a data source. So in this module we're going to talk about posting data and how we can handle a POST operation in our restful service and how we can actually send data through a POST action up to the server with Angular. So we're going to start off by talking about the RESTful service again, and we'll first create a POST action. I'll show you how we can define that route, and then hook it up to a function that'll get called. Which then will take the Customer's Repository you've seen up to this point, and use it to insert a customer into the database. From there we're going to then switch to the client side, and we're going to use the Http client in Angular to post a customer up to the newly created route that we're going to be dealing with. We'll talk about how we can then use that service in our template driven form. And we'll also revisit the Reactive version of the form. So we'll get some reuse going with our data service class. So let's go ahead and start things off in this module by first creating our POST action in our Express RESTful Service.
Creating a POST Action to Insert a Customer
Let's start things off by learning how we can build a post operation, or action, into our Node.js Express RESTful Service. So then later on, Angular can send data up to the service, and then the service will take that data and insert into MongoDB. So earlier we talked about how we can add begin actions into our API customers route, and what we're going to do now is handle the case where a client wants to post some data in the body of a message, and then we're going to need to write some code to extract that data out and work with it. And that's what we're going to look at in this particular section. Now a POST operation normally causes the page, if you're in a web browser anyway, to reload entirely, of course. So when I say POST operation, in this example, I'm talking specifically about an Http call that would be made from the client up, and then in that Http call, in the body, the data would be posted. So the way this would start out is on our router, we need to add the POST operation. So we can say, hey router, when a post comes into our root route, remember our root route is the API/customers, then we would like to call some type of function, and in this case it's going to be Insert customer. And then, as mentioned, I always like to bind it to this to make sure the proper context is always being used when the function is called. All right, so that's our first step. Pretty simple. Now before we go right Insert customer, let me go ahead and just put the shell for it down here. Let's go ahead and first talk about what is going to go on behind the scenes, as far as inserting that data into the database. Now as a quick review, we talked about schemas and models with MongoDB, and we talked about that there's a Customer Schema. Well, you've seen the form up until this point in the course, and pretty much this info up to here is what's in the form. So really what we're after, is we want to get that data that's going to be posted up, and then shape it in a way that matches the Schema. Now, that's going to be pretty easy because we have a model that uses the Schema under the covers, we've talked about how that actually makes that data shape in a certain way in MongoDB, and that includes inserting the data. So we're going to be using this Model Object here in just a moment. Now the code that actually does the insert, just like with the git request, the Selects, is the Customers Repository and there's a States Repository also going to be used here. Let's just on down to in this Customer Repository to the Insert Customer Function. Now you'll notice it takes the actual data, which is the body of the message, it takes the state, we'll talk about that in a moment, and then of course your callback. Now the bulk of the code is really right here, though. You'll notice I take the Model Object, in my model, my customer here is the actual model that I just looked at with the schema. And that particular object, and this goes for any mongoose object, that's a model object has a save function. So we can save it, that would give us back the saved customer, we check for errors, or we simply return the customer here. Now you'll notice, I'm actually grabbing the data, though, that was passed in the body of the request messes that came up to the RESTful service, and then that's forwarded into here, and I'm grabbing that and moving in the first name, last name, email, into the model logic properties. Now, there's multiple ways you can do that. This is probably the easiest way to do it. And these are just the properties that I need to be able to do an insert. You'll notice there's no orders here, because orders are something that, for an actual customer, you can have orders or maybe you're a new customer, you don't have orders. So that's why I don't have any orders there, but you'll notice this is all the form type data being mapped into the customer. And then we simply call save, and we're done. It's pretty straightforward. Now there are multiple ways you can do inserts and updates and things like that in MongoDB, but the save happens to be one of the easiest ways and it works very well for our purposes here. So that's actually what's going to happen behind the scenes, so now we need to write the code that can interact with that insert customer function. So what we need to do first is, I would like to come in and get the customer state, because the state is going to be stored along with the customer. So first thing I'm going to do is, let me just do a log like we've been doing, and we'll log out that we're in this particular function, that way it's easy to follow, especially while you're still developing the code. All right, now what I'm going to do is I'm going to take our States Repository, which is up above. We imported that. And I'm going to call it getStates. Now in the request body, that's going to be all the data that's posted up, you're going to have all the customer properties, well one of those is stateId. So I'm going to pass that in, and then that's going to give us back a state, as long as it's able to find it, of course. All right, now I need that because I do want the state, the object itself, that model, to be associated with the customer. Now I want to also emphasize is in MongoDB, I don't have to do that. Obviously that means that customers, if they're all in, for instance, Arizona, then they'd all be duplicating that state object. There are ways you can separate out and reference collections, but we're going to keep it pretty simple and just nest that state with each customer. All right, so what we're going to do here is the kind of standard code, so I'm going to grab our little air handling code to save on some typing. I'm going to write out that, hey we got an error, and we used that Utel inspect again to write out the error. And then notice I'm going to return a JSON object here, and we give it a status of false. Give an error, state wasn't found. And then, obviously, we weren't able to insert the customer so nothing was returned there. Now the next step is, if we did get a state there's no error, we need to come in to customer's repo and do insert customer, and then I'm going to give it the entire body, we saw that earlier, I'm going to give it the state, it's going to be associated with that customer, and then we have our callback function. We'll have our customer here. All right, so the next thing we can do then is, again, handle the error. So let me go ahead in this code, I'm just going to grab, because it's pretty straightforward code. And so we'll handle the error again. But if it did work, we'd hit this part, and notice we're going to say status is true, there was no error, and I'm just going to echo back the customer. And the reason for that is this customer we get back from the save operation in MongoDB, is going to have the updated customer id. That underscore id you've seen a few times. We don't really need it in this particular case, because once they insert, you're going to see it's going to go back to the grid and load the fresh set of customers, but maybe you wanted that for whatever reason. I'm going to leave it in here, just in case you ever wanted to play with it and get that customer id. All right, so we're good to go there. We now have our Insert customer ready to go, and we're going to first get the state, assuming that works, we'll then go ahead and call in a certain customer, pass in the state and the actual data. Again that maps the data into the customer model object, saves it, and then hopefully gives us back a customer here. So that would be our routing, going back up to the top here. We have our post requests handled, so if they post up to API customers, we call Insert customer. And then the rest is taken care of mainly by the Repository Objects, or modules that we imported up top. All right, now, how do we know if it works? Well let's go on back, and we won't know by running the app. Actually before I do that let's go to the console and see if we have any errors. Nope, looks good. Looks like it was able to retrieve data. I don't see any errors here on the server side, so that's a good start. But how do we know that the insert operation itself is actually working, because right now the client side of the code isn't ready to do that yet. So to test this out let me go to post man, and I've already loaded up the route, and you'll notice I've changed this to a post. And I have some data, so you'll notice raw is selected. Now this is a great way to test out your POST operations, later we'll talk about a PUT operation, and you've already seen me do gets or selects. So what I can do now is POST up a customer. So this is the raw body. And I can hit send, and now if we scroll down, all right, great this worked. So you'll notice that I have status is true, no error, and then here's the actual customer that was inserted into the data base. There's the id of the newly inserted customer, doesn't have any orders but does have a state. You'll notice. In this case Alabama matches, that's the state id. If you look down here, it has a state id of one. And so it looks like it works. So we are on our way. We now have a RESTful service operation that can handle a POST request, and it looks like the insert is also working in the MongoDB. Now we need to move on to the next step of the operation, and have the client actually post the data up to the RESTful the service.
Making a POST Request with an Angular Service
Now that we have a RESTful service capable of handling POST requests, let's enhance our Angular app, and add some code into our service that's capable of posting a customer up to the RESTful service. So going back into the code, I've opened up the app for folder, and I'm back in the data service TES file. Now, earlier we saw how we can inject Http in the constructor, so it's already available, as well as all my observable imports. We're pretty good to go here, as far as getting set up. What I need to do, though, is add the ability to insert. Now this is actually going to be pretty straightforward to do. There's not a lot of code we actually have to write here. So we're going to start by adding an insert customer function. And it's going to take the customer, of course, that we want to insert. So I have an Icustomer interface I've already shown, and we'll use that so we get some strong typing, and then I'm going to return observable of, and I'm actually going to return a customer. Now we need to add some code that returns the observable, otherwise you can see we're instantly getting an error here, saying you don't have a return type. Now from here it's very similar to what we've already done. I'm going to come in and return an observable by calling Http., but I'm going to use the POST. We're going to POST the data up to the server, and in this case I'm want to go to the API/Customers. And if you recall we have a base URL that already has that route defined in it. Second thing I'm going to do is give the customer that we actually want to POST in the body of the message. We'll give that. Then we want to map that data that comes back. So I'm going to map the response into a callback function. Like so, and then, of course, we always want to catch any errors. So we already have our handle error. So what I'm going to do in the map is extract out the data. Now as a quick review, if we go back into the controller, and insert customer, you can see that if it works we'll hit this else block. And I'm going to return a status of true, no error, and hopefully the customer, assuming it all works. So what I want to get to is the customer because that's what whoever calls this is ultimately expecting to get back. So first thing I'm going to do is just create a constant representing the data. So let's parse the JSON by pulling the JSON function. And then I'm just going to log what's happening here on the client side. So we'll say insert customer status, and we can write out the data status property that I just showed, which should be true in this case. If it works. And then I'm simply going to return data.customer. All right, so now we're in business, and we're able to get to that specific data. And then what the caller's really after, though, is the insert customer. They might want to get back to that data to modify it, they might want to get to the Id, just really depends on what you want to return. I could have just returned the Id potentially. A lot of times I'll do that. But in this case, I'm going to say, hey I'm going to give you back the customer, and it's up to you to do something with that customer. Now that's all we have to do in the service to now get this data posted up to the server, get back that customer, and then extract that data out, simply by parsing the JSON and then getting to the appropriate property. So now we're ready to go on the data service side. From here, we need to integrate the call into this insert customer into our component.
Modifying the Customer Form to Support Inserts
Now that we have an Angular service capable of issuing POST requests up to the RESTful service, we need to integrate that service in the call to our insert customer function into our component. So we're going to look at the customer form component that we saw earlier in the course, and I'm going to show you how we can add some insert capability to it. So going back into the code, here's the insert customer that we looked at in a previous section of this module. And we know we're going to get back a customer, but, of course, I have to send up a customer in the first place. So we're going to capture the data in the form, and then we're going to call this insert customer which will then handle posting that to the server. So I'm go into my customer's feature form, and open up customer edit component. And this is already going to have the data service injected into it. So we're ready to go there. We used this earlier for our getCustomer. And now what we need to do is add some code to handle the insert operation. Now I have a submit, and as a quick review, if I go back to the form, we have our ng submit directive as bound to the submit function. Now, because we're using ng model, in this template driven type of form, the customer will automatically be updated as they type and lose focus on the text box and things. So all we need to do is grab that customer and push it to our service, which then calls our RESTful service. So coming back into here, I need to know, though, if I'm doing an insert or if I'm doing an update. So the way I'm going to do it, in this example, is say if the customer has an id, now underscore id would be an id for MongoDB. Then this would be an update. So we'll come back and add to that in a later section. Else we want to come in and we'd like to add in the code to insert the customer. Now this is pretty easy, we already have the data service so we can get to that. And we already have an insert customer on that. So all I have to do is pass it to customer. And then, of course, we need to subscribe to the observer. Now I'm going to get back a customer we know. So we'll say ICustomer here as well. And now I need to check, what do I want to do. So if we got back a customer, I'm going to keep this pretty simple here. And actually add my arrow syntax. Then we're going to come on in and say, let's send them back to the grid. So if we got a customer, I also injected a router. Let me show you that up top here. So we have our router that's being injected, and I'm going to programmatically send them back and navigate to the root customers route. So I'm going to come on in and we'll say customers. And that'll send them back to the grid page that you saw. Now else, if we don't get that back, we probably have a problem. So I'm going to come on in, and I'm just going to write out an error message of unable to add customer. Now I'm only getting back a customer from the service, but this is where you have to decide, do you want to return the error data all the back to the component or not? That's kind of on you. The only problem with that is, if the server returns some more detailed error info, we generally don't want to just display that in an error message. In fact, in general, in production, you don't want any detailed errors coming back from the server. So we need to just be careful on how we handle that. So in this case, I'm just going to say hey if we don't get back a customer, something went wrong, and we're just going to write out and let them know, hey we're unable to add the customer. Now they can contact the help desk, or whatever it may be, and we can check the logs and things like that to see what happened. Now that's all we would have to do. Now we can also come down, and if the subscribe and the observer we're coming back doesn't work, we can add in an error handler here. And in this case, I'm just going to go ahead and we'll log that to the consul if we get an error. So nothing really fancy in this particular example. But! I'm going to put an any there. This can be any type of error. This will allow me to log the error if we get one, but if all works as far as getting back the observable, if there's a customer then hey we're great. The customer should be inserted so let's send them back to the grid, else. We'll go ahead and just write a simple message. Now the error message, we haven't really talked about that, it's just a simple property, and it's used towards the bottom of the form. I'll show you that down here. And so you'll notice we have an error message that'll be displayed right here if it's available. So real simple example there of logging the error. Now that's all we'd have to do then. So when they submit. Let me go ahead and save this. When they submit, because this would be an insert, we wouldn't have an id on the customer. In fact, the customer, when it first gets loaded, you'll see id is kind of purposefully left empty here. And so in this case it'll be empty. So that won't qualify. That'll hit our else block. We then call our data service, it handles all the POST operation. We simply subscribe to the observable. So let's go on back to the browser here, and what I'd like to do is try it out. So we have an Add New Customer you can see. Let's go ahead and just insert some test data. Now before I do that, I want to show you the data service. So I'm going to set a break point in my sources, and let's come on in to insert customer. We're going to make sure that the customer gets passed in properly. Let's also try to set one here when the data hopefully comes back from the servers. So let's input some just test data. And select a state. Let's hit insert. All right, so here's our customer that was sent up. So you'll notice we don't have an id right now, that's correct. We have our bogus test data. But it looks like that's pretty good. So let's move on. Now here's the data that came back. So we got status true, perfect. And we have our customer. In fact, I can mouse over this one, and you can see we now have an id, and that would be new id. Now what it should do then is send it back with a router to the data grid page. So let's try that out. And it looks like it did and we scroll on down here. Somewhere down here we'll have test in here. So we're good to go. I have a couple from trying it out earlier. So there we go. We have now an example of posting data up to the server, and then we have our RESTful service bill tab to extract that data out of the body of the message, and then it can work with it to insert it in the MongoDB.
Exploring the 'Reactive' Form
The template driven form in the application now has insert functionality. So let's switch gears to the reactive version of the form and see what we would need to do there to get it to support inserts. So earlier we had a submit in the template driven approach, and we checked if there wasn't a customer id then we go ahead and insert this customer. Now we can do the same thing in the reactive. So if I go into that it actually looks very similar. We come on down here to the submit. There's a little bit of code in here already. Now with a reactive one, the customer's not being bound directly, if you recall. Very different than the template driven approach that uses ng model and has that two-way data body. With the reactive, we're going to get the value of the different fields in the form, and then it's on us to do something with that. Now I'm just going to pass that along. In fact, I'm just going to paste in the code here for the else, and we're going to do the same call we did with the template driven one, but notice this time I'm passing the value. Now I'm doing a little bit of fix up work here to say if we have a customer id, well whether we do or not, let's go ahead and assign it to the underscore id of the value. And I'm also setting the zip because the form doesn't actually capture the zip. Although you could certainly modify that. Now what I'm going to do is just simply pass up that value, and then, from here, it's identical to what we saw with the template driven approach. We call data service insert customer. It's just that we're passing an object that implements ICustomer, but it's not actually the customer object. It's the data values, and it just so happens those values match up with the ICustomer interface. Let me save this. And now what we can do if we want to run the reactive form, you'd actually have to change the routing a little bit. Right now you'll see it's set up to hit the customer edit component. So I can just comment that one out, and we can switch it to the customer edit reactive component. Save that. All right, so now when I navigate to the route we'll be hitting the reactive form and you'll see that already loaded the data, but, of course, we want to do add. So let's do an add here. And let's do, I don't know, test2 or something like that. We'll put in some fake data. Now you'll notice in this case the button's still grayed out. That's because I accidentally didn't click quite right on the gender, so you can see that's working correctly now it's lit up. We'll go ahead and insert. Go to the very bottom. And there we go. There's our test2, but this is actually being driven by the reactive form now. So you can see that one of the beautiful things about services and Angular is, first off, we're getting reuse. Now you wouldn't have both forms obviously. You would have one or the other. I'm simply showing you the different techniques you can use in your Angular applications. But as you start to work with these forms, now you can choose which one do I want to use, and regardless of the choice, you can go ahead with the data service that you have and reuse that functionality.
In this module we saw how we can create a POST action in our Node.js RESTful Express Service. It can be used to perform an insert operation in the database. We also saw how Angular's Http client can be used to then issue a POST request, send up with customer, RESTful services processes it, and then we get that insert operation going. We also saw, along the way, how we can handle errors and deal with those types of things that may come up. Now one of the things that you really saw, as well, was Angular's two services are great for reuse. Now we had two types of forms, and yes, you would only pick one, of course, in a real lab. We had the reactive and the template driven, but both of them are able to reuse the Angular service, the data service that we had. And whether it's calculations, or business rules, or Http calls, services provide a great way to work with reusable code.
Updating Data Using a PUT Action
We've seen how we can get data from our RESTful service and even how we can insert data, but we haven't looked at updating data. And that's what we're going to do in this module. This module's all about issuing and working with PUT actions, or PUT requests, that are going to flow up from the Angular application to the NodeJS Express RESTful service. Now, as we move through the course, we're going to start off by talking about how do we define a PUT request in our Express type of service. So we'll talk about that and it's going to be very similar to what you've seen up to this point, in fact, the concepts are almost identical. So it's pretty quick and easy to work with. From there, we'll move back to the Angular service and we'll add the ability to use the HTTP client to call up and make the PUT request to the RESTful service. We'll then go into our template driven form and go into the component code and see how we can then call from the component into our data service. And then we'll explore our Reactive form again and enhance it slightly to also handle the ability to update a customer and do the PUT request to the service, which then would get that data up to the RESTful service. So let's go ahead and get started by talking about adding a PUT request into our RESTful service using Express code.
Creating a PUT Action to Update a Customer
Making a PUT Request with an Angular Service
Our RESTful service now has a way to handle PUT requests that come up to the service, so now we're going to switch our focus to the client side, and add the capability to make a PUT request from an Angular service. So, in a previous section of the course, we talked about how we can do an insert operation with a POST. And you're going to see that doing something like a PUT operation, which is really an update in this case, is pretty much the same type of thing. Now, of course, we're not going to use POST, we're going to use PUT, but everything else is very, very similar. Let's go ahead and add this. So I'm going to make a new update customer and this is going to take a customer object of type I customer, and then it's going to return observable, as usual. So go ahead and do an observable of, and then we can return whatever we'd like here. Now we'll return observable of I customer. Alright, now in here, all we have to do is simply do our HTTP PUT, so we can do our return HTTP. And this will look really similar, but what we're going to do is we need to pass the URL, so we're going to add a little slash here, and we're going to add customer._ID which is the Mongo ID. Now the second parameter is going to be our data, and that's going to be the customer, that's very similar to what you'll see up here in the insert. So really no change there. And then, we could add other things, options, and we'll talk about that a little bit later. For now though, we're going to come on in and we're going to add our map and we'll add our response again, and then we're going to add a little bit of custom code in here, we'll come back to that, and then we'll add our catch. We'll call our handle error. Let's first off grab our data, so we'll say const data, and we're going to parse our JSON again, just like up above. And I'm going to do a log here just so we have something to refer to when we do run it later if we want to. So I'll say update customer status, and we can write out the data.status. Now if you remember, we get back a status, an error and we get back the customer data. Alright. And then, we're going to return data.customer, okay. So that's why we have an observable of customer just like we had up here. So you could see that this is a very, very similar piece of code to what we had up above, and we could probably even work with automating this a little bit more, but we'll break it down piece by piece here again. So, we return the observable, but this time we do a PUT. Now a PUT, of course, only works when we're doing a HTTP type call. Of course, you can't do PUT with regular forms in the standard web world. But in this world, you can. So if you haven't used PUT before, this is our way of saying, hey, I'm not doing an insert, a POST, I'm going to do a PUT operation. So we go ahead and add that customer on up there. Then we do the same thing. From here, you've already seen this, we parse the JSON, do some logging and then return that actual customer here. And we catch the error. So that's really all we have to do. So now we can move to the component level and we can consume this update customer function.
Modifying the Customer Form to Support Updates
Earlier, we enhanced our Angular service to support a PUT or an update, so now what we're going to do is consume that update customer function that we added in one of our components, so that we can actually handle the update as the user edits a form. So before we jump into the component, I want to emphasize one thing, and this has been mentioned a few times but it's important to address. We're doing some really basic logging in our data service here, and this is an opportunity where we might even have another HTTP call where we log some of these messages back to the server. So we'd have to set up a separate RESTful service for that, but we could certainly do it. Now the reason I'm mentioning this is, in my opinion, the service is really the place that should be logging whether it's local or calling back to the server with the data. The information about what happened, in this case with the PUT request, I want the component to either get a customer or not get a customer. It's really up to them do deal with that. But at the service level, we want to be very specific about what happens when things break and what happens when things work. Now in this case, I made the decision just go ahead and return the customer, but you may say, "No I don't want to do that, "I want to return the raw data, for instance." and, that would certainly be a valid option, now the component would have to know to go to the customer property, but they would have access to get to the status and the error message. Now, that's something you would have to debate and determine in your own world and own applications, how you want to handle. But, I'm going to go ahead in this example, and just keep it as is, and we're going to return this I customer back. So now what we're going to do is back in our customer edit, that we've seen, we already have the functionality to do an insert customer. And so we're going to do this same type of thing. When they submit the form, the submit function gets called here, if they have an ID on the customer, then that means we already have an existing customer and therefore we probably don't want to do an insert, we want to do an update. And so now we can call into the code and actually do this. So we're going to use our data service that's already been injected. We'll call our update customer and we're going to pass the customer that we have up towards the top here of our component. So this will actually be, not this instance of the customer, but if we had a customer earlier when we get it, that's what would be displayed in the form and that's what the customer would represent, of course. Now we're going to do a very similar operation to what you see down here, actually. In fact, I could probably cut and paste most of that, but we'll go ahead and type this one out. So I'm going to subscribe, and this is going to get me back, as we just showed in the service, an I customer. So we'll go ahead and add our arrow function here for our callback for the subscription. And then we can also have, I showed this a little bit earlier, we'll take that out and we can have kind of an error function at the bottom of this, where we could do some additional logging that might be just specific to the component itself, though. So we'll just do a console.log error. Now, obviously, the error logging here is pretty basic, but this is where you could take it to a whole other level and you could from here call back to the data service which then logs an error. But I think the data service, because it already knows if there's an error or not, should be responsible for that. But, you know, that is very subjective. So in here we'll say if we did get a customer back, then that's a good thing. Because if you recall, the RESTful services, if there's an error does not return a customer. So we'll go ahead and do the same router navigate, and we're going to navigate, basically, to the root type of route, the customers. Alright, that'll take care of if it works, now if it doesn't work, we'll put our else and we have this error message property, let me just scroll up to that as a reminder. We have this error message property that's actually bound into the UI with a component. So we're going to go ahead and update that error message with our error message. We'll just say unable to save customer. Okay, so now we have all the code we need to consume the update customer function that data service provides, we subscribe to that observable we get back. If we get a customer back that means things are good. We're going to go ahead and navigate back to the grid, the home page, if you will. Otherwise, we're going to go ahead and log some type of an error, and in this case it'll actually display it though, to the UI. And now we're ready to rock and roll here. So let's go to the form itself. And let me refresh to make sure we pick up all the changes. Let's go back to our Marcus Hightower here. Alright. And let's just do Marcus2 and maybe Hightower2. Come on down, it's an update. Go ahead and save it. And looks like it worked, but, I always like to double check. Let's refresh, yep, looks like it took. And we'll go ahead and fix it back. Alright, so our update is working. Beautiful. Now, all that's happening for the update button, earlier we saw an insert button, but I have up here in the component an operation text. Now, it defaults to insert, but when we get the customer, if we have an actual customer with an ID on it, in other words, the route up at the top has that ID which I'll show that in a moment. Then we're going to change that operation text to update, and that's all I'm doing to dynamically change the text and the button. So if we come down to the button itself, you'll see down here we have our submit button and it simply binds to the operation text. That way I don't have to have two buttons for an insert or for an update. Now, the other thing we're doing is as we run this, of course, we have our routing. So you'll notice as I click it, there's our route parameter ID. And, of course, that's how I'm determining to change the values. So if we go back into here, I grab a snapshot of the params, we talked about this earlier, if it's not a zero or basically some other item, if I do an insert, I always pass a zero up there that's why I'm checking for zero. But if it's not, then we're going to go ahead and change it, update it and get the customer. So, that's why the button is changing very easily between update and insert and things like that. So there we have it. We now have a submit function that's all filled out now. If we have an ID, we can do our PUT to the server, our update. Else if we don't it's an insert. Now we still have some new functionality we're going to add into this later, but we're starting to make some good progress now.
Exploring the 'Reactive' Form
The final piece of code we're going to explore is enhancing the Reactive form to also support the update capability that we now have available on our RESTful service. Let's jump on into that and explore what we can add there as far as code goes. So, as with the template driven form, you'll see that we have a submit and we talked earlier about how in the Reactive world, you can get access to the actual value which is the different form control values and whether the forms valid or not. And we'd already added the ability to do an insert. Now you'll see this set up in this type of form is identical to the template driven that we did earlier. With the submit, I check if there's an ID. If there is, we're going to do our PUT request to the service, otherwise, we're going to go ahead and do our POST, our insert. Now to make this part work, we can come on in and I already have some code here because it's very similar to what you saw earlier. So we're going to do the same thing. We're going to call into the data services update customer and pass in the value. Now in this case, again, the value is of type I customer, so we're really passing the customer, it's just slightly different in how we do it with the Reactive world. And then, we're going to get back some data. Now, previously I was returning the status. Now if you remember, we have the ability to get back the status, the error and the customer. But, I mentioned earlier in a previous section in this module, that I like to have the component kind of be in the dark, as neutral as possible about errors and things. And so, instead, what we can do is we can return the customer again and you'll see this is pretty much identical to what we did down here with the insert. And that way, the components only worried about if it gets a customer or not, the service itself will take care of logging and things like that if there's any errors as it communicates with our RESTful service on the server side. Alright, so now that we've seen that and added that code, let's go on back to our form. And I'm going to reload it back from the home page here. And right now this is the template driven. So let's go back and what I'm going to do is come into the routing, we'll come on down here to our app routing, and right now you can see our customer edit component. Let's go ahead and change this. And we'll comment in the customer edit Reactive. Save this, and reload. And we should see the same type of functionality. So now we'll click here, now it's the Reactive form. We'll do update and let's refresh just to make sure it took. Looks like it did. So you can see that whether or not you choose the template driven approach or the Reactive, the code itself for interacting with the POST or the PUT, or whatever it may be type of request to the service, doesn't really change much. What changes is, obviously, in how you write the form and how you work with that form. But it's kind of good to know both approaches and that way as you work with these, you can see that the code itself is very similar actually. So that's a quick exploration of the Reactive form for editing our customers.
In this module, you've seen how we can work with data updates. We started by talking about creating a PUT action with our NodeJS Express RESTful service so that we can update our data in the database. From there we learned how we can use the HTTP client and Angular service to call HTTP.PUT, get that customer up to the service and then perform that update operation. And then we saw how that service is then reusable again across our template driven form and our Reactive driven form. And really the code there was very, very similar between those two different form styles. So up to this point in the course, you've now seen how we can create GET type of actions and PUT and POST, but we haven't talked about deleting data. And that's what we're going to work with next.
Deleting Data Using a DELETE Action
In this module, we're going to look at how we can add DELETE functionality into the application. So we'll work on DELETE functionality, of course, on the server side and the RESTful service, and then we'll go back to the Angular application and add DELETE functionality there as well in our data service that we have, and in our different forms. So as a quick overview, we'll start off with a DELETE action added using Express, we're going to create a route that takes an ID, and then we'll map that to a function that's going to be called, which will then handle deleting that customer from our MongoDB database. We'll then take a look at how we can use our Angular service to issue a DELETE request using an Http client. From there, we'll then modify our customer form to support deletes, make that delete request, which then calls the RESTful service. And then we'll also explore our Reactive form, and see how we can get that going with the DELETE operation as well. So let's kick things off again by starting on the server side, and getting DELETE functionality into our RESTful service.
Creating a DELETE Action to DELETE a Customer
In addition to handling GET, PUT, and POST operations, our RESTful service can also handle the DELETE action. Now this again is an action that you wouldn't use with a standard web app, with a form tag and things like that, but when it comes to different clients calling up to the RESTful service, we can let them specify these different types of actions. And in this case, we're going to focus on how to work with DELETE. So going back into our API, customer's route, and the customers.controller, we've already added several GETs and the ability to insert an update. So now we need to add a little bit of code so that we can handle our DELETE operations. So we can do that the same type of way. First thing I'll do is, let's come down below the update that we already added, and let's add a deleteCustomer, and it's going to take our request and our responses again. Now this particular DELETE operation is going to have to have a route parameter, we have to have the ID, of course, of what to delete. So, we'll go ahead and do that by calling into our CustomersRepository, I'll look at that in just a moment. But let's come on in first, and we can say console.log, like we've been doing, and we'll say deleteCustomer, just so we can know when it's called. And then we're going to use the customersRepo that we've been using throughout the course, so let's go back to that. And if we come on down here a little ways, you'll notice that we have a deleteCustomer, and that simply uses our Customer model to call remove based on the ID that's passed in. So we need to grab in the controller, the ID, pass it into here, and then it will take care of handling the DELETE operation. Alright, so that's going to be pretty easy actually, most of the work's done for us. So we're going to say, customersRepo.deleteCustomer, and we need to grab the ID off of the route params, we've done that before, so that's going to look like this, .params.id, and in this case I really don't have anything that's going to be passed back except for potentially an error, so I'm going to make my arrow function for that. And inside of here, now we'll handle if there's an error. Now, so we'll do like normal, we'll say, if there's an error, we'll go ahead and do a little bit of logging, and let's go ahead and log out something like, deleteCustomer error: and I'm just going to grab what we have right up here, so we're going to do the same thing. Alright, and that'll take care of that piece. Now in this case, I'm just going to return a simple status code. We certainly could return the error and all that, but either the DELETE worked, or it didn't, and the server side would log any errors that occurred, of course. So in this case I'm just going to say, let's just return a status, and the status here would be false. Now if it works, in our else, then we're going to do a true, so let's go ahead and just get that out of the way. And we'll do true, and we'll be kind of good to go on that particular aspect. Alright, so now that we have that available, we need to test if this works or not. So first thing, let's do our log again, and we'll say that our deleteCustomer ok worked. Now we have the ability to, first off, call deleteCustomer, we'll log that out, we'll then pass the parameter, which we're going to have to make in a moment, for the ID, into this deleteCustomer. If it returns an error, we'll go ahead and log it, and it just returns a status of false, otherwise we'll say true. Now, if we wanted to return a custom error, remember we had an error property, you'll see it right up above here, and we can certainly add that. So we can say error:, you know, 'Delete failed'. Now that's not going to give too much info, that's why the false in this case is probably all we need. But if you want to return more, we certainly could. Okay, so that kind of takes care of our deleteCustomer. So now we need a way to call this when the route comes in, so we'll come back up to the top. And we'll, on our router, say, when there's a delete, and it has an ID on the parameter here, on the URL, let's call deleteCustomer, and then we'll bind it again to this, just to make sure that the proper context is calling it. Alright, so now we have our two ways to GET, we can get all customers or single. We have a way to insert, we have a way to update a particular record, and we have a way to delete a record. Now, as usual, we need a way to test this. So let's go ahead and open Postman, and we'll give that a shot. So I've already issued a GET request, you'll see, to bring back all of the customers, so I'm going to scroll down to the very bottom record here. And in this bottom record, we'll have some kind of fake sample data. So you'll notice we have this James Dan, which is a record we're going to delete. So I'm going to grab this, and as we know, this route now has an ID route parameter, so we're going to put that up there. Change this to a DELETE operation, and this should now hit into our DELETE route. So let's go ahead and try it out. Alright, so we got back "status": true, that's good. Let's go back to the GET. Let's actually on the GET though try to get by ID of the same ID I just deleted, and you'll notice we get back null, we don't get back anything. So looks like the DELETE operation did indeed work. So you can see that the code is very, very similar for all of these, whether it's a GET, POST, PUT, or DELETE. Similar concept, but now we have the ability to perform these different CRUD, Create, Read, Update, and Delete operations, and you can see that also in our controller, if you will, for our RESTful service, everything is staying pretty tight, pretty small. We have thin functions, we don't have a lot of code in there, and we're going to delegate off as we've been doing the entire course to our repository layer. So that'll get us started, creating the DELETE action that's going to be performed on the RESTful service side. Then we need to add that into the Angular side. So we'll move to that next.
Making a DELETE Request with an Angular Service
Our Angular service is progressing nicely. We have support for GET, PUT, and POST operations, but we don't have support for DELETE yet of course. So we're going to add that now, and you're going to see that the same exact methodology we've been following throughout the course is going to be employed, so this is pretty simple, you'll see. So in the data service, we've seen our GETs, we've see our POSTs, and we've seen our PUT, but we don't have our DELETE. So let's go ahead and add that quickly. So we're going to add a deleteCustomer, and we'll of course need to know the ID that we want to delete. We need to pass that up to our RESTful service, that's a MongoDB ID, so it's going to be a string in this case. And then we're just going to return an Observable here, and the Observable is just going to be a Boolean. We have that status that's going to come back, so we'll go ahead and return that. Alright, so we have our observable a Boolean, and we're kind of ready to go, but we don't have anything returned, so let's go ahead and do that. So we're going to do http.delete this time, and I can really just grab this particular section and tweak it to save a little typing. So we're going to pass in the ID, of course, so we'll grab that api/customers, add on the ID that's going to be passed up to us, then like usual, we'll go ahead and do our .map. So we'll get back our Response. And in this case, the Response is going to be very, very simple, we're just going to parse that JSON, and return the status code back. Then we'll add our .catch as well, we'll do our handleError. So very, very simple operation, especially compared to some of the others we did. But we pass in the ID, we pass that ID on the URL, the RESTful service, of course, will then pick that off using the params option on the server side, and we're using the Http client's delete operation, or delete function here to do it, and then the rest is really the same as what we've been doing before. I'm just going to grab the status this time and return that back to whoever it calls. So now we're all set up for a DELETE operation, you can see that's a pretty simple one to add into our Angular service. So we need to add the code into our components that can call this now.
Modifying the Customer Form to Support Deletes
Now that our Angular service supports a DELETE type of action, let's go ahead and modify our template-driven form to support DELETE. So back in the CustomerEditComponent we've worked with several times throughout the course, we already have the GETs, and we have the ability to do our submit for our UPDATE, or our INSERT, but we don't have a DELETE at this point. We have a little delete option down here, you'll see it towards the bottom. But we don't have anything in that, so we're going to fix that. So the first thing I'm going to do is, when we delete, I'm going to go ahead and say, if this is called, we're going to get this event object, and I'm going to take that event object and make sure that we don't bubble it up. So we can do that by saying event.preventDefault, and you'll see that, actually, right above as well. If you haven't encountered that before, it just ensures that no parents also get the event. It kind of cuts it off, so it's not going to move on up, and it'll make sure that nothing else fires. Now the next thing I'm going to do is call into the database service, so this'll be pretty simple, and very similar to what we did for the GETs and the INSERT and UPDATEs. We're going to call our deleteCustomer that we just added earlier, and I'm going to pass in the customer's underscore id, which is the MongoDB property. And then we're going to go ahead and .subscribe to the observer. So, very, very similar, we know we get back a Boolean. And we'll make our arrow function here. Alright, now, before we go too far, I want to also add my err. We'll say that's an err of any, or you can leave that off in this case, either way. And we'll just log that. Alright, so now what we need to do is really very similar to what we see up here. In fact, I'm going to grab this code, paste it on down, and we don't have a customer, we have a status. We're going to say, if the status is good, if it worked and we're able to successfully delete the customer from the server and the database, let's go ahead and navigate back to the homepage, we've seen this a couple times. Else, let's write out, 'Unable to delete', our 'customer', and then let me add my missing paren right there, fix that. So now we're good to go. We have a DELETE operation that can be called, but we're also going to need to add a little bit of code into the template, to make sure that we can confirm, do you really want to delete this record. So I'm going to show you what we're going to do there. So we're going to jump to the template for this component in just a moment, but let me show you something up here on the properties first before we move too far. So you'll notice this deleteMessageEnabled, and this is going to be something that's going to be set through the UI, through the template, and I just want to call that out right now because you're going to see it here in just a moment. Alright, so what I'm going to do now is, we have the DELETE operation in place, let's go to the template, and there's a little area here for some delete code. Now, I could pop up a modal dialog, and those certainly work fine, and I think that's what most of us are used to. I'm going to show you a little, maybe more simple technique that I actually like better, because with a modal dialog, the user has to move their cursor somewhere else, maybe far, far away, it could be up at the top of the screen, for instance, from where the actual delete button was. I'm going to show you how we can add in not only a delete button, but how we can also confirm, do you really want to delete this, without actually using a modal dialog, but it'll still get the job done. So I'm going to go ahead and grab a snippet that I have here rather than typing it all, and we'll talk through this. So I have just a div, and you'll notice in this div, I'm only going to show it if we have a customer._id, because if we're doing an insert, it would make no sense to show a delete option. And, deleteMessage is Enabled. Now I already showed you that, that goes back to this property right here, which is false right now. So when this first loads, if there's a customer ID, and deleteMessage is enabled, which it's not right now, then this'll be hidden, of course. Now all we're going to do here is have some buttons, we're going to have a button here, that it's going to have a Yes, and if they click that, that's going to call into our delete. We'll then pass the event object in. Otherwise, if they click No, we're going to set the deleteMessageEnabled back to false, you can see. Now, in order to set deleteMessageEnabled to true in the first place, the user needs to click on a delete button. So I'm going to add another little snippet in here, and this will be our delete button. So again, if we have a customer ID, it's not an insert, and it's not enabled, the delete message is not enabled, which would be the case when this first loads, then when they click, and we bind to the click event, we're going to set that to true, the deleteMessageEnabled. That of course is then going to show this, now it's going to be up to the user to click Yes or No. If they click Yes, then we kick off the process we just added earlier, and we call our DELETE, that'll call into the Node.js RESTful service, and we'll be off and running, and can delete, and then hopefully go back to our homepage in this app. Alright, so we have that all ready to go. We have our delete functionality in our template now, we have our delete code, we have our service, and then we have our RESTful service with the route that should handle integration with MongoDB. So let's go back into our application, let me refresh now. And let's just click on a customer, we'll go down and see if we have any test ones. You'll see that I have a bunch down here. So we'll go down to test. Looks like we have some data, so that's good. And you'll notice Delete shows up. Now, if, just to show you, if we hit Cancel, and Add New Customer, notice Delete does not show up. That would make sense, and that's all we're doing in the template I showed. It's either showing or hiding the Delete button based on if there's a customer ID. Now in this case, I do want to delete, so let's go on down and we'll go find the test data. Alright, so we can edit it, or we can hit Delete. Now, normally when you hit Delete, you'll see a lot of apps pop up a modal, and again, there's nothing wrong with that. But I kind of like not having to move my cursor very much at all. And so if I hit Delete in this case, you'll notice it hides everything else. It's right where my cursor was, and then the user can select Yes or No. Now if they hit No, that just resets the Delete option, back to false, and we're back to where we were. But if they hit Yes, then this of course should call our deleteCustomer, which calls into our service, which calls into the RESTful service and passes the ID. So let's hit Yes, and we got redirected. Now I have a bunch of tests in here though, so, you know, who knows? There is several, but we can actually clean up all of these very quickly now, and you'll see everything's working. Let's delete this last Test. Alright, and we're good to go. So this is a really nice way in my opinion to let the user delete a record, and confirm or deny that, but not make them really have to jump around the page much, and, you know, click on a modal dialog or something that maybe pops up up here, or over here, or wherever it is. But you know, that's all very subjective to how you like to do your UI. I happen to like to keep the user in the general area where they can either hit Yes or No, and you know, then it's up to them what they want to do. So there you go, there's an example now of a template-driven form that, now we have all the CRUD operations. We can insert a new record, we can obviously edit, and update, and then we can also delete that record as well, and so we're getting pretty good here on our template-driven form. Now, of course, we have our other option, which is, if you wanted to choose the reactive forms, we need to enhance that as well, because you would choose one of those in your app. So let's go ahead and look at that next.
Exploring the 'Reactive' Form
If you wanted to get the delete functionality available in the reactive version of the form, then we can literally take the same exact code that we just did for the template form and just move that over to the reactive form, and it would be ready to go. So let's take a look at that real quick. So back in our template-driven form, we added our delete. And we have this same delete function already in the reactive, so I'm going to go ahead and just copy this code, move over to our reactive, and you'll notice the same delete is here, and just paste that in. That's literally all we would have to do, and then, of course, that's going to call the RESTful service, so there's really no other chains that we have to do there. Now if we go into the template of the reactive form, it's also identical to what you saw earlier. We have this deleteMessageEnabled, if that's true and there's an ID, then that means they've clicked the Delete, and they can then click on Yes or No, that then calls our delete. And then we have the same exact delete button that'll only show if there's a customer ID and not deleteMessageEnabled. And then that will set that particular property to true, and start this process of approving or denying the delete request. So on the reactive side, the code itself for both the component code and the template code is really identical to what you saw with the template-driven form. And if you wanted to get this going again, you simply have to come into the routing, comment out the template-driven one, start this up. And now we can come on in and refresh, I'll come on down here to one at the bottom, like Test2, there's our Delete. And we can hit Delete, and there we go. Now the reactive form is also handling our DELETE operation. Again, another example of reuse of code in our application, and I want to re-emphasize, of course, you wouldn't have both these forms. I've said this a few times. This is just to show you both types of forms, as we're integrating Angular, into our RESTful service.
At this point in the course, you've now seen the complete CRUD operations integrated into the RESTful service and the Angular application. You've looked at the GET, the PUT, the POST, and now we've looked at how we can create a DELETE action. We've also seen how Angular's Http client also supports DELETE actions. And we've taken a look at a different way of allowing a DELETE to occur, rather than showing a modal, which is completely valid, of course. I showed a different technique where we could just show the Yes or the No for the deletion right there inline in the page, and minimize the amount of cursor movement that the user has to do. Certainly not the only option, but a different one, and it shows off some of the Angular features with data (mumbles), and easily showing and hiding sections of a page. So at this point, we have our CRUD operations wrapped up, but we still have some more to work on. So we're going to talk about some additional bonus features in the next module.
Data Paging, HTTP Headers, and CSRF
In this module, we're going to take a look at headers and the role they can play when it comes to working with data and also some security features to prevent different types of hacker attacks. So we're going to start off by talking about how we can add a paging header into our RESTful service so that the response that comes back can say the total number of customers available, even if only a handful of customers are retrieved by the client. That way we can start to build out some paging functionality, access the headers in our Angular service, and then build a component that can handle paging, and I'm going to show first off how we can add the paging support from a code standpoint, then we'll talk about how we can add a custom paging component to allow the user to select which page of records they'd like to access. From there we're going to talk about something that all RESTful services and really any service that allows users to change data should have in place. We're going to first talk about CSRF, Сross-Site Request Forgery, and explain what it is, and then I'm going to talk about how we add in some functionality to block these types of attacks, using a node.js module called csurf. From there we're going to talk about how we can access a csurf token in Angular and how Angular supports using CSRF tokens and working with them out of the box. I'll show you how to get all of that going and how it works. So let's go ahead and get started by switching to our RESTful service and see how we can add some paging functionality.
Adding a Paging Header to a RESTful Service Response
Accessing Headers and Data in an Angular Service
Now that the RESTful service is capable of paging customers and returning the header with the total number of customers available, we could switch our focus to our Angular service, call up to that RESTful endpoint, get those customers and make them available to any components in the app that need them. So going back into our Angular data service. We already have the ability to get customers and we've been using that throughout the course to display those customers on the homepage of the app. What we're going to do now though is switch to paging the customers. So we're going to come on in and add a new function here and we're going to call this function getCustomersPage. Now it's going to take the page that we're on to be the number zero, one, two, three type of thing, and the page size which is really our top, how many records do we want to grab. And then from here we're going to call into the RESTful api, so we need to make a URL. Now because I have a need to embed multiple variables into a string to compose the URL, I'm going to go ahead and use ES2015 string template functionality here. You're going to see that right now. So I'm going to say we want to make a get request, but instead of using the normal quotes, apostrophes, or quotes like this, I'm going to use the tick which is an ES2015 feature, and we're going to embed the URL this way for the baseURL that'll be our api/customers. Then we can go ahead and just keep typing here and embed the page using the template syntax again, and we can embed the pageSize, and get that going and available. So I didn't use that like down here because when it's a simple thing I think it actually makes it harder to read, but in this case it makes it nice because I don't have to put a lot of pluses and I don't have to get all that concatenation going and available. Alright, so we'll have the baseUrl, which is our api/customers you'll see up top. We're going to concatenate that in with page and then we'll grab the page variable, embed that, and the pageSize and embed that. Because this is going to work with cooperation and observables, we need to do our map, we'll get back our Response object. Now I'm also going to come in and do the standard caps that we've been doing the entire time, so we'll add our handleError. In the map we need to do a little bit of custom things. First off I'm going to grab the customers, we'll say let customers as response.json, that's what we know we get back, either null or the customers. But what I'm going to do is above that grab the total records. Now this means we need a way with Angular to get to that x inline count header that I've mentioned. So we're going to say const totalRecords and it's going to be equal to, and I'm going to convert this into a numeric value, the response headers, and then we can call it get to get into the headers. This is pretty easy to do once you know how it works. We can say x inlinecount, and on the server side it was actually a little more uppercase and spots but it really doesn't matter here. I could match that if I wanted or not. Now that's going to grab that header, convert it into a numeric value, and now we'll have the total records and we also have the customers. Now, with the customers, I need to tweak them a little bit, and that's why I'm doing the let. If you recall earlier in the course, we called calculateCustomersOrderTotal, and that iterates through all the customers and basically adds their order totals up, grabs all the orders and totals. We're going to come in and do the same thing there. So we'll grab these customers like this and just paste that down, and that will take care of that part. And now what I'm going to do is return not only the customers but also the total records to any component that calls into this service. So we're going to put our return and now I'm going to say results is the customers, but the total records is the total records that you see right here that we got from the header. So now we have a getCustomersPage in our data service and any component in the application can now call into this and use it. So that's how we can actually create the functionality to get the header, grab the customers, and then return those. So the next step would be to have a component, call into this, so that we can get these page records, and it, of course, is going to need to pass in the page and the page size for the number of records that we want to grab.
Adding Paging Support to a Component
Let's start to add some paging functionality into our component that's responsible for displaying the customers in the application. So the existing customers component, this is the one that is used for the homepage of the app, already has the ability to store customers and filtered customers, we talked about that earlier. But it also has two properties, total records and page size, and you'll see that we have some default values there. So what we need to do is our data service that we just looked at earlier is going to return the total records, we need to store that here. And then of course page size we're setting to 10, but that could certainly be dynamic, as well, if we wanted. But currently the application isn't using paging. Instead what it's doing is actually coming in and getting all the customers, and that's okay for now, but obviously we didn't go to the trouble of creating that on the server side to not use it, so we need to fix that. So what I'm going to do is come down below and we have a getCustomers and we can go ahead and leave it, but right above it I'm going to add a getCustomersPage, and this is going to take the page of customers we want to get, page one, page two, page three, whatever that may be. Now in here we're going to add the code to call into the data service and we're going to call that new getCustomersPage that we saw. I'm going to pass in the page, but the page, as you're going to see later with the pager component, is going to be one-based. I'm going to subtract one off of that, so we're going to say page minus one, and we're going to multiply that times the pageSize, and that's going to be the number of records to skip, and then we're also going to pass the pageSize for the number of records we want to grab. Alright, now that we have that we can come on in, subscribe to the observable. But what are we going to get back? Well, if we go back to our data service currently getCustomersPage actually doesn't really return anything useful there. It's kind of hard to know what we're going to get back. So what I've done is created an interface called IPagedResults, and IPagedResults is going to be responsible for allowing us to return page results of, and then we can pass ICustomer as an example into that. So let's come into our data service, I'm actually going to change this. We're going to say this returns an observable of IPagedResults, which has a type of ICustomer for the actual value. Now, this currently is an imported. So let's go ahead and come on up here, we'll add IPagedResults there. Okay, so now I'm being very specific that getCustomersPage returns an observable but the observable type is an IPagedResults and the actual results are of type ICustomer. Now by having this, I can return really any type of results. It could be customers, it could be orders, it could be whatever we want, but this provides some nice consistency across the app and it provides some nice intellisense or code help you're going to see as we, of course, type. So now that our data service is more explicit about what's being returned here, let's switch back to here, and now I can say that our response, and we're going to get back from the subscription, is going to be an IPagedREsults of ICustomer. And now that we have that, we can be a little more specific now. I'm going to say, actually I just realized I need to make this an array though because we are going to get back an array instead of one customer. Let's do an array there. Okay. So now that we have that, let's do our error function and we go ahead in here, deal with our error if we had one, and I'm also going to add a kind of do always function, a do. And I'm also going to log some information about the paging just so we know that it was called, so I'm just going to say getCustomersPage, retrieved customers, and then we could pass in the pageSize and all that if we wanted, as well, but we're going to go ahead and leave it as that. Okay, so now we have a way to call the getCustomersPage, we pass in the page that was passed in here minus one so we get back to zero-based, multiply that times the pageSize, that will give us the skip value, and then the pageSize which defaults to 10 in this example. We then subscribe to the observable which we know we're going to get back as IPagedResults of ICustomer array. And now when we get to the response, we can be very specific about the code help, I'll show you what I mean here. So I can say the customers and the filteredCustomers are both going to be set to response, and when I do dot here you'll notice I get some nice code help because of the interface. So we'll do results. Then I'm also going to need to update the total records for the paging we're going to be doing a little bit later so that we know how many pages to show of information, and we're going to grab the response and get the total records, and then we'll be done. So that'll update the properties for the customers and the filtered Customers. Now we were doing that a little bit earlier, if we come on down to this example with the getCustomers. But we now have that available. I'm going to go ahead, since I added the other functions, and take that parentheses out because we close it here, and now we're ready to go. So at this point though getCustomers is still being called, when the ngOnInit gets called. So if we come back up to here you'll notice we have getCustomers. Let's change that. Let's now call into, so we can try this out, our getCustomersPage, and what I'm going to do is pass in a page of one give me the first customers and then of course I'm going to grab 10 of those. So instead of showing all of the customers in the database, now when the component calls the data service it'll tell it, "I just want 10 records," and that then will kick in, load those customers, and will only display those 10. Now we won't have the ability to page through other customers at this point, but we're going to fix that a little bit later in this module. So let's see where we're at so far, let's first go back to the console, make sure we don't have any issues. Okay, the incremental compilation started. Looks like the typescript compiler worked okay there. Let's go back into the app now and refresh. Okay, now we should only have 10 customers here. Alright, you'll see that it ends much sooner, I'll let you count them. But that should be 10. And so we're working now, that's great. Everything else should still work the same, nothing's changed on these. But now we have that part of the functionality in place and the component is calling the data service which then integrates with the RESTful service. But we don't have a way to get to multiple customer. We don't have page two, page three, and so on and so forth, so we need to fix that. That's what we're going to do next.
Adding a Paging Component
The next thing we need to add into the component is the ability to actually click on a page and view those different customers. To do that, we're going to come back into our customers component, but what I want to do first is go into the template for it. Now, in the template we need a way to not only display the grid of our customers as you see here, but we also need a way to page through those customers. Now, in the application I already have some pagination functionality built-in, a custom component, I'll talk about that in a moment. But let's go ahead and first use this pagination component and put it into play. So what I'm going to do is add this pagination. This is the name of the component selector, and that has several properties and an event we can hook into. First off, any time you do pagination you need the totalItems, so we're going to bind to an input property that we look at called totalItems, and bind that to our totalRecords that we saw back here. So we know that totalRecords gets updated, we're going to pass that value to here. So we'll say totalRecords. Now I'm also going to come in and we're going to bind to another one called pageSize, this is also an input property and you've seen that we also have a pageSize in our component. And then finally we're going to use our clicks on one of the pages, I need that as well, so we're going to add a pageChanged, and you'll notice that this is a custom event or an output property that will be defined in this pagination. So we're going to call a function that we're going to be adding here in a moment, we're going to pass the event object. Then we go ahead and add our closing tag. Okay, so that'll take care of adding our custom pagination control, binding the records and the page size into it so it knows how many pages to render in the UI, and then, when a user clicks on one of those we need to call pageChanged, that's going to call back into our custom function that we're going to have that will take care of that. So let's now go in and add this pageChanged back in our component. So let's go ahead and write above the getCustomersPage, and we'll just say a pageChanged, so this is going to be pretty simple. We're going to get the page and this page is going to be one-based, that's why earlier I showed you we're going to take whatever is passed into here and subtract off one. So we go ahead and now pass that along to our getCustomersPage. Whatever page is passed in, it then subtracts off one, multiplies the pageSize, and calls our service. Okay, so now we're good to go. We now have a custom pagination control in play. We've bound to its input properties, into an output property, we can now be notified when the page changes, and we're off and running. So let's try it out first and then we'll talk about the pageChanged. So let's refresh. It looks like we got our records back. But now at that bottom you'll notice I have a little custom pagination control. You'll notice that it starts with Michelle Avery, now we'll go to page two, looks like we now have Elaine Jones, page three, and then of course I can move one by one if I'd like, and if we wanted we could also put this up top, move it up maybe instead of the bottom, but go ahead and leave it here for now. So that's an example of the paging. Now, to make this paging component was actually pretty straightforward. It's really just a template and some code. So the template itself is pretty simple. I'm using some nav features from HTML5 and I'm just embedding a ul, and the ul is then going to have lis, and these lis will ultimately represent the pages. Here's where I do an ngFor, and I loop through all the pages which I'll show you in a moment. When one of the lis is clicked, we're going to call an internal function in the pagination component here that's going to pass the page and the event object, and then I also handle setting some CSS classes like active to change it as the user clicks. Now, the other lis would be move back a page or move forward a page, and you'll notice that we either disable those or enable them based on if they're at the end or the beginning, just depends on where the user's clicking here. Alright, so if we go back into the code for this, the important part with this pagination component is that it has two inputs and one output. So you'll notice I have a get and a set, so we're using some ES2015 features here, and the input, any time the pageSize is going to be retrieved, we're going to return it from some private members we have. We have a pagerPageSize and a pagerTotalItems up here. And then any time one of these properties is set, we're going to call update. Now, that way if the pageSize changes or something else changes on the external world, we can be notified we can call update, and the update's actually going to take care of rendering our pages. So we'll go in, we'll add our pages, the pages then get rendered using this li, we loop through those pages, and that's actually what renders one, two, three, those types of things in the pagination component. Now, just as a heads up, there's plenty of pagination components out there. This is a custom one just so you can have it and you can see the process. I won't go through all the code, but do want to talk about a few other things. So we've talked about the inputs to set the pageSize and the totalItems. Now that of course is what allows us, if we go back to our customers component, to bind to totalItems and pageSize. Those are two input properties. Now, if we go back to the pagination component here, we now also need a way to disable and we need to handle the click, so that would be an output property. So you'll see we have a pageChanged, that of course is what we're hooking to in our customers component, and that's an EventEmitter. Now, the EventEmitter is just passing a number and that number gets passed up back into our customers component, which then calls and uses the data service. Alright, so there's our two inputs and our output so we can get data into this component and then also raise events to get data out of the component. Now, really the rest of this is just determining what to show on the previous and next as we move forward. If we go back into the template, you'll notice that as they click we call changePage and there is also a previousNext function you'll see for both the beginning and the end options. So if we go back into the component code, you'll see previousNext is going to determine which direction we're going and basically track what page we're on. Because if we're at the very beginning then I don't want the ability, if we go back to the app here, if we're at the very beginning this should be disabled. There's nowhere to go to. You don't want to go to zero or minus one, of course, so you want to disable it. And of course, if we're at the very end we want to disable this one. And so that's really what's happening with this previousNext. It's determining what direction we are and where we are, and then as changePage is clicked we'll either enable or disable the previous and the next based upon where the user is in the pager. Now there's a lot more we could talk about with this, but I'll leave it in here. There's also UI bootstrap and some other options out there that have pagination controls I mentioned, so there's certainly some third party Angular modules you could just include and not even have to write any of this. But I wanted to include a custom one so you could at least explore and see how this works. The beauty of it though is now all we have to really know about from the external world is this right here. We have the component selector and then we have our two inputs and our output and we can certainly add more and make it more robust. But that's the actual functionality that's driving our paging and now we're able to page through these different records you can see.
Adding CSRF Functionality with csurf
Using a csurf Token in an Angular Service
Now that the csurf token's been added into the node.js service and has the ability now to set that token automatically, we need Angular to be able to set a header that includes the token value it's going to get. Now again, it's going to get that from a cookie that the server side's going to set. Angular will then set the header and then the server, once the request comes in, will compare that header to the token in the cookie. If they don't match, it's going to throw an error. If they do match, it allows it to proceed. So as mentioned, I like to enable the csurf module on every project I have because it'll prevent these types of hacks. So now that we've seen that, let's jump into what Angular does with that token. So if we go back into the server side code, I mentioned that Angular looks for a very specific cookie name called XSRF-TOKEN. If it finds it, the value for that cookie is then going to be set as the header and then everything just works. Now this happens just out of the box. If you go read the docs, you'll see that XSRF support is just built-in natively to the HTTP client. However, you have to make sure that the server you're calling sets a specific cookie. Now to prove that, I'm going to go ahead and just name this XSRF-TOKEN2, go ahead and save. I should have restarted the server, looks like it just finished, and now I'm going to go back to the application and refresh it. Now, this should have just said a cookie, I have cleared all cookies prior to this, and let's say we have this Michelle2 again, and I would like to come in and change it. Now before I hit Update, I'm going to go to Inspect and go to the Network tab. Now let's come on down and do an update, and up, looks like we got an error. Now if I come down to the console, it says that we have some forbidden invalid CSRF token. Now, what just happened there is Angular didn't see a cookie that it knew about. If we go into here and scroll down, we're going to see right here that we have this XSRF-TOKEN2. Well, Angular doesn't know about that and so it never set the header, therefore the server, when it checks as the request comes in, says, "Well, I got the cookie value back, "but I didn't get the proper matching header value." And that's why we get this error. Now, to make this work of course I need to come in and probably clear out some cookies, so let me do that just really quickly here. And now if I go back and change it to XSRF-TOKEN without the two, restart the server, give that a moment, and then let's refresh, let's actually just go back to the homepage. Let's go ahead and inspect. We'll go to Network, and now let's try to update. Now Angular knows about the XSRF-TOKEN cookie and so when we hit Update, it's going to set the header, that now just went back, but this XSRF-TOKEN, this was actually set and this particular value is now going to match up with the value on the server side. In fact, you'll see there's actually the cookie right there and there's the token that was set. And so now we're good to go. It knows about the cookie coming in, Angular set the header and it all works. Now, you might ask the question, "Well, that's great, Dan! "What if I don't have control over the server? "What if I'm not the one actually setting this?" Maybe it was called, XSRF-TOKEN2 or something like that as an example. Well, the good news is you can override it in Angular. I'll show you a quick example. If I go into my core module, this is the one that loads up all our singletons we talked about earlier, you'll see I'm actually importing something called an XSRFStrategy, and then I have this CookieStrategy. Now, what I'm going to do is override the default provider for the XSRFStrategy to use a custom CookieXSRFStrategy. Now, if it was called XSRF-TOKEN2, then this first value represents the cookie that Angular would look for. The second value is the default value of the header that it's going to set, and that's a header that csurf and other modules out there typically know about, in this case csurf definitely knows about it. So, I could uncomment this and then I could make the token two value for the cookie actually work. It would read that cookie and then set this header, and then the server side would validate the cookie against the header. So this is a way that you can actually override the default. Now, I don't need this in the app because I do have control over the server, and so I just set it to what Angular looks for anyway. Now, of course, it goes without saying that if we're going to allow post, put, and delete request that you're going to want to provide authentication. Now, because authentication is very specific to apps and companies in general, I'm not going to add anything like that here. But even if authentication is enabled, just keep in mind, as I showed earlier, a bad site could potentially do a CSRF attack, and that's exactly why we've now talked about the importance of the header, the cookie that goes down, and why you want to prevent CSRF attacks from the server side.
We've covered a lot of information in this module. We started off by talking about how headers can be used to transmit and send information from a RESTful service down to a client and vice versa, and we looked at how, for instance, a record can or can't go from our service down to Angular and how Angular could send tokens for CSRF attack prevention from the client up to the RESTful service. We also looked at a pager component and how it can be used to provide pagination services in a reusable way across an application, and then went into CSFR attacks, Cross-Site Request Forgery, and talked about what they are and how we can shut those down by adding some functionality into the server, in our case we added the csurf module. Finally, we took a look at how Angular has built-in support in the HTTP client to take advantage of csurf prevention, and it can read that cookie that's set by the server, grab the token, and then set the header that goes back up to the server so it can validate the token.
This course has been all about integrating an Angular application with a Node.js Express RESTful service that talks with MongoDB. The overall goal of the course was to show how data could be moved from a database to an Angular client. As the user changes the data in the application, we examined how to insert, update, and delete that data, and how those different types of operations could actually be performed. The course started out by talking about key technologies used in the sample application. This included an overview of RxJS and the key functionality it provides, as well as Observables, and how they can be used to work with asynchronous calls made by an Angular application as it retrieves data and how we can subscribe to those Observables. From there, we jumped into the sample app and looked at how it can be used to page and then modify data, we looked at updates and deletes and inserts, and really the full CRUD operations set of actions that can be performed. On the server side, we looked at how the application can be used to create RESTful endpoints. We talked through that process of creating get, post, put, and delete routes that a client could call into, and we added code to handle calling MongoDB to perform those CRUD operations and return the appropriate response to the client. We also looked at how Angular services and the HttpClient can be used to make async calls to the RESTful service and retrieve data. The Observables returned from the Angular service were the subscribed to by Angular components to display and capture data. The role that HTTP headers can play in an app are also discussed. Headers were used to page data and revamp CSRF attacks, and along the way, you saw how components could subscribe to Observables and then render data in the user interface using child components. This included discussing input properties such as the customers properties shown here, and we also looked at output properties. You also saw different form techniques that can be used in Angular, including the template-driven approach and a reactive approach and the differences between them. Well, that's a wrap on the course. I sincerely hope you enjoyed going through it as much as I enjoyed creating it for you, and that you were able to learn a lot along the way. If you'd like additional information on these technologies and others, feel free to visit codewithdan.com, and I also tweet a lot about these technologies, so feel free to follow me @DanWahlin. Thanks for taking the time to watch, and please do check out my other courses on Pluralsight.com.
Released30 Jan 2017