What do you want to learn?
Skip to main content
Angular 2 End-to-end
by Reggie Dawson
Angular 2 is one of the most popular frameworks to build Web apps. This course will teach you to build a complete Angular 2 app with authentication, a blog, and a store as features.
Start CourseBookmarkAdd to Channel
Table of contents
Hello everyone. I'm Reggie Dawson. Welcome to the Angular 2 End-to-End course. The original Angular was a very popular development framework built by Google. The followup Angular 2 is notable because it is a complete rewrite of the original. As the result, it is completely different how we build apps with this version. Now there are a lot of great courses on Pluralsight that give you the basics of getting started with Angular 2. This course will instead focus on building a complete app from start to finish. We will develop a minimum viable product for the fictional Gigabyte Gaming Company. Now with this app we will offer the functionality that you find on other sites on the web. First we will need to secure our project from unauthorized access. To do this we will need to develop some sort of authentication system. We will also build a blog to allow visitors of the site to learn about the Gigabyte Gaming Company. Then finally we will build a shopping cart system to allow visitors to purchase the products that we offer. Typically to provide these features we would need to use some sort of database as the back end. This also incurs the overhead of having to configure the server to host this database. As a better alternative we could use a cloud-based service such as Amazon Web Services or Microsoft Azure. This will allow us to designate the resources we need to host our project and support the scale that it could grow to. Now if you're using these services or your own machine, you would still have to configure the database that you will use. We will instead use Firebase as the back end of our app. Firebase is a cloud-based database owned by Google. Instead of us having to write code to manipulate a database, we can use the Firebase API. Firebase comes with an authentication system that allows us to create users and authenticate them. Instead of us having to handle the details such as verifying the format of the email, Firebase handles this for us. As a result, much of the work we need to do to support the authentication system is already done for us. The Firebase database allows us to store data in a JSON style format. We will use this database to hold our blog posts as well as the products that we will offer for sale. The final part of Firebase that we will use is Firebase Storage. JSON data is generally just text based, but if we need to save files such as images or audio, we can use Firebase Storage to hold these. In our case, the blog posts and products will have images associated with them so we will use storage to hold them. This course will be useful in helping you understand how the individual pieces of Angular 2 fit together to build a complete app. You will learn how to use Webpack to manage the individual components that make up the app. Webpack will take these components and bundle them into one file that is easy to deploy. Following that we will take a look at the architecture of an Angular 2 app and the files that we need to bootstrap it. Angular 2 follows specific patterns and once you become familiar with these files, building any type of Angular 2 app will be possible. The first part of the app we will add is the authentication system. We will first use Angular 2 routing to protect sensitive areas of the app from unauthorized users. Since the blog and products will be managed by users, we will add forms to allow them to handle this. Of course, not just anyone should be able to access this area and we can use a built-in feature of Angular 2 to protect it. Then we will use some of the form handling features of Angular 2 to make sure a user enters the correct information in the form to add a user. Again, the built-in features of Angular 2 will make this easy. We will also make use of the Firebase API to create new user accounts. As I mentioned, Firebase will handle verifying whether the email is already taken or the address is malformed. After we have the account created we will then be able to use the Firebase API to authenticate the account. This will in turn allow access to the admin areas of the app. The blog will be the first feature that we add that is protected by authentication. First we will add an image for the blog post in Firebase storage. We will in turn associate this image with the blog post which is stored in the Firebase database. Then we will use the Firebase API to create our blog post in the database. Firebase also offers the advantage of generating a unique ID for each item we save to the database. Now once we have the ability to create posts, we need a way to edit them. Although we can edit the posts from the Firebase console, it is not reasonable to give blog authors access to Firebase. Instead we will create a component to allow them to edit. Now in addition to being able to edit the blog post, we also need a way to delete them. This requires an added step since we must also delete the associated image from storage as well. The last part of the app we will add is the store that allows us to add products that users can purchase. The way the store will be configured will be identical to the blog for the most part. This will make setting up the store simple since the concepts will be familiar. The only difference between the store and the blog is that we will add a shopping cart that allows us to send a total of the user's purchase to whatever payment processor that we are going to use. Now once this course is finished, you should have a good idea how to build a complete Angular 2 app from start to finish. This knowledge will server you going forward as you can apply what you learned to a variety of Angular 2 apps.
Obtaining the Project Files
Now with the course I've included the project files for every step of the way of our project. This project is going to have a lot of different parts that will work together so it will be easy to make a small error that will be difficult to track down. Since you have the source files, you will be able to compare it to your own work to figure out your problems. Go ahead and download the exercise files before you start the next module. I have included a starter project that we will build out to make our app. This project includes everything we need to get started. Now we are going to go over a lot of different parts of Angular 2, but if you ever need more information on something, make sure to check out the Angular 2 documentation. This documentation is a great source of information and can help you understand Angular 2 much better. I often refer to it myself as Angular 2 is still changing and being updated constantly. Now that you know what files you will need, let's get started on our project.
Setting up the Project
Hello everyone. I am Reggie Dawson. Welcome back to Pluralsight's Angular 2 End-to-End course. When setting up an Angular 2 app, getting off on the right foot is essential. There are many moving parts of the project that you have to consider. This module will get us started in building our app for the GigaByte Gaming Company. First we will set up our project and learn how Webpack and TypeScript work with our app. Then we will move on and build out our initial components. These initial components will be very basic, but they should help you get comfortable with the syntax of Angular 2. Then we will set up routing that will control the navigation of our app. After this module is finished, you will only have a very basic app, but a great start to the finished product that we will have at the end of the course.
Setting up the Project
In this lesson we will learn what we need to do to set up our Angular 2 project. The Angular team has recently released the Angular command line interface that will streamline this whole process. Additionally, this will also generate components, routes, and other features that we use in Angular 2. Even though this is a useful tool, in this course we will manually set up our project. The reason I chose this workflow is that it will help you understand the architecture of an Angular 2 app if you are new to the framework. I have created a starter project that scaffolds out the most important parts of our app. Download the project and open it up so we can take a look at the files. First we have the karma configuration file in the root of the project. This is just importing the real karma configuration file from the config folder. Karma is a testing library that we can use with our Angular 2 project. Then we have the package.json file which is how we will install the libraries we need for our project. Package.json is a configuration file used by NPM, which is the Node.js Package Manager. At the top of this file we have our scripts, which we will use while we develop our project. The start script will launch a web server, which will allow us to preview our project. Test will launch the karma test framework and build will compile our project using Webpack. Then the dependencies section of this file has the packages that are required for this project. The angular, core-js, rxjs, and zone.js files are required to make Angular 2 work. Foundation-sites is the popular foundation library which is the framework we will use to style our app. jQuery is necessary since foundation makes use of it. Then finally we have our devDependencies that are only needed as we build our app, but not in deployment. Anything that has loader at the end is therefore Webpack, which we will go over in a later video. The same holds true for anything in the devDependencies that says plugin, as these are Webpack plugins. Jasmine and anything prefixed with karma are for the Karma library. TypeScript provides the TypeScript compiler and the webpack entries are for Webpack, of course. And node sass allows us to compile sass files. Now if you've previously used Angular 2, you had to import types into your project manually. Types are definition files that allow TypeScript to understand the syntax of third party libraries. With the release of TypeScript version 2, these types are now installed through NPM which eases the process tremendously. Installing types this way may even become redundant as most libraries that install through NPM come with their own definition files. Here we are installing definitions that we need for Angular 2 to work properly. The tsconfig.json file handles the configuration of the TypeScript compiler. The most important part of this file as it relates to Angular 2 is the emitDecoratorMetadata and the experimentalDecorators properties. That is because Angular 2 makes use of these features and without decorators it will not work. And then finally the webpack.config.js file points to a webpack.dev.js file in the config folder as well. I've based this project off the Webpack starter from the Angular team and that workflow allows for a few different configurations depending on what version of the project we're going to generate. As we build our project, we will be using the dev configuration. When we are testing our app or building for production, a different configuration will be used. Inside of the config folder is where we find the files that will be used in the configuration of our project. Helpers.js helps to generate the correct paths for Webpack. Karma-test-shim.js imports all of the libraries that the Karma library needs. Karma.conf.js handles the actual configuration of Karma. Now webpack.common.js has configurations that are common to all Webpack configurations. Webpack.dev.js is the configuration we use as we develop our project. Webpack.prod.js is for generating a production bill and webpack.test.js is for testing. The public folder contains a sass folder which has the settings and styles.scss files. SCSS is a pre-processor that compiles the CSS. The reason we are using SCSS is that it will give us greater flexibility as we style our project. Then finally we have our source folder. This is the folder where we will develop the app in. Inside this folder we have an index.html file that will be the starting point for our app. If you look at this file you may notice that we don't have any script or style sheet references on the page. That is because we are going to dynamically add these as we build or preview our app. Main.ts is an Angular 2 file that we will go over in a future video. Polyfills.ts and vendor.ts are imports for the libraries we need in our app. Now you may notice that these files have a ts extension. This indicates that these are TypeScript files. Don't worry if you don't know TypeScript. I will show you enough to get started with Angular 2. And then finally the app folder is where we create all of the components, modules, and services that make up our app. Don't worry about the files and folders we have here already, as we will go over them in a later lesson. One thing to note is that I like to keep each component in its own folder. As the app grows, you can go further and encapsulate features areas of your app in their own folders as well. Each component will have three or four of its own separate files. In the case of a large app, this can get very messy if you put everything in one folder. This won't be an issue when we go to production, but this will help remain organized as we build our app. Now in order to install the packages for the app, make sure you have Node.js installed. After that, navigate to your project folder and run npm install. Now this command has installed the contents of our package.json file into our project. Once that runs successfully, your project is configured and ready to go.
Understanding the Root Module
Now that we understand how our project has been set up, we can start looking at how Angular 2 works. We will start by looking at the app.module.ts file in the app folder. This file is considered the root module of our app. An Angular app can have multiple modules, but it has to have at least one, the root module. As I mentioned before, our Angular components will start with our imports. First we are importing NgModule from angular/core. This is providing the NgModule decorator to use, which is what we will need to configure our root module. Browser modules require for any browser-based apps and gives us important directives such as ng if and ng for. Directives allow us to attach behavior to html elements. We will talk more about directives as we go through the course. Then the final import statement is grabbing AppComponent, which is the component that is displayed when our app starts. After that we have our NgModule decorator. We talked about how decorators allow us to provide annotation and metadata to our classes, well here we actually get to see this in action. The imports are for modules that are needed for templates declared in this module. In this example we have browser module, which is needed by any templates that the components display. Declarations are for the view classes that belong to this module. A view class in Angular is a component, directive, or pipe. Here we have the AppComponent, which is the only component we have in our app so far. We imported this component, but we still have to add it as a declaration in order to make it available to the module. As we add components to our app, we will need to import them and add them to our app here. Then we have our bootstrap. This represents the component that will launch when the app starts. Again this is the AppComponent. The bootstrap is actually initiated from another file, main.ts. The process of starting our app works like this. First the main.ts file runs and executes the bootstrap method against the app.module.ts file. The app.module.ts file launches the app.component.ts file. We will look at the main.ts and app.component.ts files in later lessons. We can also add exports to our decorator. This allows us to export declarations for use in other components, although this is not necessary in the root module. Providers are another option we can add to our NgModule decorator. This allows us to import services into our module. Then after our decorator we have our app- module class. Decorators immediately precede the class they apply to and by adding the class with the export statement, we make it available to be imported into other components in our app. That's all there is to the root module. Now that you understand how it is configured, in the next lesson we will look at the app.component.ts file.
Understanding the App Component
In the last video we looked at the root module and how it was configured. In this video we will look at the app component that the root module launches. This app component is actually made up of four different files. I have added these files to their own folder named start inside of the app folder. App.component.ts is the actual Angular 2 component file. This will have the component decorator and the class that is imported into the root module. App.component.css contains any component-level css files that apply to just this component. App.component.html is the html template that will be displayed by this component. And app.component.spec.ts is the file for Karma. This file allows us to run tests against our component, but it is in no way required. The app.component.ts file starts off with the import. This time we are grabbing the component decorator from angular/core. This decorator is what we will use to configure all of the components in our app. Then inside of our component decorator we have our metadata properties that we will pass on to our class. Remember when I mentioned directives in the last video? Well, a component is actually one of the three kinds of directives that you can have in an Angular app. The first thing we have inside of our component is our selector. This corresponds to the my-app element that is in our index.html file. This selector is where the component will be displayed when the app starts. Depending on how we configure our app, not all components will need to have a selector. Then we have our templateUrl, which is the file that will be displayed by this component. We could also specify an inline template by using the template property. These inline templates can also make use of template strings that I mentioned in the TypeScript video. Next we have styleUrls which specifies the component level styles that it uses. We can also specify inline styles by using the style property. This property is also optional since you can just use the styles that apply to the entire app. Following the component decorator we again have the class that we export, the app.component class that is referenced in the bootstrap of the root module. Now if we look at the app.component.html file, it just has a simple h1 tag. Now we will change this as soon as we begin to build out our app as this is just some dummy content to verify the app was working properly when I configured it. Now the app.component.css file is empty right now, but it can be used to add styles that only apply to the app component. The app.component.spec.ts file is used to run karma test. We will take a look at this file later on in the course. You should now understand how a component is configured. As we build our app, these concepts should become familiar to you.
Understanding the Bootstrap
In this lesson we will learn how the main.ts file bootstraps our app. In the Webpack configuration, this file is the entry point to the app bundle. Any files that are imported into main.ts will be added to the bundle. Any files imported to those files will be added and so on. This file also executes the actual bootstrap method that launches the app. The main.ts file of course begins with the import statements. The first import is for platform dynamic browser, which launches the just-in-time compiler for our app. This converts our code to display in the browser at runtime. We also have the option of using the ahead-of-time compiler; this produces a smaller project that may run better on mobile devices. EnableProdMode allows us to enable production mode on our app as it runs in development mode by default. The final import is for our app module. Now the next part of this file is to see what mode our app is running in. When we finally build our app, we will look at the Webpack production configuration. In this configuration we set the process variable to production. If we are running in production, the app will be set to run in production mode through the enableProdMode method. Now the last line of this file is where the magic happens. First we call platformBrowserDynamic and then we chain in the bootstrapModule method. In this method we supply the app module as the argument. This serves the bootstrap to app with our root module. Now that we have looked at the Angular specific files and configurations, you should have a better understanding of how the Angular 2 app works and fits together. Now this app is a very simple example right now, but as we build out the app, we will integrate more features of Angular 2 that we have left out so far. This starts in the next video as we will build our first component from scratch to add to our app.
Configuring the Navbar Component
Now that we have everything configured we can start building our app. The first component we will build for our app will actually be a component that is hosted by other components. This will be a navigation component that will be used by multiple components in our app. This is not necessary, but the alternative is adding the code for the foundation top bar in each component. The first thing we need to do is add the navbar.component.ts file to the shared folder. The shared folder is where we keep all components and modules that are used by multiple components. The first thing we will do of course is import our component decorator. Then after that we will add our component decorator. The first thing we will add to this decorator is our selector. This of course is where this component will be placed once we add the selector to our templates. Now we'll add this components template inline since this file exists in the shared folder and I don't want to clutter it up with too many files. Also since the code will be relatively simple, it is no problem to add the template inline. Fortunately we can use template strings, which allow us to create a multi-line string. Instead of template urls, we use template to specify an inline template. Here we are using a standard foundation top bar. First we create a div with the top-bar class. Then after that we have our GigaByte Gaming Company branding that uses the top-bar title class. Then we have our menu which presently has one placeholder menu item. This is just another standard foundation component that we are using which is why we have added the menu as an unordered list. As we build our app we will add links to our other components here. After that we have our styleUrls that points to an external style sheet. The styles.css style sheet supplies css rules that apply to our entire app. When we add styles on our component, they are considered component level style. That means the styles will be loaded when the component is, but they will only affect the component they are loaded with. After that, the last thing we have to do is export our class so that it is available to the other components in our app. This finishes up our component, but I am not happy with the default styles of foundation. In this project we will use foundation as a starting point. This saves time as we do not have to create our html elements and css styles from scratch, but we will customize these styles to suit our project. Create a file called navbar.component.css in the shared folder. The first thing I will do in this file is to import a Google font. I don't like the default font used in the project so I'm going to change that in a moment. I don't necessarily want the same font for the brand in the navbar so here I've selected a different font for that purpose. A simple css import does the trick. Then I add the override for the top-bar title and the nav-menu classes. I apply the font I just imported, set the font size to 22 pixels, and change the color of the text in the top-bar title. The project default is black text so the brand will be hidden. I could change the setting for the whole project, but I just need to configure the navbar. I use the nav-menu override to change the color of the length from the default blue to white. Now that I have shown you how to use the component level css files, next we will take a look at the styles.scss file that controls the look of our whole app. In this file we are importing the foundation settings and then foundation itself. We will take a look at the settings file in a moment, but one thing to note about the foundation import is that we are using the flex box version. Foundation comes with two grid systems and here we are choosing the flex box version. This gives us a bit more flexibility as we use the grid system to build our app. Now above the existing imports we will add an import for the font that we want to add. This is similar to the import that we used in our component.css file and just uses a different font. Then we add a variable for the font that we just imported. The cool thing about SCSS is that it lets you sue programming concepts such as functions and variables in your css file. As a result I could create a variable for our font as I've done here. Now understand that the order of these imports matters. I'm going to use this font in the settings file so I have to add the variable before the settings import statement. When this project is run, Webpack will add all of these imported files into one big file and compile it to CSS. If we added the variable for the font after the import and tried to use it in the settings, we would get an error. Next, let's go over to our settings.scss file. You may notice this as an underscore before its file name. This tells SCSS to add this to the file that imports it as opposed to generating its own settings.css file. Now there is a lot of information here, but fortunately, there is a table of contents to help you find what you are looking for. First we need to scroll down the page a bit and find the body-font-family variable. I could just change this, but to safe, I recommend that you copy and paste the variable you want to alter and then comment one of the lines out. This way in the future you will know what you have changed from the default as well as being able to revert back if there is a problem. Now once we have done that, it's as simple as adding the font variable. This is now the default font of the project. After that, scroll all the way to the bottom and find the top-bar background variable. After that use the foundation color variable for black. At the top of this file, foundation has set up some default colors for us and this is one of them. This serves to change the top-bar default background color to black. The last thing we need to do is import this component into our app.module.cs file. This will serve to make this component available to any of the other components in our app. First we add our import for the nav component. Then we add the nav component to the declarations. Now since this is the root component, adding the nav component to the declarations will make it available to all components. After that your component is finished. Make sure you save all of your files. You should now understand how to create a component as well as when and how to apply app-wide or component- level styles to your project.
Configuring the Home Component
Now that we have our navbar component, we can start working on the home component that will launch when our app starts. App component is currently the component that will be displayed when the app launches. Our app is going to use routing for navigation so eventually our app component will host all of the other components. Before we set up routing, we need a couple of components to route between. In the app folder, create a folder named home. Inside this folder we will create home.component.cs, home.component.html, and home.component.css. Instead of the inline template that we used in our nav component, here our template will be the home.component.html file. The home.component.css file is of course our component-level style sheet. The home.component.cs file will be very simple. First of course we will import the component decorator. Then after that of course we add our component decorator. Now all we have in the decorator metadata is template urls and style urls. These of course point to the external template and style sheet respectively. Now you may notice that this component doesn't have a selector. Since we are going to use routing, we don't need it. You will understand this better once we set up routing in a later video. Then of course the last thing we do is add the home component class. Then in our template, the first thing we will add is our top-bar. Here we are using the selector from our nav component the same way we would use a standard html element. Then we add a div with the row expanded class. Expanded serves to make the content extend the full width of the page. Inside that row we add an image that we will save to our shared folder. Now as we build up our app we are going to add content that we pull from a remote data source to our home component. Before we get to that we have some other functionality to add so for now this file is finished. Next add a folder named images to the shared folder. After that we can copy the hero image that we will use into this folder. After that, the last thing we need to do is add some styling for our hero image in the home.component.css file. Here we set the width of the image to 100% so that it spans the full width of the display. Now our home component is finished for now, but the last thing you have to do is import this component into the app module. First of course we import the home component. After that we add the component to the declarations. Now even though we have added the component, we still cannot access it at this point. We will configure that when we get to routing, but in the next lesson we will configure an error page for our app.
Configuring the Error Component
I am sure you have seen the very common 404 error as you have navigated the internet. This error is displayed when you attempt to navigate to a page that cannot be found. In our application we will need a page that will be displayed when someone attempts to navigate to a non-existent route. In keeping with the theme of our project, we will create another folder called error. This time we will add error.component.ts, error.component.html, and error.component.css. First we will start with error.component.ts. This file will be almost identical to the home.component.ts file. The only difference of course is the file is used as a templateUrls and the styleUrls. Again we start by importing our component from Angular Core and then we have our error.component.html as our templateUrl and our error.component.css as our styleUrls. Now we really haven't done much with our components yet, but we are just getting started. Now since this component will also be loaded by routing, we won't add a selector in this component either. Then in the error.component.html file we will add our top bar again. After that we will add our foundation row. Now this time I am not adding the expanded class so that content will be centered instead of full width. The float-center class centers the error image. After that we have the text that lets the user know they have navigated to a page that doesn't exist. Of course the image that we display is saved in the shared image folder. Then in the error.component.css file we will set the color of the text to red. After that, all we have to do is import this component into the app.module.ts file. Of course the first thing we do is import the component, then after that we add it to our declarations. After that our error component is finished. Again, this component is not yet accessible in our app. This will change in the next video when we configure routing.
Configuring the Routing Module
In this lesson we will finally make the components we just configured accessible in our app. We will do this by setting up routing. Routing allows us to translate our browser url to a specific component. Configuring the router, we have a choice between using the app module and setting up a separate routing module. I prefer to use a separate file, but this is a personal preference; either way is perfectly acceptable, but I find that using a separate file keeps my app module cleaner. Now before we get started, I want to take a quick look at the index.html file. At the top of this file we have the base href tag. This tag is necessary so that the router knows how to compose navigation urls. After that, create a file called app.routing.ts in the shared folder. Now even though this is a routing module, nothing has changed and we will start off with our import statements. This time we are importing NgModule instead of component. Remember we can have multiple modules besides the root module. Then we import the router module, which is what we use to configure our routes. Then we import the components that we just created. Notice we don't import the app component or the navi-bar. The app component will host the route so we don't need it in the configuration. Navi-bar of course will be embedded into the templates of other components and doesn't need a route. Next we add our NgModule decorator. This time, however, in the imports we have RouterModule. The RouterModule for root method should be only used to provide routes for the root module. In a later video we will use another method to use child routing definition. Then we have our first route that points to our home component. The path corresponds to the path that we can use to access this route. Component specifies which component will be loaded by this route. Now this configuration will display the 404 error if we run it this way since when we start there will be no active route. Instead of the standard route that home component is using now, we should be using a default route. In order to make this route the default route, we're going to empty the path. Now this matches the empty path when our app starts and causes the home component to display first. Now the next route is a wildcard route. This serves to match any route that is not configured in our app, which is what the asterisk in the past specifies. Any route that is not configured will display our 404 error. The thing to understand is that the router works from the top down with the first match strategy. If we put the wildcard route first, we would always get the 404 error, which is why we always have to add that route last. This is also why we would get the error if we kept home in the path for the home component. This is because the app starts with an empty path. Now since an empty path was not configured in our routes, it would match the wildcard route and display the error. There are a few more different ways you could configure your routes that we will look at later on in the course, but for now this will get us going. Then after our routes we have added router module as an export, which makes these routes available to whatever module imports them. Then after that, the last thing we add to the AppRoutingModule is the class. Next we import our AppRoutingModule into our AppModule and then we add our AppRoutingModule to the imports instead of the declarations like we do with our components. Remember, imports will be available to any components that are declared in this module. As a result, the components have access to the routes. Now we use the AppComponent for the next part because it is the component that is launched from the bootstrap. Go back to the app.component.html file. Now we don't have to add anything to any other files in this component or import anything. Remember the routes are available to us thanks to the AppModule. This makes the components attach to those routes available to us as well. RouterModule also makes the router directive available to us. Remove everything from the app.component.html file and replace it with the router-outlet tag. This is the reason we don't need a selector for the components loaded through the router. The router-outlet will serve as the selector for our components that are routed to. Now this is only a basic configuration of the router. We still have a lot to learn about navigating around our app, but now we have enough to actually see what we have been building so far.
In this lesson we will actually preview our app in the web browser. We will also make sure that our routing is configured correctly. We also want to make sure that we don't have any typescript errors. If we do, our app will fail to start when we try to run it. Before we check out our app, let's look at the package.json file in the root of our project. As we know, this is the file we used to configure our project, but it also houses the scripts that we will use to preview our app. If we look at the scripts section of this file we see the start script. This script launches the webpack-dev-server on port 8080. This step doesn't actually build our files for us, and instead generates our bundles in memory and displays our project. Now if you navigate to your project in a command line, type in npm start. When we run it, the files will be compiled to memory and then our dev server will launch. Now we can be sure we don't have any errors because if we did we would see a lot of red right now. To access the project, type in localhost:8080. Now when the app launches, we should see the nav bar and the hero image. Now to test if our routing is working properly, go ahead and type in a nonexistent route. Now as expected, when we try to access a route that doesn't exist, we get the 404 error. Now if we go into our developer's tools and then if we go into elements, we can see the bundle files that have been dynamically added to our index.html file. Also in the console you can see that Angular 2 is running in development mode, which is to be expected since we aren't generating a production bill. Now I know it doesn't do much yet, but you currently have a working Angular 2 app with routing implemented.
You now have a working, but very basic app. We didn't get too crazy with the components, but I wanted you to get comfortable with them. This is a key part of becoming competent with Angular 2. Once you master the syntax of the framework, building your projects will become easy. This module has started you on the right path and you should now be able to just look at a component and tell how it works. Since the initial components we built were so simple, we have a lot of room to improve our app. We also have a start on our routing, which will be a key part of our app going forward. In the next module we will build upon this project and add authentication so that users can sign up and log in. We will introduce our first Angular 2 service to make this happen.
Setting up Authentication
Hello, everyone. I am Reggie Dawson. Welcome back to the Angular 2 End-to-End course. In the last module we got up to speed with the syntax and structure of an Angular 2 app. I know it was a lot to digest, but now you should have a solid foundation to build on. The things you learned in the last module will be used throughout this course. In this module we will build out our authentication system. The app we are building will have backend services that authorized users can access. We don't want to just allow anyone access so we will need to create some sort of authentication system to allow users to log in. Now under normal circumstances we would have to build our authentication system from scratch. Although this isn't necessarily difficult, we will instead leverage a service called Firebase. Now Firebase was originally a separate entity, but has since been acquired by Google. We will use Firebase to host the backend data of our app, but Firebase also comes with its own authentication system built in. This turn-key solution allows Google, Twitter, and Facebook in addition to standard username and password logins. Now once we have Firebase installed into our app, we will then set up a feature area. The admin area will exist as its own subsection of the app. As a result, we are going to configure the admin area with its own routes. Also in this module we will configure our first service, which will handle the authentication using Firebase.
Setting up the Admin Feature Area
In this clip we will set up the admin feature area. Now the feature will be in its own folder, but inside this folder will be all of the components that will make up this feature. The first thing we will do is create a folder called admin. In the root of this folder we will create a file called admin.module.ts. Now this will be the root module of the feature area. It will be similar to the app module, but will serve a different purpose. As a result, it will be configured differently. Now we won't do anything with this file right now, but instead we will save it for the next clip. What we will do, however, is set up some basic components. They will just be placeholders for now, but will allow us to test our app as we go along. We need to set up the files because we're also going to be setting up routes and if we don't have the components we will get errors. First we will set up the sign-up component. Again, we place this component in its own folder. Then create a file called sign-up.component.ts. Inside this file we set up a basic component. Again, this is just here so that we can run the app. We will circle back in a later clip and add some more to this. Then we're going to add a file called sign-up.component.html. This is just a way for us to know when we navigate to the sign-up page. By the end of this module this component will look much different than it does now. After that, create a file called sign-up.component.css. Now we won't add anything to this file right now, but later we will use this to add styles that we need for this component. Next create a folder called login. Inside we will add files called login.component.ts, login.component.html, and login.component.css. Login.component.ts will be the same as the sign-up component, except the file names are different. Then login.component.html will just have a title as well. And again, we save the css file for a later clip. Then finally create a folder called adminMenu. This will be the menu that we will use to access the different areas of our backend. After we have the folder, create admin-menu.component.ts, admin-menu.component.html, and admin-menu.component.css. Again, the admin-menu.component.ts file is the same as the previous components that we have set up. Then we add the title to our admin-menu.comoponent.html file. Now that we have our basic components set up, we can move forward in the next clip and configure routing.
Configuring the Admin Module
In this clip we will set up routing for the feature area. Instead of us having to manage routes app-wide, we can just configure routes in our feature area and import them into our app module. Now as opposed to what we did earlier in separating the routing module out into its own file, this time we will add the routes for this area into the admin module. Now since we won't be adding a lot of routes to this, it will be manageable as part of the admin module. Let's go to admin.module.ts. Now the first thing we will add are the imports. Now here we have the different modules we will need in our feature area. NgModule we know provides the NgModule decorator. CommonModule provides some directives we will need. RouterModule and Routes allows us to configure routing, and the FormsModule allows us to use the features that Angular 2 provides to work with forms. Then we import our components as well as our service that we are going to add. Now we will configure the admin component in a moment. We will set up the user service in the next clip. The next thing we are going to do is add our routes. Now this looks a lot different from the routes that we initially set up and that is because these will be child routes imported into the app module. Now even though this has a path and a component, the AdminComponent will actually host the other components. The AdminComponent will have another router outlet that will take over for the main router outlet that we already have. This allows us to change the look of the app when we navigate to the admin area. Now inside the admin route we have the children routes for the login, signup, and admin components. As you can see, the AdminMenuComponent is the default route we will navigate to when we access the admin area. We also have something new, canActivate with our UserService inside as an argument. CanActivate is what we call a route guard. This allows us to protect certain areas of our app against unauthorized access. The UserService is there because it lets the route guard know where to go to verify if we can access this route. Now how we access these routes from our main app will be by adding admin to our path. That will take us to the admin menu where the route guard will make its check. We will learn more about the route guard in the next clip. Then we add our NgModule decorator. In the imports we have our CommonModule, FormsModule, and RouterModule. Instead of the forRoot method we use forChild with our RouterModule, which is what we use for child routes. Then we export the router module so that it is available to other modules. The declarations are for our components that we need access to. Then finally we have providers which allows us to inject our service into this module. Next, create a folder called adminComponent. Inside this folder we will just have admin.component.ts. Here is where we will set up the component that will host our routes. Now we don't need a component-level css file in this one, so all we have is the template. I have added the template inline since it is just a router outlet. The last thing we need to do is import the admin module into our app. Go back to app.module.ts. The first thing we need to do is import the AdminModule. Then after that we add the AdminModule to our imports. Now when we add the AdminModule we need to place it before the AppRoutingModule. If not, the app will not work properly. Remember, I set it to top-down first match policy with routing. If we added the routes after the app routes we would always see the 404 error. Now notice we don't have to import the individual components, and instead we just need to import the AdminModule. Now with our route set up, in the next clip we will create our first service that will handle user authentication.
Creating a Service
Services in Angular 2 allow us to provide data to our components in modules. The beauty of these services is that the component doesn't need to know how it got the data as long as it's in the proper format. First create a folder called adminShared. Inside that folder create a file called user.service.ts. Of course, the first thing we add is our imports. Injectable lets us use dependency injection to add our service to other components and modules. CanActivate is how we will check if a user is logged on before we navigate to the guarded route. ActivatedRouteSnapshot and RouterStateSnapshot will be used when we process the route guard. The we add our injectable decorator. This is followed by our class with the implements CanActivate keyword. We use this keyword when we are going to add a special method from Angular 2. This time it will be CanActivate. Since we use this keyword, this class must have the CanActivate method. Then we also have a userLoggedIn variable that is set to a Boolean. Then we add the constructor where we inject the router. Then we add the CanActivate method. ActivatedRoute is used to hold the route that will be activated. RouterStateSnapshot represents the future state of the router if we pass through the route guard. Then we set the url variable to the RouterStateSnapshot url. Once we do that, we return the url to the verifyLogin method. Then in the verifyLogin method we first check if the userLoggedIn variable is set to true. If it is, we return true. If we return true to CanActivate, the route check will pass. Now since a function can only return something once, if the user is logged in, we will exit the function after returning true. If the user isn't logged in, we will navigate to the login page. As a result, no one can access the admin menu if they aren't logged in. The final thing we do in the function is to return false. Then let's go back to the navbar component. Now here we will add a router link to our menu. RouterLink is a directive that allows us to navigate around our app by using links. Here we are just using admin. The default route is the admin menu which we know is guarded and as a result we should end up at the login page when we visit this link. Now if we save our project and then navigate to it in the command line and run npm start, well, it seems I had a misspelled file name, which I have since corrected. Now if we go to localhost.8080, then if we click on the admin area, we should navigate to the login page. Again this is not much, but we can be sure that our child routes are working.
Finishing the Service
Now that we have Firebase installed we can finish up our service by adding the methods we will use with our authentication. Before we get to that, let's add a few more variables to user.service.ts. After that we will add another method to register a new user. Before a user can log in, we need a way to create a new account. In the register method we have email and password as the arguments. Then we use the firebase.auth creatUserWithEmailAndPassword method. Once we create the new account we are automatically logged in. Now if we fail to create an account, an error message will be returned. Here we are just alerting the user with the error message. Then the next method we will add is verifyUser. Here we are using the firebase.auth current user to grab the currentUser. This will only work if someone is logged in. When it detects someone is logged in, we will set the loggedInUser to the email address of the authorized user, which is also the username. Then we set userLoggedIn to true. We know this will allow us to navigate to the admin menu. Then we use the router.navigate method to go to the admin menu. We will use this method after we create a user as well as when we log in. The login method will also take email and password as arguments. Here we are using the firebase.auth signInWithEmailAndPassword method, which requires both the email and a password. Again we use a catch method to handle any errors. Now once we log in, we need a way to log the current user out. Also, remember that when a user signs up, they are automatically logged in. In the logout method we first set the userLoggedIn variable to false. This will prevent access to the admin menu which is protected by the route guard. The next thing we do is use the firebase.auth.signOut method. Now after that our user service is finished. We can now begin to incorporate this service into our components.
Configuring the Sign-up Component
Now that our user service is finished, the rest of our authentication system can be completed. We will start with the signUp component. Now with this app in its present form, we are allowing anyone to sign up. In a production app you could create the user accounts manually or use Firebase to track admin users. You will need to do this because any user that signs up will have access to your admin area. First let's go to signup.component.ts. First we will import our service in the router. Even though we provided the service in the module, we still need to import it to use its methods. We import the router so that we can use it to navigate to another route. Then we add variables for our email, passwords, and passwordFail. We have two passwords so that we can verify the password-entered matches. The passwordFail will be used to track the state of the password and whether or not they match. Then we add our constructor where we inject our UserService and the router. Now the signup method first checks if the passwords entered match. If they don't then passwordFail will be set to true. This will in turn display a message letting the user know. If the passwords do match, the passwordFail will be set to false in case non-matching passwords were entered previously. Then we use the UserService register method with the email and passwords as arguments. Then once we register the user, we will run the verifyUser method. This will set our logged in user and navigate to the admin menu. Then we add a cancel method in case the user navigated here accidentally. All this does is use the router.navigate method to go back to the login screen. Then if we go over to the signup.component.html file we can build our signup form. First we will add a label. Then we will add our form tag. Now we are using a line center, which is a class from foundation to line up our form inputs. Now we aren't using the NgForm directive in this form because we don't need NgSubmit for our information. Instead we will just run a method when the user clicks on the button to submit the information. Then we add a text input for the email address. As usual, we are just using the classes and styling that foundation provides for us. The Angular parts with the ngNodel wrapped in square brackets and parentheses. This indicates that this is two-way data binding. This means that the information will flow both from the component to the template as well as from the template back to the component. What we are doing here is binding to the email property. When we use ngModel the control must have the name property. Then we have a template variable set to ngModel. We use ngModel to verify the contents of the control. We see this in action in the div that follows the control. First we use one-way binding on the hidden property bound to the valid and pristine state of the control. Valid is when there is something in the control and pristine is when there has been a change to the data in the control. Hidden will hide the div as long as the expression evaluates true. It will true when there is data in the control or the data has changed. Therefore when there is no valid data in the control, the div will show. Then we add another input, but this time it will be for a password. Setting the input to password will conceal what is typed in this control. Otherwise this input is pretty much the same as the email input with the exception of the message in the div, which lets you know that a password is required. Then we add a second password and put to verity what is typed as the password. Then we add a div with the ngIf attached. This directive allows us to display an element if the expression evaluates true. Here we have bounded to the passwordFail variable. If we set this to true such as when the passwords do not match, then the message will show. After that we have a button group with two buttons. Here we use event binding on the click event to assign our buttons to the signup and cancel methods. After that, our signup component is finished. Now we could take a look at our project right now, but let's wait until the next clip when we set up our login page.
Configuring the Login Component
Now that we have our signup component, we need a way to log into our app. The login component will be very similar in how it's configured to the signup component. Go to login.component.ts. First we will import the UserService and router. Then after that we will add variables for the email and password. Then we will add our constructor, again injecting the UserService and the router. Once we do that we can add our login method which makes use of the login method from our UserService. Here we are passing in the email and password variables to the login service. Then after that we run verifyUser which will navigate us to the admin menu. Then the signup and cancel methods just navigate to other routes. Signup will go to the signup page while cancel will navigate all the way back to the start page. Next let's go to the login.component.html file. First we will add our prompt for the user to log in. After that the rest of this file will be like the signup component, except it will just have one password input. First the username will prompt for an email address. We will also display an email required message if the control is empty. Then we do the same thing with the password input. After that we have two buttons. One for login, the other to sign up. Then the last thing we have in this file is some text that we have attached a click event handler to. This is how a user can get back to the main site if they navigated here by accident. The last thing we need to do is go to the login.component.css file. Now here we have added some rules for the text that appears at the bottom of the template. Since it is originally a paragraph tag, we're going to make a few changes so that the user will recognize it's a link. Now after that our login component is finished. Go ahead and save your project and then navigate to it in a command line. Then run npm start. Now if everything is typed correctly, you should see all green and that means everything compiled correctly. Now if we navigate to localhost:8080 we can see our project. Then if we click on the admin area we should be taken to our login page. If we click signup, we're taken to our signup page. If we click cancel, we go back to our login page and then back to the main site takes us all the way back to our start page. So far our project seems to be working properly. Now we won't create an account just yet and we'll save that for our next clip when we create an admin menu. For now I just wanted to verify that everything was typed correctly and we can move forward without any errors.
Configuring the Admin-menu Component
Now the last component we need to finish is the adminMenu component that will allow us to access the backend of our app. First we will start off with admin-menu.component.ts. Of course the first thing we add are the imports. Here we have also added OnInIt which we import from angular/core. This is a special method of Angular 2. We also import our UserService and router as well. Then we add implements OnInIt to our class. This again lets Angular 2 know that we're going to implement one of its special methods inside of this class. In this case OnInIt is a method that we use to execute other methods when our component is created. Typically we use this to load some sort of data when our component is created. We will use OnInIt to get the currently logged in user. Then we add a variable in our constructor. Again we inject our UserService and router into our constructor. Then we add the ngOnInIt method which we have to include in this class since we use the implements keyword. All we do is use the loggedInUser variable, which we know is set by the verifyUser method. This method is run whether the user logs in or signs up so there will always be a loggedInUser when we navigate to the admin menu. Then we add logout, which uses the logout method from our service. After that we use router.navigate to go back to the start page. Then if we go to the admin-menu.component.html file we can create our top bar for our admin area. This will just be a standard foundation top bar that we are adding. First we add a div to wrap our top bar. Then the top-bar-title represents branding for our top-bar. Then we add links that will eventually be for our admin functions. Then after that we add an ngIf bound to the user variable. Of course this will only appear if a user is logged in, but we also know that only a logged in user can make it to the admin menu. First we have a link to log out since otherwise there would be no way to clear the logged in credentials. We are just calling the logout method when the button is clicked. Then the other menu item just displays the email address of the logged in user. The final thing we need to do is go over to the admin-menu.component.css file. Now all we're doing here is giving the items in our admin-menu top-bar a consistent look. Once we do that, our admin feature area is complete.
Now we will finally preview the completed admin area. The first thing we will do is create a user account. Then after we create that account we will make sure that we can log out. Then once we do that, we will make sure that we can log in with our new account. Go ahead and save your project and navigate to it in a command line. Then you're going to run npm start. After that we're going to go to localhost:8080. Once your project is up, go ahead and click on the admin area. As expected, this should take us to our login page, but first we have to create an account. Go ahead and click the signup button. Go ahead and sign up with an account and make sure your passwords match because if they don't, you will then get the error message if they don't match. Then after our account is created, our user is logged in and we navigate to the admin menu. Now let's go ahead and log this user out. And we go back to our start page. Now if we log in, and now we are logged in with the account that we previously created. And then if we log out, we are again taken back to our start page. We now have a basic authentication system set up so that we can verify authorized users of our app.
Now that we have our authentication system complete, we can now begin to flesh out the functions of our backend. We learned that we can use Firebase to authenticate our users, whether they are email and password based or existing users from Facebook, Google, Twitter, or GitHub. After our users are authenticated we can now begin to make use of the real-time database that Firebase provides us. In the next module we will build out the blog system that will deliver the content for our site.
Setting up Blog System
Hello, everyone. I am Reggie Dawson. Welcome back to the Angular 2 End-to-End course. Now that we finally have our authentication set up, we can now add the blog system that will provide the content for our site. Although we have leveraged Firebase for authentication. This is the real reason we chose Firebase. In this module we will build another service using Firebase, but this time it will be for managing blog posts. The beauty of this is that we will not have to write a lot of code to configure the database. It will be as simple as using the web base API to interface with Firebase. After that we will configure our admin area to allow us to manage blog posts. Now with the authentication system we can just create accounts and nothing more. If we want to delete a user at this point we will have to go into the Firebase console. The blog will be different in that we will be able to create, edit, and delete blog posts. Once we have the blog set up, we will then need to configure the customer-facing portion of our app to host the blog. We will add a listing of the individual posts to the start page. Then we will create a component that will be used when we select an individual blog post. Once this module is finished you will have a working content management system.
Creating the Blog Class
Now before we build our service that will power our blog we will first create a utility class that we will need for a few purposes. Of course we are using TypeScript and this class will allow us to provide a shape for our blog data. The second thing we will do with this class is to use it to create objects that will represent our blog posts. You will see how all this works as we build the blog system. In the meantime let's create a file called blog.ts in the admin shared folder. Now we create this class the same as we do with our Angular 2 classes, except we don't have any imports or a decorator. Of course, we still export the class so it is available to the rest of our app. Now the only thing we will have in this class is the constructor. Inside we will have properties that will apply to the objects that we create with this class. First we have the title, which will be the title of our blog post. It is set to public so it is available to other classes. Then we add the content property, which will be the actual text of our post. Now of course we're going to be able to add an image with our blog post and here we capture the title of our image. I have also added the question mark, which indicates that this property is optional. The first two properties are required, but this property can be omitted as we create our objects. When we add optional properties to our class they have to come after any required properties. Then we add img which is set to any. I can actually change this to a string if I like since we are going to capture the image in a base64 format. I will leave it set to any so that we can accommodate for any future changes. Then we have the id of the blog post. This will actually be generated by Firebase for us. This is because this id will be the index for our blog post. This means that we can have duplicate content in our posts since they will be sorted by this unique id. Now I made the final three properties optional just to show you how this works, but they are all going to be required for our blog post. Now that we are finished we can move on and create the blog service, which will make use of this class.
Creating the Blog Service
Now we are going to create a service to handle some interactions with Firebase. So far we've just seen the authentication methods that are offered with Firebase, but there is so much more that you can do with the real-time database. In addition to the database we will also use Firebase storage for our images. Create a file called blog-admin.service.ts in the admin shared folder. We will of course start our file out with our imports. We know injectable provides the decorator we need for a service and Firebase is giving us the Firebase methods that we need. Blog is the utility class that we created in the last clip. Then we add the injectable decorator in our class. We don't have to initialize Firebase in this service because we will be logged in when we reach the blog area. As a result, our Firebase project will be available to us. Then we add the createPost method. Here we are using our class as the type to the argument in our method. Again, we know this provides the shape of the data for our blog posts. Then we create a reference to our Firebase storage. Understand this is not the database, but Firebase storage where we will hold our image. Firebase storage is used to hold actual files such as images and audio as opposed to the JSON style data that we store in the Firebase database. Before we can write to both storage and the database, we first must create a reference to that location. Then we use our reference with the child method. This allows us to reference a child location in our storage. This location does not have to exist prior to writing to it and if it doesn't, it will be created when we add the first file. Here we are creating the images path and using the image title as the title for the image. Understand that the images we will use will need to have unique names since they will all be in the images folder. Using the same name will overwrite the existing image. Then we use the putString method against the actual image. Base64 indicates that this is a base64 formatted image. Optionally we could have used the put method for other file types. Now you may have noticed that the properties that we are using are from our blog class. Since we are going to pass the service an object, we can unwrap the properties and use them in our method. Now since the putString method is promise based, next we will chain in the then method to handle the return snapshot. We need this since the image will exist outside of our database and we need a way to include the image with our blog post. You may also notice that the function looks a little odd. This is an arrow function which is an ES6 way of writing anonymous functions through an and. Then we use the snapshot to grab the downloadURL from the metadata. We could've added more metadata when we added the image to storage such as size or content type. The downloadURL is actually an array of strings which represents the path to download the image. It is an array because we can add more downloadURLs from the console, but when we create it, there is just one. We can also use this downloadURL as an image source, which is why we are capturing it so we can use it with our blog post. Now that we have our image we will create a reference to the Firebase database. The syntax is similar to storage, except this time we aren't using the child method. Again we are creating the blog post path on the fly if it doesn't already exist. Then we use the reference with the push method. Push will append the data to a list as well as generating a unique key for each item added to the list. This allows us to create identical data for any of our posts since they are indexed by their id. Then we use our set method against the push method. We can also write data to Firebase using just a set method alone, but this will overwrite any data that exists in a location, which wouldn't work for our blog. Here we are unwrapping the contents of our object to add to the database. The title in the content is the text that will be displayed in the post. We will capture the image title in case we need to delete the post. Since they exist in two different areas, we have to account for when we delete a blog post. If not, the image would still exist in storage. In a small scale app this wouldn't be an issue, but in a full-scale production app this would be unnecessary overhead as old content is removed to make way for newer content. After that we use the downloadURL that we grabbed from the snapshot. As we know, we will use this url as the source of our blog images. Then finally we use key to grab the id that is generated by Firebase push. Now the only reason we do this is so that we will be able to delete the post if we need to. Then we account for any errors, which is always good practice. Now bear in mind this is for failure of the image to be added to storage. We aren't accounting for any errors with adding our blog post. If the upload fails, only the catch method will be executed. Now that's all we will add to this service for now. The last thing we need to do is import the service into our admin module. Of course, save your work first and then go to admin.module.ts. The first thing we will do of course is import our service, and then we add the service to our providers. We are finished with this service for now, but we will be adding to it in a later clip.
Updating the Admin Menu
Before we add the component to manage the blog, first we need to make a few changes to the admin-menu.component.html file. First we need to add a way for us to access the blog admin area. We will replace the temporary placeholder links with the router link that points to the blog-admin route, which we will create in the next clip. Now our admin-menu looks really plain right now, so we will add an image into the admin shared folder, again inside of a folder named images. Then we will use this image in our admin-menu component. The admin-menu will be a splash screen of sorts for the admin area. We will also add a warning for unauthorized users. After that we are finished with the changes we need to add to allow access to the blog area.
Creating the Blog Admin Component
Now that we have the preliminary setup complete, we can add our first component that will directly interact with Firebase in our blog posts. The blog admin will be where we can add, edit, and delete blog posts. First we will add a folder called blogAdmin inside of our admin folder. Inside this folder we will create a file named blog-admin.component.ts. Of course, the first thing we add is our imports. We use OnInit because we will grab our blog post when this component is created. The UserService is imported because this component will make use of some of its methods. Everything else has been imported in other components so you should understand why we are importing them here and what they provide to this component. Then we add our component decorator, again nothing we haven't seen before. As usual, we have our templateUrl and our styleUrls. Then we add the BlogAdminComponent that implements OnInit. Remember we have to add implements when we use OnInit. Since we used it with implements we have to create an OnInit method in our class. That is why my editor is warning me with this red underline. Then we add some variables. The user will hold our user the same way we used it with the admin-menu. The blogAdmin will have the same top bar, just with different links on the left side. We will use the menu choice to swap between the different states of the blog-admin menu. The blogPosts array will hold the blog posts that we return from Firebase. Then we add the constructor where we inject the UserService, router, and the BlogAdminService. Then we follow up our constructor with the same logout method that we used in the admin menu. Next we have the chooseMode method that will display the correct menu depending on what was clicked. We will pass a string to this method that we will set as the menu choice. You will see how we will use this when we set up the template in the next clip. Then we add the ngOnInit method where we grab the user like we do in the admin menu. We also call the getPosts method that will grab our blog posts for us. Then after that we add the getPosts method. The first thing we do here is create a reference to our Firebase database. As you can see, we are pointing it at the blog post path. Then we use the once method to grab the data. Now I could've also used the on method which works similar to this one, but I prefer one since it is a promise based method. Now since the data we will return will be displayed in our app, it will cause an error if we bind to the content that is fed from Firebase data and it is not available. There are a few ways to deal with this, but in this instance, using the once method was enough to handle the issue. In a later clip we will take another approach to deal with this problem. Now once is a listener and it responds to the value which in this case is our blog post. Once is the recommended way of returning a list of data even if it is just one item. As a result we will need to loop over our data to extract it. The great thing about Angular 2 is that we can handle that directly in the template. After that we chain in our success method. Again we are using an arrow function and this also returns a snapshot. Instead of metadata, however, this snapshot will hold our blog posts. Then we capture the snapshot in a temporary array. We are doing this since this data is returned as JSON which Angular can't directly parse. Then we use the keys method to extract the keys from our data. Then after that we use the map method to extract the data as an array. We capture this data in our blog post variable. After that we are done with this file for the moment. The last thing we need to do is import this component into our admin module. First of course we import the BlogAdminComponent and then we will add a route so that we can access our BlogAdminComponent. We also add the route guard so unauthorized users cannot access the blog admin area. Then finally we add the BlogAdminComponent to our declarations. We can now access the BlogAdminComponent in our app, although this will not work without a template. In the next clip we will set up this template.
Creating the Blog Admin Component Template
Now we need to add the template for the BlogAdminComponent. Add a file named blog-admin.component.html to the blog admin folder. Now since we are going to have the same top bar, we can copy it from the admin-menu.component.html file. Now all we have to do is change the links on the left side of the top bar. First we have the add post link that will execute the chooseMode method. We will pass add to this method. This is how we will switch to the add post menu. Then we have the manage blogs link that is bound to an ngIf. This link will only appear when the add post menu has been selected. This is how we will navigate back to the BlogAdminMenu. Here we are using the chooseMode method with no value. You will see why in a moment. Then finally we add a link to take us back to the admin menu. Then after that we add the ngSwitch directive, which is what we will use to display our menus. The menu to add a post will be a separate component and instead of adding a route for it, we will use the ngSwitch to display it. NgSwitch displays an element when an expression matches the menu choice. Remember, we set the menuChoice to what we pass in the chooseMode method. First we add the case for when we choose the add menu. The idea is that the element will be displayed when we transmit the matching case. Then we add the default case that will be displayed when we don't have any matching cases. Remember how we use chooseMode with the manage blogs link. This will set the case back to the default when we click this link. This will also hide the manage blogs menu option. Next let's create a file called blog-admin.component.css. Here all we do is add the styles to make our top bar look the same as the one from the admin menu. After that, save your project. Then navigate to your project in a command line. Now I have encountered some issues with different versions of Firebase and storage. This can be remedied by going to the latest version of Firebase. Before we preview our project, we're going to run npm update. Now this will update your Firebase to the latest version as well as saving it to your package.json file. After that we can run npm start. After the app compiles, go ahead and navigate to localhost:8080 in your browser. After that, click on the link to go to the admin area and log in. If you haven't already created a user account, go ahead and do so now. After you log in, click on the manage blog link. Everything looks fine, but if we go to the developer tools and look at the console, you will see a lot of red. Now this is expected because we don't have any blog posts so our getPosts method is showing an error. Don't worry. We start to fix this in the next clip when we add our form that will allow us to add blog posts.
Creating the Blog Add Component
Now that we have the basic framework of our blog system, we need some content to make sure our project is working correctly. In order to add this content, we need a form. Now this form will have a lot going on since we will need to add a way to grab the image that the user will upload to go with the post. First let's add a folder named blogAdd. Inside this folder, create a file named blog-add.component.ts. Of course the first thing we add is our imports and then we follow that with the component decorator. Now here we have added the selector in addition to the templateUrl. We add the selector because we aren't going to add a route for the form and instead we will just include it in the div for the form in the blog admin template. That may be a bit confusing, but you will understand better when we add it. Then we add our class and some variables. The image title, image source, the post title, and content are all the properties we will need for our post. Then we have our post itself where we use the blog class we created earlier as the shape for the data. Then we add the constructor where we inject the blog admin service and router. Then we add the fileLoad method that will respond when the user adds an image. Before a user creates a post, the image needs to be uploaded. The event is passed from Angular and lets us access the file that it's uploaded. Then we use the FileReader method to create a new FileReader instance. As you can probably figure from the name, FileReader is how we will read our image and then convert it to base64. Then we grab the file from our event and then after that we set the image title to the file name of the image. Then we read the file as a data url, which will return a base64 image. After we read the image in we use the file reader onload method to detect when the image is loaded. Once this is done we set the image to the image source variable. We will use this to display a preview of the image as well as being the image that we will upload to Firebase. Then we add the createPost method that actually sends the post to Firebase. We use the blog class to create a new instance of a blog object. We add the content of our post as the properties of this new object. Now I use substring on the image source because even though Firebase expects a base64 image, it has a problem with deleting text that is on the image. As a result I use substring to shave off the first 24 characters of the image source. Then we use the createPost method from our blog admin service to pass the post object to the service and create the post. After we add the post we will alert the user. Then the last method we will add to this file will be the cancel method, which will navigate to the admin menu if you decide not to add a post. After that, all we need to do is save our component and then import it into the admin module. The first thing we will do of course is add our import. And then we add it to our declarations. Once we do that, all that is left is to configure the template that will house the form that we will use to add the post. We will set that up in the next clip.
Creating the Blog Add Component Template
In this clip we will set up the template for our blog-add.component. Create a file named blog-add.component.html in the blog add folder. First we add a foundation row to hold our title and then we add a form. This form will be similar to other forms that we have added. Here we are adding a row that we will use to hold our text input for the post title. This input uses ngModel bound to the postTitle property. We also capture this value in the ngModel so we can use this to verify the contents of this control before we submit the post. Then we add a text area to hold the actual content of the blog post. This will allow users to enter a multi-line string as the post; otherwise this control is set up the same as we have configured other controls in our app. Then in the same row as the text area we add a file input. We bind the change event handler to the fileLoad method. After that we have an ngIf directive that will display a preview of the image that we upload. NgIf will keep the image hidden until we actually have some data in the image source. We also bind the image source to the source of this image. Now the final thing we add to the form are the buttons to create a post or cancel. The createPost button will be disabled if the title or content are not valid. If you recall, valid is when there is content in a control. Then we use the createPost method with the button. We don't have to pass anything to the method since all of the data in the form will flow from the controls the properties they are bound to. Then the cancel button simply executes the cancel method. Now after that, save the file and then we can add it to the blog admin template. Here we are using the add menu selector as one of the cases in our ngSwitch. Once we do that, go ahead and save the project and then navigate to it in a command line and run npm start. And then once the app is compiled and running, go ahead and log in to your admin area. After that, select manage blog link. Once we navigate there, click on add post. This brings us to the add post menu. Let's go ahead and add a post. Then when we add the image it should appear in the preview. Once you have your post set up, click on create post. An alert will pop up, letting you know that the post has been created. Once we click on OK, we go back to the admin page. Now to be sure we have added a post, let's go back to our blog admin area. Now if we look at the developers console we shouldn't see any more errors. If we don't see any errors we can be sure our post worked. Once you have that working add five or six posts with images so we have content to work with. Now once you have your content, you could take a look at it in the Firebase console to be sure everything saved correctly. First if we look in the database you can see all the posts and the information that I have added, and then in storage under the images path, we have all of our images. Your blog add component is now complete.
Finishing the Blog Service
In this clip we will add the methods that we will need to complete our blog admin area. Beyond creating posts, we also need an easy way for users to manage these posts. Now as the developer we have access to Firebase where we can actually use the console to manage posts. This works for us, but it is not ideal to allow end users to access the console. If we go back to the blog admin service, we can add the editPost method. Now we will only be able to edit the title and the content of our post. If you want to change the image, you will have to delete the post and recreate it. Now I chose to do this since changing the image will require deleting the image from Firebase storage and adding the new image. Now if you notice, we are passing it an argument that takes the shape of the blog class. This is a clue that we will again be passing in an object as our argument. Then we add a reference to our database in the blog post path. After that we use the id of the post as the key to determine what post to update. This is why we extract the id and save it with our posts. This time we will use the update method which we chain into our database reference. Of course we will be using a form and we will extract the content of the form with ngModel. After we update the title and content, we will alert the user. Then after that we add the removePost method. Again we have blog as the shape of our argument. Deleting the post is simple as all we have to do is add the remove method to our database reference. Again we use the id of the post to reference the post that we want to delete. Then we alert the user that the post has been deleted. Remember, we also have to account for the image which we want to delete if there is not a post associated with it. First we create a reference to the path and storage where we keep the images. This is why we extract the file name of the image and add it to the post, since we need the image name to delete it. Then we use the Firebase delete method chained to the storage reference. This is a promise based method so we chain in our success and error methods. If we are successful we will alert the user and let them know the name of the file that was deleted. If there is an error, the user will be alerted to the file name that could not be deleted. This way they can let the administrator of Firebase know and then they can delete it manually. Our blog admin service is now completely finished. We can now finish up our admin component and begin to display posts.
Finishing the Blog Admin Component
Now that our blog admin service is finished, we can complete the blog admin component. In addition to the form that the blog admin will hold to add posts, it will also display a listing of the existing posts. This listing will allow users to edit or delete any of the posts. If you go back to the blog-admin.component.ts file, we will add a couple more variables. The form display will be used to display the listing of blogs or a post for editing. The single post variable will hold the data that we will use when we edit a post. Then we add the editPost method. All this will do is set the singlePost variable to the post that we click on and then it will change the form display to false. This will in turn show the editPost form. The beauty of Angular 2 is that this is all the code that we need to add here to make this work. The rest of this will be configured through the template. Then we will add the cancelEdit method, which will just set the form display to true. This method will be attached to the cancel button that appears on the edit form and will switch the display back to the listing of posts. Then we add the updatePost method. This uses the editPost method from our service. When we click on a post to edit, we will load the post into the single post variable. As we edit this information it is bound to ngModel. Remember this flows both ways so any changes we make to the post title or the content will flow back into the single post object. When we click on the update button, any changes will be reflected in our singlePost object. After that we set the form display back to true which shows the blog admin listing. Then finally we have the deletePost method which will delete the selected post. The first thing we do is set up a confirm dialog to be sure the user wants to delete the post. A confirm dialog will return a Boolean depending on the selection a user makes. If they confirm the delete, true will be returned. Then the if statement will use the removePost method from our service to delete the selected post. Again, all we are doing is passing in a single post object as the argument of the removePost method. After we remove the post we navigate back out to the admin area. If the user declines to delete the post, we will instead alert the user that nothing was deleted. Now our blog admin component is finished. All we need to do now is add the blog listing. Before we do that, however, we need to add a pipe to manage the content of our posts.
Creating a Pipe
At this point we could easily display the blog posts on our blog admin page, but before we do that we will create a pipe to handle the content of our posts. I purposely added a lot of text to the blog post and as a result, displaying them a readable format becomes problematic. We will display a list of these posts, but foundation doesn't really account for overflowing text and all of the post content will be displayed in our listing. We need a way to display a snippet of the post in our listing and only show the full content of the post when we select it. To do this we will create a pipe. Pipes are a way to transform the data displayed in our templates. For example, Angular 2 ships with the uppercase pipe that will transform your text to uppercase. Now even though Angular ships with a lot of built-in pipes, it is relatively simple to create your own. In the admin shared folder, create a file called trunc-pipe.ts. Trunc which is short for truncate will cut out a specified number of characters from our content. First we import pipe and pipeTransform from Angular Core. Then we add the pipe decorator. The only metadata we provide is the name, which is what we will use to reference our pipe. Then we add our class with the implements Pipe Transform keyword. This of course means that we will need to use the transform method in our class. Then we add the transform method with two arguments. The value is what is passed to the pipe from the template. The second argument is the number that we will pass to the pipe that tells it how many characters to extract from the content. Another thing you may notice is that we are typing the return value of the transform method. Now I didn't do this too much during the course, but you can also type the return value of a function if you want to. The beauty of TypeScript is that it has optional typing, which means you can pick and choose when you use it. Then we have a text variable where we use the substring method to extract the desired number of characters. We use template strings to concatenate the string and after the content we display an ellipses. Then after we extract the text we return it. Now that's all it takes to create a pipe. The last thing we need to do is import the pipe into our app. Go ahead and save your project. Then after that, go to the admin module. First we need to import the pipe. Then we add the pipe to our declarations and exports. Remember, declarations will allow it to be accessed by other components declared in this module. Exports will make it available to components not declared in this module. Now after that we can use our pipe in our components. In the next clip we will put this pipe to use as we finish up the blog admin template.
Finishing the Blog Admin Component Template
Now that we have everything else configured, it is time to display the blog post in the blog admin area. This will enable us to view, edit, and delete blog posts. We will do this by configuring the rest of the blog-admin.component.html file. Now we will be adding a lot to this file and it is easy to miss a closing div or something like that. In Angular 2, minor errors in your templates will cause a problem. To make this easier to follow, I'm going to add a bit more spacing than I normally would. The first thing we will do is add a div with an ngIf attached. We are going to add this inside of our ng switch default case. We have bound this ngIf to the formDisplay variable, as we know this is a Boolean that controls what form we will display to the user. When the form display is true, this is the form that we will see. Then after that we add a div that will display the blog post. NgFor takes an array as an argument and will loop over the array. NgFor will then display a template for each item in the array. Let posts of blogPosts will use the blog post array that we created. On each pass of the loop we can access the current object with the post variable. You will see how this works in a moment. I have added four more divs inside of the ngIf div. The first row is a spacer to give us some room. Then we have the media object and a media-object-section. These classes are what we are using to set up the foundation media object. The media object will display a small image alongside text content. We will use this to display our listing of blog posts. Then we have a div that is holding the post image. We use attribute binding to bind the post image to the source of the image tag. As you can see, we use the posts object to access the image. Again, this is supplied by the array that we passed the blog posts. Then we will add another div inside of the media object div, but after the media object section div. This will be the text section of the media object. Here we have the post title followed by the content. Now as you can see, we have our added our pipe to the content. This is how you use the pipe by passing the data that you want to transform to the pipe. We add the number of characters we want to grab after the pipe separated by a colon. Here we are setting our pipe to extract 140 characters. Then we have the editPost method set to execute when we click on the edit button. Remember that an identical media object will be added for each item in the array. As a result, each button will pass the current post object when we click on the edit button. For example, if this was the first post, the post object would contain all of the data from this post. This is how we access the correct blog from our list. Also, after editPost executes, the edit form will be displayed. The delete button executes the deletePost method, which works similar to the edit method. Here we just pass in the post object again and we know the deletePost method will extract the id and use it to delete the correct post. After that, scroll down a bit and add another div. Now this div has to be outside of the first ngIf div, but inside of the default ngSwitch div. This is also bound to ngIf, but it will be displayed when the variable is false. As a result, the different forms of the blog admin will toggle between the two depending on the value of the formDisplay variable. Then we add a form tag to wrap the whole thing. Inside this form we add a row to let the user know that they can edit their post here. Then we add a text input that is similar to the ones we have created in our other forms. We bind the ngModel to the singlePost title and as we know, this data will flow both ways. Then we add the text area for the post content. This is the same as the add form, except the ngModel is bound to the content property of the singlePost object. Now even though we can't edit the image here, I am still showing a preview so that the user knows what image is associated with the post. Then finally we add the buttons. The updatePost button will be disabled as long as the title or content is not valid. This of course can only happen if there is no data in either of these controls. As long as we have valid data, this button will execute the updatePost method again using the singlePost object for this specific post. The cancelEdit button will just run the cancelEdit method that will navigate back to the admin menu. After that we are done with the template. The last thing we need to do is go to the blog-admin.component.css file. First we will add a class to manage the height of the image in our listing. Then we add the styles for our spacer. After that we are finished with the blog admin area.
Now that we have our blog admin area complete, we will run it to make sure we have entered our code without any errors. Navigate to your project in the command line and run npm start. After it's done compiling, go to localhost:8080 and your app should be running. Navigate to the admin area and log in. Once you log in, you should see the admin area. Click on Manage Blog. Once we do that we should see a listing of the sample blog posts. If you click on edit on one of the posts, we should go to the edit menu. Go ahead and edit the content of one of the posts. After that you will see that the change is reflected in the listing of posts. Next let's delete a post. When the confirm dialog comes up, hit cancel and nothing should be deleted and the alert box should verify this. Now let's actually go ahead and delete a post. When we delete we are notified that the post has been deleted. We are also alerted that the image has been deleted as well. If we go back to the blog admin, we can confirm that the post has indeed been deleted. We can now be sure that our blog system works on the backend. Now we need a way to display our blog posts for the end users of our app. In the next clip we will start to incorporate the blog into the publicly accessible areas of our app.
Adding the Blog Listing to the Home Component
Now that we have the blog system up and running, we need to add the post to the user facing portion of the app. In addition to displaying a listing of the posts, we also need to show an individual post if we select one. First we will set up the listing in the home component. Go to home.component.ts. First we will add our imports. We add OnInit to the component which we will use to grab the blog post when the home component is created. We include the UserService because otherwise we would have to create another way to initialize Firebase. Remember, Firebase initialize only runs when we log into the backend. End users that visit the site will not touch this service and as a result a call to Firebase database will fail. Then we add the implements OnInit keyword to our class as well as adding a variable to hold the blog post and our constructor. In the constructor we are injecting the UserService as well as the router. Adding the UserService to the constructor allows us to initialize Firebase. Then we add the ngOnInit method which will execute the getPost method. Then we add the same getPost method we used in the BlogAdminComponent. All we are doing again is using the Firebase reference to return all the blog posts. Then we transform the return data into an array that we can pass over to Angular 2. Then after that we add the choosePost method. This takes a blog object and extracts the post id. It then uses router.navigate to go to the post route. In addition to the post route we also have a post.id. This is a route parameter and how we will pass the correct post to the post detail page. Once we are finished with that we can go over to the home.component.html file. First we will add two divs. The second div is how we will wrap our templates we are going to use to display our posts. Again we are using ngFor to loop over the array. The medium for a column class will ensure that there are three posts per row. Then we add a foundation card to display the blog post. We use the post images as the display image for the card. Then we use the title, but we also make this clickable and add the choosePost method. Again, we pass in the posts object. Then finally we use our pipe with our post content and again grab 140 characters. After that, go over to the home.component.css file. The display-image class will set the height and the width of the image in the card. The spacing class will add some space between the header image and the blog listing. The card-spacing class creates spacing between the posts so they aren't pressed up against each other. Then finally the list-title:hover is used to give the end user a visible cue that the title is a link. When the user hovers over the link it will turn blue and the pointer will indicate that the link can be clicked. The last thing we need to do is go over to the Firebase console. First click on Database. Here we can view or edit posts as well, but right now we need to look at the security of the database. Click on Rules. First we need to change read to true. Under the default configuration, only authenticated users can read and write to the database. End users don't need to write to the database, but they will need read access. By setting this to true, end users will be able to see the blog posts. We didn't need to worry about this in the back end since we were logged on. After you make this change, make sure you click on Publish to save the changes. After that, our blog has been configured to display in our home component. In the next clip we will set up a blog detail page that will display when a user clicks on a selected blog.
Creating the Blog Detail Component
The last thing we need to do to incorporate our blog into our app is to set up the blog detail page. This is the page that we will navigate to when we click on a blog post. The first thing we need to do is add a blogDetail folder to our app folder. Then inside this folder, create files named blog-detail.component.ts, blog-detail.component.html, and blog-detail.component.css. First let's go to the blog-detail.component.ts file. How we start this file off should be very familiar by now. First we add the imports, but this time we are also grabbing the activated route and params from the router. You will see why we need these in a moment. Then we have our class with the implements OnInit keyword added. Then we add a variable to hold the single post and our constructor to inject our activated route and router. Then we add the ngOnInit method which will run when the component is created. Here we use ActivatedRoute to grab a snapshot of the route. We use param to grab the id param we set when we navigate to the detail page. After that we used the id we got from our param with our getSingle method. Then we add the getSingle method that takes an id as an argument. Now I could've easily used the returned array and pass it to an ngFor. Instead since it is one post, I will show you how to unwrap the data that is returned. First we start off with our reference and then we use the orderByChild method with the id. We do this because the equalTo method needs an orderBy to work properly. EqualTo with the id will return the post that matches the id. After that in the success method, we grab the returned data and transform it into an array. Instead of passing that data to an ngFor, we unwrap the data for each for each part of our post. Then we use the blog class to create a new instance of the selected blog for our display. Once we do that, we know how to access the blog post from our template. After that, let's go over to the blog-detail.component.html file. Now first we add a row to hold our image. Here you may have noticed we added a question mark after our singlePost object. This question mark is what we call a safe navigation operator. Remember earlier I mentioned how we used the promise-based method to deal with the fact that our data might not be present when our template loads? The safe navigation operator is another way to protect against this. By adding the question mark, this makes sure that our image is not displayed until the data is available and avoids an error. Then we add our go back button with a router link that takes us back to the home page. Then finally we add two rows to hold our post title and our post content. Again we have added our safe navigation operator to guard against our data not being present when our template is loaded. After that we need to go over to the blog-detail.component.css file. Here we will add some styles to make our detail page look better. First we reign in the image sizes a bit. Then we add some spacing between the image and the rest of the content. Then we style the post-title and the back-link. Then last thing we need to do is import this component into our app routing module. We will also need to add a route for this component as well. Go back to app.routing.ts in our shared folder. First we import the BlogDetailComponent, then we add a route that will be used when we navigate to the detail page. The path will be composed of the post path with the id of the post appended to the end. Now when we first created this component, we could've added the home and error components to the declarations and we wouldn't have had to import them in the app module. This time I will just add the declarations to import the blog component. After that the blog portion of our app is completely done. Make sure you save your work.
Now that we are finished with our blog system, we can run the app and make sure it is working properly. The backend to add in manage blog posts is now fully functional. End users that visit the site will be delivered content from the blog. As a result, we can use this content to drive the app. First navigate to your project in a command line. Then run npm start. Once your app is up and running, navigate to localhost:8080 to preview the app. And as you can see, our blog posts are now displaying on our home page. Click on a post and make sure that we can navigate to the post detail page. Also make sure the Go Back button works. Then after that, navigate to the admin area and log in. Let's make sure that we didn't break anything. Go to the blog admin area and add another post. Once the post is added, make sure it is listed. Then let's try to edit this new post. After we edit the post we've decided this post is no good. Let's delete it. We can now be certain that our blog system is working correctly and supplying our content to our end users.
Now that we have our blog we have a content management system that can be adapted for a variety of uses. An inventory system or a shopping cart would not be that difficult to create from our blog. Above all else, we can use this blog system as the backbone of our project. All we need to do is add blog post to fill out the content of our site. Now the app still needs a polish, but for all intents and purposes it is a complete app.
Setting up the Shopping Cart
Hello, everyone. I am Reggie Dawson. Welcome back to the Angular 2 End-to-End course. The next part we will add to our app is the shopping cart system. In our app we will have a shopping cart that will allow users to purchase our products from our site. We will stop short of processing payments as that is outside the scope of this course. You will, however, have a sum of the cart after we are done so it will be no problem using the payment processor of your choice. In order to save a lot of duplicate work, we will base the cart off the blog system. The products will work a lot like the blog and the only difference will be the information that we save with the products. After viewing this module you should understand not only how to build a shopping cart, but how to repurpose existing code for other uses.
Refactoring the Blog Service
The first thing we need to do is create a service for our products. For the most part, this will be identical to the blog service with a few small changes to make it handle products. Before we start on the changes, first we will copy and paste the blog class and rename it product.ts. After that we can first change the class name to product. Then the title and the content will be changed to name and description. The rest can remain the same, but we will add a price. We can also remove the question marks from all of the properties except the ID since all of these will be required. Go ahead and save your product.ts file. Then copy the blog-admin service and rename it product-admin service. First we replace the blog class import with an import for the product class. Then we change the name of our class to ProductAdminService. After that we can change the createPost method to createProduct. We also add the product class to this method as the shape for our product data. The next thing we need to do is change our reference to Firebase storage. We will hold the product images in their own path. Here we have changed the reference to point to the product images path. Then we use the product object to extract the image name and image like we did with the blog post. Then we set the Firebase database reference to the products path. Again we will store our products in a separate path from our blog post in the database. After that we change our push method to use newProd as a variable. This is not required and just serves to make the code more understandable. Then we change the set method to use the newProd variable. Then we update the title to use the name and then we change content to use description. Then before the id we will add the price. Next we will change the editPost method to editProduct. Again we will use the product class as the shape of our argument. Then we change our database reference to use the products path. After that we change the title to name and content to description. We add the price because we also will be able to edit this as well. Then we change the alert to read product updated. Next we change the removePost method to removeProduct. Again, we change this to use the product class as an argument. We also change the reference to the id to reflect deleteProduct instead of deletePost. Then we change our alert to say product deleted. Next we alter the storage reference to reflect the product images path. Then finally we change the delete method to reflect our deleteProduct argument. Then after that, save your work and go to the admin module. The first thing we need to do here is import the ProductAdminService. Then we add the service to our providers. After that, our ProductAdminService is finished. In the next clip we will refactor our BlogAdminComponent to manage our products.
Refactoring the Blog Admin Component
Now that we have our product service set up, we need to refactor our blog admin area into a product admin area. First let's copy and paste the blogAdmin component folder. Rename the folder to productAdmin. Then change the files inside to product-admin.component.ts, product-admin.component.html, and product-admin.component.css. First, let's go to product-admin.component.ts. Here we will start by replacing the import for the blogAdmin service with one for the productAdmin service. Then we change the import for the blog class to one for the product class. Then we change the templateUrl and styleUrl to reflect the productAdmin template and style sheet. And then we change the class to ProductAdminComponent. Now the user in the menu choice variables can remain the same. We will change blog post to theProducts and then use our product class as the shape of the array. FormDisplay will also remain the same, but singlePost will be changed to singleProduct. We will also use the product class as the shape for the singleProduct variable. Then in the constructor we will replace the reference to the blog admin service with one for the ProductAdminService. The logout and chooseMode methods can stay the same. Then in the ngOnInit method we will change the getPost method to getProducts. Then we will change the actual getPost method to getProducts. First we will change our path in the database reference to products. Then after that we will change the variable to hold the return products to theProducts array. Then we will change the editPost method to editProduct. Then we change the singlePost variable to singleProduct. Next we will change the updatePost method to updateProduct. Again we alter this method to use the product class. After that we change the editPost method to the editProduct method from our product service. Then we change the deletePost method to deleteProduct. Of course, we use the product class as our argument again. Then we change the verify dialog to say are you sure you want to delete this product? After that we just change the removePost method to the removeProduct method. Then go over to the product-admin.component.html file. First change the title from admin area to product admin area. Then we will change the menu options for adding and managing a blog to reflect adding and managing products. Then we will change the selector for the add blog component to a selector for the add product component. Then in the default case for our ngSwitch we will change the ngFor to reflect theProducts array that we created. Then we will change the image to reflect using the prod variable that will hold the individual products as we cycle over our array. After that we can change the title and the content to the name and description. We will also add the price after the description. Then after that we will change the buttons to reflect the editProduct and deleteProduct methods. First we change the editPost label to editProduct. Then after that we change the post title to product name and we also change the singlePost.title to singleProduct.name. We also change the name to editName and the template variable to prName. Then we will add a row to hold our price. Our blog post of course did not have a price so we have to add this to our edit menu. Then after that we will change the post content to post description. We will also change the id ngModel name and template variable. Then we will change the image preview to reflect the single product. Again we do not have the ability to change the image from the edit menu, but we will still show a preview. Then finally we will change the button to edit our product. First we will change the variables that we are using to confirm the proper content in our form. We will switch this to use the name, price, and description. If any of these are empty, the button will be disabled. Then we set our click handler to use the updateProduct method. After that we are done with our admin area. All we have to do now is import it into our app. Go to the admin-module.ts file. First we add an import for the ProductAdminComponent. Then we need to add a route for this component. Then finally we add the ProductAdminComponent to the declarations. The last thing we need to do is go over to the admin-menu.component.html file and add a link for the product admin. After that we have set up our product admin backend. Now we just need a way to add some products. In the next clip we refactor the blog add component to allow us to do just that.
Refactoring the Blog Add Component
Now that we have our product admin menu configured, we need a way to add some products. To do that we are going to create a productAdd component. All we need to do is copy and paste the blogAdd component folder. Once you do that, rename the folder product A. Then change the blog-add.component.html and the blog-add.component.ts files to product-add.component.html and product-add.component.ts. First go to the product-add.component.ts file. First we will change the imports for the BlogAdminService and the blog class to the ProductAdminService and the product class. Then we change the selector to product-menu. We also change the templateUrl to the product-add.component.html file. After that we can change the class to product-add.component. Image title and image source are fine. We will change title to name and content to description. Then we add a price variable set to a number. Then we change post to product and set the product class as its type. Then we change the BlogAdminService to the ProductAdminService in the constructor. Then we will change the name of the createPost method to createProduct. Then we will change post to product. Notice that we add properties for everything except our id. We do this since the id will not be added to the product until it is saved to Firebase. Then we change the call to the createPost method to the createProduct method from our ProductAdminService. We also change the argument passed to this method to the product object that we just created. Then we change the alert to reflect the product name. After that we can go over to the product-add.component.html file. First let's change the label to add a product. Then after that we could change the post title role to use the product name. First we change the label to product name, then we set ngModel as the name and the name of the input is set to prodName. We also capture the content of this input in a template variable. Then after that we add a row to hold the price of the product. We of course set the ngModel to our price property. Then we change the content text area to hold the product description. We start by changing the label to product description to reflect this. Then we set the id, the ngModel, the name, and the variable to the appropriate values. After that, the last thing we need to do in this file is change the createPost button to a createProduct button. This means we will change the label, but also the variables in the disabled property. We update these to reflect checking the name, description, and price variables. If any of these fields are empty the button will be disabled. Finally we will import the product-add.component into our admin module. First we add the import. Then after that we add the component to our declarations. Once you do that, save your project and navigate to it in the command line. Once you get there, run npm start to preview the project. Once the project finishes compiling, navigate to localhost:8080 inside of your browser to preview the project. Once you have that, go to the admin area and log in. Once you get there, click on the Manage Products link. When we go there we won't see any products so let's add some. Go ahead and add five or six more products. Now that our product's back in the setup and we've added some products, in the next clip we will set up a store display for our end users.
Refactoring the Home Component
Now in order to add our product listing we will just copy the home component. Copy and paste the home folder and rename it to shop. Then rename home.component.ts, home.component.html, and home.component.css to shop.component.ts, shop.component.html, and shop.component.css. First in the shop.component.ts file we replace the import for the blog class with the product class. We could delete the UserService import but we will keep it. This in case our user bookmarks directly to the shop page. Remember we have to initial Firebase before we can access data from it. If a use comes directly here, that won't happen if we remove the UserService from our constructor. It will still work, but only if we come from the homepage. After that we change the templateUrl and styleUrls to reflect the change in name to shop. Then we change the name of the class to ShopComponent. After that we change the blog post variable to products and use the product class as the shape of the data. Next we change the name of the method that we call in ngOnInit from getPosts to getProducts. Then we will change the getPosts method to getProducts. Now since the method to grab the data from Firebase is only dependent upon using the correct path, all we have to do is change the reference. Then we change the blog post variable to products. Then we change the choosePost method to chooseProduct. We also change the arguments to use the products object instead of the blog. After that in the router.navigate we will change the path to product and use the prod.id as the route parameter. Next let's go over to the shop.component.html file. First we are going to change the image that we use in the shop area. This image has also been copied to our shared images folder. After that we add a title row to let users know this is the shop. Then we change the ngFor to use the products array. Then we change the image to use our singleProduct variable. Then we change the click handler to use the chooseProduct method. We pass our product object to this method and it will work just the same as it did with the blog post. Then we change from the title to the name of the product. Then we add our price. Then we replace the content with the description of our product. After that let's go over to the app.routing.ts file. After that let's go over to the app.routing.ts file. First we add an import for the ShopComponent. Then we add a route for the shop. Now after that we need to go to the app.module.ts file and import the ShopComponent. Then after we import it, we will add the component in our declarations. After that we need to go to the navbar.component.ts file. First we will add the nav menu class to the div for our brand. The nav menu class will make our brand white, otherwise it will be styled as link which will make our brand blue. Then we add a router link that navigates to the shop page. Now our shop display is finished, but we won't preview it until the next clip when we set up a component to display individual products that we select.
Refactoring the Blog Detail Component
Now that we have our products being displayed, copy and paste the blog detail folder and rename it productDetail. Then change the names of the individual files in the folder from blog to product. The first thing we will do is go to the product-detail.component.ts file. Change the import for the blog class to one for the product class. Then change the templateUrl and styleUrls to match the correct files. After that, change the class name to ProductDetailComponent. Then change the singlePost variable to singleProduct. We will also use the product class for the shape of this data. Then in the ngOnInit method we will change the postId to productId. The snapshot can stay the same because we are still passing the id. After that we change the getPost method to getProduct and pass the productId to it. Then we change the actual getSingle method to getProduct. We have to change our database reference to our products path. Then we change the title to name and the content to desc. Image title and image will stay the same. Then we add the price and id. Now normally we wouldn't need the id, but we are going to use this to track our product for the shopping cart. Then finally we change singlePost to singleProduct and pass along all of the properties that we need to create a new object. After that, let's go over to our product-detail.component.html file. First we will change the image to use our singleProduct variable. After that we can change the route and the router link for the back button to shop. Then we change the title to name. We also change the name to be aligned to the left by adding the textLeft class. After that we add a row to hold the price. And then we change the content to use description instead. After that we need to import the component into our app routing module. First we add the import, then we add the same kind of route that we used for the blog detail page with the id included. And then we add this component to the declarations. Once we do that, save everything and preview. Once the project is up, click on the shop area. Then click on an individual product. If you click the Go Back button you should navigate back to the shop. Now we are done with all of the work on our shop that was based on existing components. The last part we need to complete our shop is a shopping cart to process the orders. In the next clip we will get started by building a service for our shopping cart.
Creating the Shopping Cart Service
Now that we can add and edit products we need a shopping cart to total up our purchase and then submit it to some sort of payment processing. There are a lot of options for this and getting into how they work with our app is beyond the scope of this course. Ultimately we will have the order ready for whatever payment processor you use and all you will need to do is pass over the amount to charge. Now to manage the shopping cart we are going to build another service. This will not make use of Firebase and will just use an array to show the shopping cart. This means that the cart will not remain persistent if the user refreshes the page. Eventually we will need to overcome this by storing the card in local storage, but for now that will not be an issue. In the shared folder, create a file named shopping-cart.service.ts. The first thing we add is the import for the injectable decorator which we use to set up a service. Then we add the decorator in our shopping cart class. Next we add a variable to hold our cart. We add the empty array to avoid an undefined error. Otherwise the shopping cart will not work correctly. Now the cart setup will be very simple and won't take a lot to make it work. First we will add the getCart method to grab the contents of the cart. Now here we use an immediately resolve promise to return the myCart array. Now the reason I used the promise here is because of the issues I encountered with the Angular templates loading before the contents of the cart have been returned. Since I am grabbing the data in the component and not in the template, I can't use the safe navigation operator. Instead I use the promise to deal with this. Next we had the addProduct method that we will use to add a product to the cart. This method will take the id, name, and price of our product as an argument. Then we will use the array push method to add an object for the product. Now take note that I added the number method to the price. Without this our cart will not properly calculate the amount of the cart. As I was building this part of the app I initially had dummy data populate the cart so that I would not have to have add products to test it. Once I had it working and changed it to live product data, the cart would not work correctly. The reason for this is that the default behavior of adding an object through the push method was turning the price into a string. To fix this I added the number method which forces the price to be a number. After that we alert the user that the product was added to the cart. Then we add the removeCart method which will remove an item from our cart. This method will use the id to remove the correct item from the cart. There are a few ways to handle this, but I originally used the filter method. It worked, but the problem is that when I removed items the template would not update. Now the default behavior when using ngFor is that it will update if the array that it is bound to changes. The problem is that the filter returns a new array instead of updating the initial array. Even though I was assigning the new array to the existing array, it would not work. Fortunately, the other option ended up being a better choice anyway. First I will find the index of the id with the map method. This will search by the id and return the index of the array of the item. Then we add an if statement to make sure we have an index since the indexOf method will return a -1 if it doesn't find a match. Then we use the splice method to remove the item from the array. We pass in the index and the number 1 to make sure the splice method only removes one match. That way we can have duplicate items and only one will be removed at a time. After that the service is finished. Next let's go to the app.module.ts file. First we import our service, and then we add the service to our providers. Once we do that, let's go over to the product-detail.component.ts file. First we will import the ShoppingCartService. Then after that we inject the service into the constructor. Then we add the addProduct method that will allow us to add our products to the cart. We use the addProduct method from our service to pass over our id, name, and price of the product. The last thing we need to do is go over to the product-detail.component.html file and add a link to add the product. Here we add a column set to text-right which will align our link with the right side of the screen. Then the click handler will use the addProduct method. We are passing in the id, name, and price from the singleProduct variable which we used to populate the product detail page. After that we are done adding the cart service. We could launch the project and test adding a product now, but we don't have a way to make sure the product has been added. In the next clip we will fix that when we add the shopping cart.
Creating the Cart Component
The last piece we need to add to complete our shopping area is the cart. We already have a way to add and remove items from the cart; all we need is a way to display the contents of this cart. The first thing we need to do is add a folder named cart inside of our app folder. Inside this folder add the cart.component.ts, cart.component.html, and cart.component.css files. First we will go to the cart.component.ts file. First we will add our imports. Of course you should understand what all of these imports are for by now. Then we add our component decorator with our templateUrl and styleUrls. We don't add a selector because we will add this component through routing. Next we add our class that implements the OnInit method. Then we have the myCart variable, which is where we will store the contents of our cart. We also have the cartTotal, which will hold the total of the items in the cart. Then we add the constructor where we inject the ShoppingCartService and the router. Then we add the ngOnInit method where we grab the contents of our cart. Remember the getCart method is a promise-based method. Since it is, I can chain in the success method to handle the return cart. Once this happens, the return data is then sent to the myCart variable. After that we chain in the sumCart method, which we will add in a moment. This method is also promise-based so we pass the result of this method to another one, which sets the cart total. Then we add the sumCart method. This takes the cart array as an argument. Here we use a promise again with the reduce method. This will cycle over an array and run a function against each item. The total is the running total of all of the elements in the array. The item represents each individual item, then in the function we add the current items priced to the total. The 0 at the end is the optional starting value for our total. Next the removeCart method takes the id as an argument. Then we use the removeCart method from the service that we set up in the last clip. After we do that, we run the sumCart method again to update the sum. Then we add the purchase and cancel methods. As I mentioned before, we aren't going to use any specific payment processor or process any payments. Instead we will just alert the user with the cart total. Then we will navigate to the shop. The cancel method will just navigate back to the shop. Next, let's go over to the cart.component.html file. The first thing we add to this file is the navigation bar. Then after that we add a label for the shopping cart. Following the label we add a div with an ngFor. We bind this ngFor to the myCart array. Inside that div we have a row. Align-bottom will align the content with the bottom of the row. Then we add some columns to hold our name and our price. After that we add a button to allow us to remove this item from the cart. Of course we are passing in the id as an argument since we know this is how we remove an item from our cart. Then we add another row that will display the total of the cart. First we have a label for the total, then we use the cart total with Angular's built-in currency pipe. USD specifies the currency to use, in this case the U.S. Dollar. True specifies to display the currency symbol. Then the last thing we add to this file is a button group. These buttons will be bound to the purchase and cancel methods from our component. After that we can go over to the cart.component.css file and add a couple rules. The cart class will set a bottom border and apply a little padding between the individual products. The space class just applies some padding. After that, save your work and let's go to the app.routing.ts file. First we import our cart component and after that we will add a route for the cart. Now we won't add the component to our declarations and instead we will go to the app.module.ts file. Now since we didn't export the navbar component, it is only available to components in the declarations of this module. That is why we import the cart here. Then we will add it to our declarations. Then the last thing we need to do is add a link for the cart in the navbar. After that our shopping cart is finished. Save your work, then navigate to the project in a command line. Then run npm start. After the project compiles and starts, navigate to the shop and select a few products. Add them to the cart and choose a few duplicates. After you have selected a few products, navigate to the cart. The items should be listed in your cart and the total should be accurate. Remove a few items. Even if there are duplicates, only one item is removed. Then click checkout and the total should be reflected in the alert. We now have a working shopping cart in our app based off the blog system.
In this module you learned to refactor the blog system into a shopping cart. All this took was a few minor changes to the Firebase paths that we were using. We also built a standalone service that didn't make use of the Firebase API. This simple service will hold the cart contents as an array. We also looked at some different ways that we can manipulate this array. After this module you should understand how to repurpose code for different uses as you build Angular 2 apps. Angular 2 follows a specific design pattern and as you have seen, there are consistent features that you include in every component. As a result, we don't always have to reinvent the wheel as we build our components.
Finishing the App
Hello, everyone. Welcome back to the Angular End-to-End course. We have come a long way with this project. We have added authentication, a blog, and a shopping cart, all built from scratch. You should now be comfortable with the syntax of Angular 2. Now that our app is built, it's time for us to finish it up. First we will talk about testing. Now although everything is working, some developers like to use testing frameworks. The Angular team understands this and includes support to allow you to use a few different testing tools with your app. After that we will build our app for deployment. So far Webpack has just compiled our project on the fly. We haven't actually generated the files that we will use to deploy the project. Now that we are done, we will compile our project and generate the necessary files. We will also talk about what it will take to host this app on the web. You have learned a lot and built a full app. Let's finish it up and learn how to build our app for deployment.
Now as we have built the app we have run it to make sure it is working properly along the way. With the nature of the app it has not been necessary to perform any formal testing. Everything has worked as it should so we haven't had any real bugs to track down. Now along the way I have encountered a few issues, but for the most part some well-placed console logs have been enough to track down problems. For example, the issue with the cart not correctly summing the contents was tracked down by logging the object to the console. Ultimately if you want to perform real tests against your app, there is support from many different tools. Now in this clip we won't perform any tests and instead we will briefly talk about the different tools that we can use to test in Angular 2. The first tool we will talk about is Karma. This is a test runner that is built by the Angular team. Now if we look at a snippet of the karma.conf.js file in the config folder we can see some of the settings that we will use when testing. Now of course Karma works with Webpack and as a result we import the webpack.test file first. This is so we can use the various loaders and plugins offered by Webpack. Loaders allow us to process specific files while plugins provide additional functionality. Now this configuration file also uses Jasmine as the framework to perform our test. Karma also gives us the option of using other frameworks such as Mocha or QUnit. We will talk a bit more about Jasmine in a moment. Then we have some options where we configure how Webpack works with Karma. In the browsers we can select which browser we will use to test. Right now it is using PhantomJS which allows us to perform headless testing. We can also use Chrome, Firefox, Internet Explorer, or Safari. Now in order to use these other browsers we would first need to load a plugin for the respective browser. Then in the app.component.spec.ts file we have the actual file that will test our component. Karma allows us to perform unit tests, for example against a single component or service. While Karma configures and runs the tests, we write our tests using the methods provided by Jasmine. That is what the contents of the spec file is. First we have the imports for TestBed, which is an Angular specific method that we use for testing. Then we import the AppComponent, which is the component that we are going to test. After that we begin our test with the Jasmine describe function. BeforeEach will run before each test to reset the test bed prior to testing. Now the TestBed.configureTestingModule is a method that will create a detached version of the AppComponent in its own test environment. That is why we pass in the AppComponent in the declarations. After that we have our spec, which uses the it function. Should work is the title of the spec and the following function is the actual test. The test can resolve to either true or false with true being a passing test. Now we can't actually run this spec file as it is configured right now as it will cause an error. This is because since it is the unit test it only works with the element being tested. The component uses routing, but the routing module is not imported so we get an error if we try to test. We would need to mock up routing in our spec file to make this work, but since we aren't going to run any tests we will leave the file as is. Now in addition to the other tools we have talked about, we can also use Protractor to perform end-to-end testing. All the tests we have talked about so far have been unit tests. While Karma allows us to perform unit tests against a single component, Protractor allows us to test our app as a whole. Protractor also uses Jasmine so once you get comfortable writing unit tests it should be simple to transition to end-to-end testing. Now that's as far as we will go into testing since our app is ready for production. If you are interested to learn more, make sure you check out the documentation for Angular Testing as well as Karma, Jasmine, and Protractor.
Building the App for Deployment
You now have built a complete Angular 2 app from the ground up. Right now it is little more than a minimum viable product, but it is enough for launch. Of course there are many refinements and improvements you can make, but it's a great start. We have built out functionality for authentication, a blog, and a shopping cart. These individual systems complement each other to provide all of the features that will be expected. We built these components using Firebase as the backend. This has given you the experience to be comfortable using external APIs with your Angular 2 apps. Going forward you should be able to adapt Angular 2 for any of your purposes. Beyond just web apps, Angular 2 is also the backbone of mobile frameworks such as NativeScript and Ionic 2. As a result, after this course you should be prepared to build a variety of apps. As a developer who uses Angular 2 on a regular basis, I hope that you can make use of some of the concepts that I showed you. I look forward to seeing what you can come up with. Thanks for watching the Angular 2 End-to-End course.
After over 16 years in IT, Reggie finally decided to follow a long-term dream of learning to
Released17 Apr 2017