What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
Angular Services
by Brice Wilson
Services are a crucial part of every well-structured Angular application. This course will teach you how to create great services, and how to use Angular's dependency injection system to deliver the right service at the right time.
Start CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Learning Check
Recommended
Course Overview
Course Overview
Hey everybody. My name is Brice Wilson and welcome to my course Angular Services. I'm a server and client side web developer. Angular is a wildly popular client side web framework and for lots of good reasons. It helps you quickly build well-structured, maintainable, and performance oriented web applications. In this course, we're going to cover how to create and configure Angular Services that take full advantage of Angular's dependency injection system to make sure your apps deliver the right service at the right time. Some of the major topics that we'll cover include creating and use services, configuring injectors, working with hierarchical injectors, creating asynchronous services, and finding and using built-in services. By the end of this course, you'll know how to create reusable and maintainable services that are configured to take full advantage of Angular's dependency injection system. Before beginning this course, you should be familiar with the basics of Angular and TypeScript, but you certainly don't need to be an expert. I hope you'll join me on this journey to learn about creating great services in Angular with the Angular Services course at Pluralsight.
The Role Services Play in an Angular Application
Introduction
Hi, this is Brice Wilson. Welcome to Angular Services. Services are an important part of any Angular application and in this course I'll show you how to create them and how they work so that you can take full advantage of them in your own applications. In this first module of the course, I'm going to focus on the roles services play and help you understand when you should use them. We'll get started by defining exactly what a service is before explaining when you should use a service instead of just adding more code to an Angular component you've already created. I'll then give you a brief overview of the topics I'll cover in the course, as well as a quick look at the demo project and how the code in it is structured. Let's get started.
What Is an Angular Service?
It's now time to answer a very important question you likely have, exactly what is an Angular service? You've probably heard of and used components, but how is a service different and when should you consider using one? A service is really just a reusable piece of functionality that can be shared across components. Services should have a single responsibility. You shouldn't just add a single service to your app and dump all of your shared functionality into it. Services are also bits of functionality that are able to be delivered when and where they're needed in the application. This is accomplished in Angular by adding our services to the dependency injection system. I think the hospitality industry is a good analogy for how services should work in an Angular app. If you're staying at a nice hotel, there are multiple services you may take advantage of while you're there. You might order some food from room service, you might have your clothes cleaned the night before an important meeting, or you might call housekeeping to request a fresh set of towels. These are all discrete services with a single responsibility. They're also shared across all of the guests in a hotel in the same way that Angular services are shared across all of the components in an application. If I take this analogy one step further, we can think of the concierge at the hotel as the dependency injection system for the hotel services. We request the services we need from the concierge and she makes sure they're delivered when and where they're needed. Okay, now that you know what services are, the next reasonable question is likely - components or services? Where should you add your code? If you've ever created a component, you know it contains executable code and you could implement just about any feature you need right inside the component. The official Angular style guide on the Angular website provides some guidance for us on this question. Style 05-15 says "do limit logic in a component to only that required for the view. All other logic should be delegated to services." The primary purpose of the code in the component is to control and manipulate the user interface. It should call services to provide functionality not directly related to the UI. Therefore, you should consider creating a service if the necessary functionality is not required by the view or if you need to create some shared logic or business rules that you expect to use for multiple components. Another reason to consider a service is if you need to share small bits of data across components. Services are great for that as well. I'll show you an example of that in the next module.
Course Overview
Let's quickly go over what I'll cover in each module of the course. After this introductory module, I'll start writing some code and show you a couple of different ways to create and use services and the things you need to do to make sure Angular's dependency injection system is aware of them. In the following module, I'll dive deeper into the dependency injection system and show you lots of ways you can configure it to deliver the right service at the right time. After that, I'll show you how to create asynchronous services. They're essential if your app needs to retrieve data from a server, which nearly all apps do. I'll wrap up the course with a look at some common services that are built into the framework and how to use them. Let's now jump into a demo and I'll show you the project I'll be working on throughout the course and how the code is structured.
Demo: Book Tracker Project Overview
In this demo, I'll show you the Book Tracker app I'll be working on throughout the course, as well as how the code in the project is structured. I've named the app Book Tracker and it's an updated version of the Book Logger app I created in my earlier course, Angular JS Services In Depth. That course covered how to create and use services in the 1.x version of Angular. The app is very simple and provides an interface that parents or teachers might use to track the books and reading habits of kids. This is the main screen in the app. It's being presented by an Angular component I named Dashboard. It lists all of the books in our modest library all the young readers were tracking and the book that's been set as the most popular among the readers. I can click the Edit link beside the title of a book to go to a page where I can edit the details about the book or set it as the new most popular book. Not all of the buttons in the app are functional yet, but we'll wire up a few of them throughout the course. Also notice the location bar of my browser. The app is made up of just a few simple components, and I've configured very basic routes to load those components. This is a view of the Edit Book component, and it's displaying the book with id 2. I can go back to the dashboard by clicking the Book Tracker link at the top of the page. Similar functionality exists for the list of readers below. I can click the Edit link to view and update the details for a particular reader. I'm currently capturing just the name of the reader, his or her weekly reading goal in minutes, and then total number of minutes they've spent reading. There are links at the top of the page that load components for creating new books and readers. They just open blank forms ready for us to add the new information. I've styled the app with the default Twitter Bootstrap theme, just to make it look a little nicer, but we won't be doing much with CSS in this course. Okay that's about it for the functionality in the app right now. It's very simple, but I only wanted to make it as complex as necessary in order to explain and demonstrate realistic use cases for Angular services. Let's now jump over to the code and quickly review the structure of the project. I'm going to be using Visual Studio Code as my editor in this course. I think it's great for working on TypeScript and Angular problems, but of course, you're free to use your favorite editor. I want to quickly go over the structure of the project and explain what some of these files do. However, let me point out that I created this project and the stubs for most of the code in it using the Angular command line interface. I think the CLI is a great tool to get new apps up and running quickly while also following best practices as recommended by the Angular team. I'm not using everything in here currently, but I hope it at least looks familiar as a very standard Angular app. The .vscode folder at the top just contains a few Visual Studio code settings I'm using. The dist folder below that is where the files required to run the app will be put when I build the project. They'll be served to the browser from there. The e2e folder is where end to end test would go, but we won't be doing anything with that in this course. The node_modules folder contains all of the npm packages I'm using in the project. The server folder below that is one piece here that you may recognize as nonstandard. The code inside it, combined with the server.js file below, make up a server for the Angular app. It's a pretty basic app I built with Node and Express. The server will deliver the Angular app to the browser, but more importantly it contains an API for the data we'll use in the Angular app. We'll use it later in the course to demonstrate now Angular services can send and receive data to and from a server. Data saved to the server is stored in simple text files, so no database is required. I'm not going to cover the server code in this course, but I will demonstrate how to use the API when discussing writing asynchronous Angular services in a later module. Below the server folder is the source folder. This contains most of the code for the Angular app. Most of the code we'll write is inside the app folder. You can see that I've already got a folder for each of the components in the app. I'll open the add-book folder, and you can see that it contains two files, the add-book.component.ts file is the TypeScript code for the component, and the HTML file above it contains the markup for the component. I created all of the components using the Angular CLI, but I created them without unit tests and without a separate CSS file, since we won't be using them in this course. The models folder currently contains two TypeScript classes I created to make it easier to work with book and reader objects in the app. You can see that the book.ts file just exports a simple class with four properties on it. Similarly, the Reader class contains the properties we're tracking for each reader in the app. As I mentioned earlier, I've implemented a few basic routes in the app. They're configured in the app-routing.module.ts file. The routes for dashboard, addbook, and addreader take no URL parameters and route to their respective components. The editreader and editbook routes take an id parameter on the URL so that the related component can load the appropriate reader or book. Going to the route of the app will redirect you to the dashboard. Okay, the app.module.ts file contains the main module for the app. In it I've imported all of the components I created and added them to the declarations array on the NgModule decorator. A little farther down, I've imported the other Angular modules I need to handle routing and forms management. The last file I want to show you in the app folder is the data.ts file. I'll close the Project Explorer so you can see the contents a little better. This file just contains some hard coded book and reader data that we'll be using until we get the app wired up to the API. I'm exporting constants named allReaders and allBooks, which just contain the arrays of reader and book data you saw earlier in the app. The rest of the files here at the root of the project are just the standard files you get when create a new app with the Angular CLI, and we won't be doing much with them in this course. However, I do want to quickly peek inside the package.json file and show you the command I'll use the start the server and run the app. The package.json file contains references to all of the npm packages I'm using, but it also contains a section for scripts you can use to manage and run the app. I'm going to run the app with the start script you see here. It uses the package I've installed named concurrently to run several commands at once. The kill-others option tells concurrently that if one of the commands stops, then all of them should stop. The two commands I'm running are ng build and node space server.js. The ng build command uses the Angular CLI to build the app and deploy it to the dist folder in the project. I'm running the command with the watch option so that every code change triggers a new build. That will make it easy for me to jump back to my browser and quickly see the effect of the changes. The no-delete-output-path option prevents the output-path from being deleted prior to each build. The node space server.js command starts up the server application I wrote that hosts the Angular app and the API we'll be using later in the course. Let's take a look at how to run this script. I'll close this file and then open the terminal that's built into Visual Studio Code. You could do the same thing from a terminal or console window on Windows, Mac OS or Linux. I had already started the server so I could show you the running app earlier. I'll stop it now by pressing Control+C and then confirming that I would like to terminate the batch job. I'll clear the screen just to make the next command a little more readable. To start the server again, I just type npm, followed by start, which was the name of the script in the package.json file that I want to run. I press Enter, and you can see that command translates to the concurrently command I showed you earlier. The next bit of output lets us know the server has started and is listening on port 3000. The app then gets built and lots of barely readable output scrolls past. Okay, let's go back to the browser and check it out. I'll go up to the location bar and navigate to the route of the app at localhost:3000. Remember that my routing config redirects from the route to the dashboard component. That redirection happens and all of the data appears as expected. Everything looks good.
Summary
I hope you've seen in this module that services are important. They're one of the major building blocks of any well structured Angular application. They allow you to easily implement shared, reusable, discrete pieces of functionality and compliment the view logic you write in your components. In the next module, I'll show you how to create services, add them to a dependency injector, and use them in your components. Stay tuned.
Creating and Using Services
Introduction and Overview
Hi, this is Brice Wilson. Welcome back. In this module I'm going to start writing some code and show you some of the fundamentals for creating and using services in your Angular applications. We'll start by going over the parts of a typical Angular service. Thankfully there aren't many and the ceremony required to turn some code into a service is minimal. We'll then take a look at a couple of different ways you can choose to create a service. I'll show you how to create one manually by adding the necessary files to your project and coding them from scratch, but I'll also show you my preferred approach, which is to use the Angular command line interface. Once we've got our newly created services in place, I'll show you how to inject them into a component, as well as another service, so you can take advantage of the functionality they provide. After seeing how to use the functions on a service, I'll wrap up this module with a look at how you can also use services to share data in your apps. Let's first look at the parts of a service.
Parts of a Service
The code for a service in Angular is really little more than a normal TypeScript class. They can have public and private methods, properties, a constructor, and anything else you might typically add to a class. Angular components are also defined with classes and you quickly recognize a class as a component because it has the component decorator on it. Services are similarly recognizable by the injectable decorator added just above the class definition. Technically speaking, the decorator is only required if the service will have other services injected into it. However, it's considered a best practice to go ahead and include it on all of your services. It futures proofs your code for injecting services later and serves as a quick visual indicator to other developers that this class is a service. We'll talk more about the injectable decorator in the next module of the course. Before your service can be injected into a component, an instance of it must be provided to Angular's dependency injection system. It's provided with something cleverly known as a provider, easy to remember. Let's see what each of these parts looks like in code. Let's look at the code for a very simple data service. It starts with a TypeScript class. I'm exporting the class so it will be available throughout the app, and I'll also followed a convention for service class names by adding the word service to the end of the class name. The next thing I need to add is the injectable decorator. It's a class decorator and therefore goes just above the class declaration. This particular decorator requires an empty pair of parenthesis after it, so don't forget to add those. With that in place, you're free to add the methods and properties you need to implement the functionality the service will provide. Once you've created your service class, you need to provide an instance of that class to an injector so that the injector can make it available to the components that want to use it. I'll talk a lot more about providers in the next course module, but for now, know that services may be provided to injectors at either the module or component level of your app. This is a snippet of a basic route app module. Modules include the NgModule decorator, which is used to provide some metadata about the module. This often includes the components its declaring, other modules its importing, and the component that will bootstrap the app. It may also include the providers property you see here. It's an array of all the services that should be provided to the module's injector. I've put the type of the service class inside the square brackets. This effectively lets Angular's dependency injection system know that when a component, or another service, requests an instance of the DataService class, the injector should create a new instance and then deliver it to the requesting component. Once an injector has delivered an instance of a service, it will cache it and deliver that same instance whenever future requests are made for that type of service. Because an injector only keeps a single instance of any particular service type, services are often referred as singletons. Now that you've see the parts of a service, let's talk more about how services are delivered to components.
Delivering Services to Components
Providers are really like blueprints for how a service should be created. The Angular documentation refers to them as recipes, which I think is also a good description. Most of the time the recipe for providing a new instance of a service is just to use the new keyword to instantiate a new instance of the service class. However, there are other ways to provide service instances, and I'll cover them a little later. Regardless of how it happens to get created, the new service instance is then handed over to one of Angular's dependency injectors. The injector maintains a single instance of each service it's responsible for delivering to components that request them. Components request that services be injected into them using the technique known as constructor injection. You simply write the constructor for your component's class to accept a parameter for the type of service you want to have injected. In this example, the dependency injection system will see that I want a DataService instance. The injector will use the provider blueprint for that type to create one if it doesn't already have one cached, and then deliver it to the constructor parameter in the component. This process is largely transparent to us as the developers of components and services. Angular is really doing the hard work of dependency injection for us. However, I think it's helpful to conceptually understand how this works so that you can carefully tweak the process, should you need to. I'll show you some ways you can do that a little later in the course. Let's take a closer look at how to implement constructor injection with TypeScript. I'm showing you an example here with a component class, but it works the same way if you're injecting one service into another service. You add a constructor to the class and have it accept a parameter with the same type as the service you want injected. Here I want to receive a DataService instance. The injector will supply that instance and automatically pass it into the parameter I've also named dataService. I want to be able to use the dataService instance throughout my class, so I've added the private keyword in front of the parameter declaration. This is known as a parameter property in TypeScript. By adding an accessor modifier in front of the parameter name, TypeScript will automatically declare a class property for the parameter. This is just a nice bit of syntax that saves you having to separately declare a property and assign the parameter to the property inside of the constructor. With it declared as a parameter property, you can add code to the component and reference the injected service, just like any other member of the class. You just add the parameter property name after the this keyword to access members of the service from anywhere in the class. Okay, we've now covered everything required to create and use basic services in Angular. We're now ready to go add some services to the Book Tracker demo app.
Demo: Manually Creating a Logger Service
In this demo, we're going to manually create the files and code necessary to add a logger service to the Book Tracker app. The first thing I want to do is create a new folder in the project to store my services. I'll add it inside the app folder and just name it services. The first service I want to create is a very simple logger service that I can configure with a few simple upgrades to the standard console.log statement. I'll name the file logger.service.ts. You can name the file anything you'd like, but I think it's very helpful to follow established Angular naming standards. We've already seen how the file names for modules in with module.ts and components in with component.ts, so it should feel very natural to name your service files to end with service.ts. Remember that an Angular service is little more than a TypeScript class, so I'll start by defining a class named LoggerService. This class name also follows a convention for service. The name of the class is just a combination of the words I used in the file name, LoggerService. I want to be able to use this class outside of this file, so I'll add the export keyword in front of the class declaration. I don't plan to inject any other services into my LoggerService right now, but I'm still going to go ahead and add the Injectable decorator. It's now ready to receive injections should I need to add them later and the combination of the Injectable decorator and the class name make it abundantly clear to other developers that this class is an Angular service. One more reminder, don't forget to add the parenthesis at the end of the decorator. You can see Visual Studio Code has put a red squiggly line under the decorator, letting me know there's something it doesn't like about it. I'll hover over that and it tells me it can't find the name Injectable. That's because I haven't imported it yet, so I'll add that to the top of the file. I'll import it from the Angular core module, and that gets rid of the red squiggly. I'm now ready to add some methods to my service. I'll come down inside the body of it and use a code snippet to paste in some, so you don't have to watch me type so much. I'm just adding two methods to the service for now. The first one is named log, it takes one string parameter and doesn't return anything. I want it to behave very much like the JavaScript console.log function, except I want to append the current time to the end of the log message. To do that, I just capture the current time and store it in this constant I've named timeString, and then use console.log to log the message passed in with the time appended to it inside a pair of parenthesis. I named the second method error and it's just a thin wrapper around the console.error method. It just pre-pins the word ERROR in all caps to the message passed into the method. Okay, I've got a class with methods on it and I've added the injectable decorator. The last thing I need to do is provide this service to Angular's dependency injection system. I want it available everywhere in my app, so I'm going to provide it to the route app module injector. I'll open up the app.module.ts file and then scroll down to find the providers array in the NgModule metadata. I'll then add LoggerService to the array. That gives me another red squiggly line because I haven't imported the class. So I'll hop back up to the top of the file and add an import statement for it. I'll scroll back down and you can see that that got rid of the error. Now that I've provided the service to an injector, I'm ready to use it. I'll add it to the dashboard component first. The first thing I need to do is import the class so I can use it in this file. I now need to inject it into the components constructor. This is the part that will trigger the dependency injection system to pass in an instance of the service when the dashboard component is being created. I don't currently have any code in this constructor. I'll give it a parameter named loggerService that has the type LoggerService that I imported above. I'll then declare a new private property named _loggerService that I'll use to store the service passed to the constructor. Inside the constructor, I'll assign the parameter to the private property. I could leave the code like this, but as I showed you earlier, TypeScript actually provides a nice shorthand syntax for this known as parameter properties. To use it, I can get rid of the assignment inside the constructor, as well as the private property and just add an access modifier to the constructor parameter. I'll make this one private. I'm now ready to use my loggerService anywhere inside this component. I'll quickly test it out by calling the log function right here in the constructor and passing in a message that the dashboard is being created. Remember that the loggerService parameter is effectively now a property on the Component class, so to reference it in TypeScript, I have to use the this keyword. I'll open up a terminal and build and run the app with the command npm start. Once it gets going, I'll switch over to my browser and go to localhost:3000. The app loads as it should. I'll nail open the browser developer tools, and we can see that my log message does appear in the console and it has the time appended to the end of it. Nice! That was a good test, but I don't really want messages appearing every time I go to the dashboard. So I'll go back to the code and remove that line from the constructor. Okay. Now that you've seen me create a service manually, I want to show you how using the Angular CLI can make the process a little faster and definitely less error prone. I'll do that in the next demo.
Demo: Creating a Service with the Angular CLI
In this demo, I'm going to show you how to use the Angular CLI to create a service and show you how it adds all of the configuration you need to help you start implementing your service faster. Before I actually create a new service with the CLI, I want to quickly show you the official website for it. You can see the URL is cli.angular.io. There's lots of good information here and I've found it's a good place to quickly look up syntax for the different CLI commands. You can install it with npm using the command you see here. To see the syntax for all of the CLI commands, click on the Documentation link here at the top of the page. From there scroll down a little bit and you'll see a list of the commands you can use. I'm going to focus here on how to use the CLI to create a new service, but I definitely encourage you to learn about and use the CLI for all of your Angular development. John Papa has a very good Pluralsight course on the CLI that can help you get started with it, so be sure to check that out. The ng generate command is the one used to create services. The documentation refers to the various things you can create with it as blueprints. I'll click on the service blueprint. It shows you an overview of the syntax to create a service and below that shows you all of the options available for that command. Let's now hop back over to the code and give it a try. I'll open the terminal in Visual Studio Code. I've got the build command running in watch mode here, so I'll open a second terminal where I can run more commands. Okay to create a new service, I can type ng space generate service. There are lots of shortcuts built into the CLI so I can actually shorten generate service with the first letters of those words, g s. I'll then pass to the command the location and name of the service I want to create. I want it to go in the services folder in my project and I want to name it data. This will be the primary data service for the app and responsible for retrieving and saving all the data. I don't want to create any unit tests for my service right now, so I'll add the --spec false option. I want the service to be provided to the injector for the route app module, so I'll also add --module app. I'll press Enter and we can see that the CLI created one new file for me in the services folder named data.service.ts and it updated the app.module.ts file. Let's go take a look at each of those. Inside the services folder, we can see the new file for the data service. Notice that even though all I told the CLI was that the name of the service should be data, it applied the conventional service.ts suffix to the file name containing the service. Inside the file, you can see that it went ahead and stubbed out a basic class, it again applied a naming convention by appending the word service to the name of the class. It also imported and added the injectable decorator for me, so I didn't have to remember to do that. Let's now quickly peek inside the app module. This file was updated to import the new DataService class and then it was added to the provider's array so it would be available to the module's injector. As you saw in the last demo, I could have done all of these things myself, but the CLI is much more reliable. It doesn't forget to add things and never makes typos. I can't make the same claim about my own coding. Okay, let's now add some functionality to the new data service. Eventually I'm going to have the service retrieve data from an actual server, but for now I'm just going to have it manage the hard coded data I have stored in the data.ts file. The data in that file is currently being imported and used in several places, but I'm going to clean that up so it's only used in this service. I'll add an import statement to pull in the allBooks and allReaders arrays. I also want to show you that injecting a service into another service is just like injecting a service into a component, so I'll import the LoggerService I wrote earlier. I'll then use a parameter property on the constructor to inject the LoggerService. The first method I'm going to add to the service is named getAllReaders and it will return an array of Reader objects. Remember that I defined Reader and Book classes in the models folder of the project to represent those entities in the application. I'm getting an error that TypeScript can't find the Reader class. I just need to import it. Visual Studio Code is showing me a little quick fix light bulb over here. I can click on that and it will add the appropriate import statement for me. I'll then just have it return the readers in the allReaders array I imported. I also want to add a getReaderById method that will take a numeric id value as a parameter and return the Reader with that id. The allReaders value I imported at the top of the file is just an array of Reader objects, so I can use the find method that exists on arrays to return the reader with the id that matches the value passed to the method. I also want the service to have similar methods for books. I'll use a code snippet to paste in nearly identical methods named getAllBooks and getBookById that use the allBooks value I imported from the hard coded data file. Just as I had to import the Reader class, the red squiggly lines let me know I need to import the Book class. I'll again use the quick fix light bulb to add that import statement for me. And I'll quickly scroll up and we can see it here, just after the Reader import. Okay, we're not ready to add the new data service to some components in the app. I'll start by using it in the dashboard component. The first thing I need to do is jump up to the top of the file and add an import statement for the service. It's located in app/services/data.service. This component has so far been importing and using the hard coded data directly. I'm still going to use that data temporarily, but access to it is now done through the data service. Therefore, I can get rid of this import statement that pulls the data directly into the component. The next thing I need to do is inject the data service into the component's constructor. I'll add it on a new line after the LoggerService, just to make it a little easier to read. I now need to clean up these properties declared just above the constructor. I'm initializing the properties using data that was in the import statement I just deleted. I now want to assign data to these component properties by making calls to the new data service. I'm going to leave the property declarations, but I'm not going to initialize them here. Instead I'm going to write some code in the ngOnInit method that uses the data service to provide data to those properties. The ngOnInit method is one of the lifecycle hooks available to components. I've declared my intention to use this lifecycle hook by specifying that the DashboardComponent class will implement the OnInit interface. NgOnInit is the only method on that interface. Angular will call it as part of creating the component. It's considered a best practice to initialize the data on a component inside this method. I'll set the allBooks property to the array returned from calling the getAllBooks method on the dataService. I'll also set allReaders to the array returned from calling getAllReaders. For now I'm going to set the mostPopularBook property to the first book in the allBooks array. We'll update that feature to be a little more realistic later on in this module. Okay, that's all we need to do in this file right now. I imported the dataService, injected it into the constructor, and then used it to return some data that was assigned to properties on the component. Let's now go update the edit-book and edit-reader components to use the new service. I'll work on edit-book first. I'll close the sidebar to make the code a little easier to see. Just like I did in the last component, I first need to import the data service. I no longer need the hard coded book data, so I'll remove this import. I now need to inject the DataService into the constructor so I can use it throughout the class. The only place I need to use it right now is inside the ngOnInit method. I'm going to set the selectedBook property by assigning it the value returned from a call to the getBookById method on the data service. I'm passing it the id that I parsed from the route. The URL includes a route parameter that's the id of the book to be edited. That's the only update I need to make for this component. Let's now go do nearly the same thing in the edit-reader.component. I'll import the DataService at the top of the file, just like I did before. I'll also remove the import of the hard coded reader data. Next, I need to inject the DataService into the component's constructor. I'll then go into the ngOnInit method and call the getReaderById on the service and assign the result to the selected reader property. Okay, I've now updated three components to use the new DataService. Let's hop back over to my browser and make sure everything still works. I'll refresh the page. I've still got data here on the dashboard, that's good. I'll click on one of the Edit Book links and that's still working. I'll do the same thing with one of the Edit Reader links, it looks good too. Okay we're making progress. You've now seen how to add a new service using the Angular CLI and we have a data service we can use for all data related calls in the demo app. We'll update the code later in the course to actually make calls to a server. In the next clip, I'll talk about how we can use services to share data between components.
Sharing Data with Services
Most of the functionality provided by services is usually contained in the functions exposed by the service. However, services can also have properties. If you combine those properties with the fact that services are singletons inside an injector, you can employ a simple strategy for using services to share data between components. Let's imagine the injector associated with the root application module. I can provide an instance of may DataService to that injector, and it will be available to any component in my application. Remember that the service is a singleton, so if the service has a property on it, like a favoriteBook property, then any component that has the service injected into it can access the value stored in that property. All components that receive that service instance will have access to the same value in the property. Any changes made to the property will also be reflected in those components. Let's go take advantage of this data sharing capability to properly implement the feature that stores and presents the most popular book in the Book Tracker app.
Demo: Sharing Data with Service
In this demo I'm going to modify the data service to store and share the current favorite book between components in the app. Let's first peek inside the code for the dashboard component. You may remember that the dashboard displays the current most popular book. However, as you can see here, that property is just hard coded to the first book in the array of allBooks. We're going to use the data service I created in the last demo to make this a little more functional. Inside the DataService, I'm going to add a property to store the mostPopularBook. Many people think of services as only containing a bunch of functions, but they can also contain properties. I'm going to initialize the new property to the first book in the allBooks array. Ideally this data would be retrieved from the server, but we aren't using a server yet, so I'm just going to hard code the initial value here. I'm now going to write a new function on the service named setMostPopularBook and it will accept a Book object as a parameter. The function will simply set the mostPopularBook property to the book passed to the function. Those are the only changes I need to make to the service. I'll now open the code for the edit-book.component. I'll close the sidebar to give myself a little more room. You can see here that I've already got a function on the component named setMostPopular. This function is already bound to the click event on the Set As Most Popular button in the components template. Clicking the button calls this function. As you can see, the function currently just logs a warning to the console letting us know this feature is not yet implemented. I'm going to delete that line and replace it with a call to the setMostPopularBook function I just wrote on the DataService. I'll pass it the selectedBook property on this component. I'd also like to log that there is a new most popular book, so I'll just up to the top of this file and import the LoggerService. Before I can use it, I need to inject it into the constructor. I'll now come back down to the setMostPopular method and add a simple log statement to output the title of the new most popular book. Okay, I now have the code in place to assign the most popular book to a property on the DataService. The last think I need to do is modify the dashboard component to read that property. I'll go back to the code for that component. I'm going to change the line in the ngOnInit method to set the mostPopularBook property. Instead of the hard coded first book, I'm going to have it read the property I set on the DataService. Okay, let's go back to the browser and give it a try. I'll refresh the app and then open the Developer tools so we can see the console. When the app first loads, it still defaults to the first book in the array, which is currently Goodnight Moon. I'll click the Edit link beside Curious George and then click the Set As Most Popular button. We can see in the console, the log statement letting us know that the property on the service was changed. I'll now go back to the dashboard and, as expected, Curious George is now the most popular book. The dashboard is bound to the same property on the same DataService instance that we set by clicking the Set Most Popular button on the Edit Book component. This is a handy feature of services, so keep it in mind the next time you've got some data you'd like to share across multiple components.
Summary
Before you can dive deep into any topic, you need to understand the basics. In this module, we went over some Angular service basics. You've seen that services are really just TypeScript classes. Instances of those are provided to Angular's dependency injectors and then those injectors deliver them to components when they're needed. We'll go much deeper into this process in the next module. Angular is a very large framework and really shines when used to build medium to large sized applications. When working on large frameworks and applications, consistency is important. Make your code easier to read for your colleagues, and even a future version of yourself, by sticking with naming conventions. I showed you the naming conventions for services, but this applies to components, modules, and everything else you create for your app. One of the best ways to maintain that consistency is to use the Angular CLI to help stub out pieces of your app for you. It helps you stick to accepted conventions and plugs in some of the bits of syntax that are easy to forget if you try to do it all from scratch. Once we had fully functional services in place, I showed you how you can use services to share data between components. I don't pretend this is the most important feature of services, but it is handy and I bet you'll quickly find places in your apps to take advantage of it. In the next module, we're going to dive deeper into the dependency injection system, and I'll show you how you can exert more control over providers and injectors when working with services. Stayed tuned.
Understanding and Configuring Dependency Injection
Introduction and Overview
Hi, this is Brice Wilson. In this module we're going to dive deeper into Angular's dependency injection system and I'll show you how you can exert more control over exactly how and where your services are made available to other parts of your application. Angular's dependency injection system is large, complex, and effects every application built with the framework. It's capable of injecting lots of different things, but in this module I'm going to focus on how to use it with services. I'll start off this module with a general overview of dependency injection and why it's a useful pattern to follow when creating software. We'll then take a closer look at a couple of features I introduced in the last module. I'll show you how to use providers to precisely control how services are created and delivered to injectors. We'll then look at those injectors so you can understand how they map to components. It's important to understand the injector hierarchy so you can choose where in the hierarchy to provide your services. There's lots of cover, let's get started.
What Is Dependency Injection?
I want to give you a very quick explanation of dependency injection to help you appreciate why frameworks like Angular go to the trouble to implement something that might seem like a lot of added complexity. Most software is broken down into lots of interrelated pieces that are assembled into applications. Let's look at a very simple example. Here I've got the shell of the DashboardComponent class from the Book Tracker app. You've already seen that the DashboardComponent uses the data service I created. The data service is a dependency. The DashboardComponent depends on the data service to help perform the component's work. I could make available that dependency by declaring a new property to store a DataService instance and then inside the class creating a new instance of it and assigning it to the property. Here I'm using the new keyword to create a DataService. The result is that my DashboardComponent is now very tightly coupled to the type of DataService I'm creating here. This makes it very difficult for me to ever use the DashboardComponent with any other type of DataService. Let's now look at an example that retains the dependency, but reduces the tight coupling to the DataService class. The DashboardComponent still needs a DataService to do its work, but instead of creating it inside the class, I'll pass an instance of the class to a parameter declared on the component's constructor. The dependency will now simply be passed into the DataService parameter. I've used a type annotation to make it clear that the type we're taking a dependency on is the DataService type. I haven't removed the dependency on the DataService, but I have made the code more flexible by now allowing any appropriately typed value to be passed to the component's constructor and used as the DataService instance the component depends on. If you've done dependency injection in other object oriented languages, this code may look a little strange to you because I've declared the type of the constructor parameter to be the DataService class, rather than an interface that defines the contract for the instance I'll ultimately pass in. That's possible because TypeScript uses a structural type system. This means that the DataService class can behave much like an interface in a language like C# or Java. You're only required to pass in a value that has the same shape, the same properties and methods, as the declared type. I can pass in any value that is structured like a DataService instance even if it's not actually an instance of the class I created with the new keyword. You can learn a lot more about TypeScript's structural type system in my Pluralsight course titled TypeScript In Depth. You've now seen how we can inject dependencies into a class, rather than declaring them inside the class, but you might reasonably wonder why is this is important? As I mentioned earlier, passing the dependencies in means our code is more loosely coupled. Loosely coupled code is also more flexible code. It becomes much easier for me to swap out one implementation of a particular dependency for another implementation. There are a number of scenarios in which you might want to do something like that, but probably the most common is when writing unit tests. Code written to support dependency injection is easier to test because you can more easily isolate the code being tested by passing in fake implementations of the dependencies. The fakes have the same structure as the real objects, but are coded to behave in a very specific way to support the test being performed. Let's now move on to looking specifically at Angular's dependency injection system.
Provider Tokens and Recipes
Providers are a very important part of the dependency injection system. They define how the system should create the services that will ultimately be injected into components and other services. I found this line in the Angular documentation that I think is very helpful when trying to understand providers. A provider is a recipe for delivering a service associated with a token. The two most important words here are token and recipe. You can also think of them as key and a value if you prefer more traditional data structure terms. I'm going to stick with token and recipe. Let's look at some code to demonstrate what these terms really mean. We've already seen that one way you can specify a provider is to add the service type to the providers array in the NgModule metadata for your AppModule. Here I'm providing the DataService to the module by just adding the name of the DataService class to the providers array. The class name in this case is serving as the token. When we want to inject the service elsewhere in the app, we do it by declaring a constructor parameter that will receive the injected value. The type of that parameter is also a token. Angular will use this token to look up a recipe specified with the first token in the providers array. Let's now see how that recipe is specified. Let's imagine the same AppModule, but this time I'm going to provide two services to it and show you how the token and recipe are specified for each. The first will be the same DataService you've already seen. The syntax you see here is really a shorthand syntax that declares the DataService class as the token and tells Angular that the recipe for creating a service associated with this token is to just use the new keyword to create an instance of the class. I can provide the LoggerService the same way, using this more verbose syntax. Rather than just adding another class name to the providers array, I've added an object literal to the array. That object has two properties. The provide property is used to specify the token for this service and the useClass property is used to specify the recipe. The value I give to the useClass property is the class Angular will create an instance of to associate with the token. In this example, Angular will create a new instance of the LoggerService whenever it sees the LoggerService token used as the type for a constructor parameter. When the class and the token are the same, as they are here, it's common to use the shorthand syntax above and just add the class name to the providers array. This syntax is equivalent to the more verbose version below. DataService will be the token, as well as the class to be instantiated when that token is used in an injection. The useClass property on an object literal isn't the only recipe you can specify for a service. In the next demo, I'll show you several other options for telling Angular how to create a service to associate with a token.
Demo: Multiple Ways to Provide Services
In this demo, I'm going to show you multiple ways to provide services to your app using different recipes. I'm going to open the app module where I'm currently providing the logger and data services. You can see here that they're the only two services currently in the providers array. I'm going to format this so they're each on their own line and then make a few changes. Okay, the way I'm currently providing these services, the name of the service class is effectively the token used to identify instances as well as the recipe for how an instance should be created. They'll be created with the new keyword, much like you would create any new instance of a class. I'm going to change the provider for the LoggerService to make that recipe very explicit. I'll change this item in the array to be an object literal. The first property I'll add to it is the provide property, which will specify the token that will be used by components requesting instances in their constructors. I'm going to leave it as LoggerService. After that, I'll type a comma and start typing the word use, and I immediately get some code completion support showing me properties for some of the different recipes I can choose to use. UseClass, useExisting, useFactory, and useValue. I'm going to select useClass and give it the value LoggerService. This is just the more verbose form of what I already had. When the injection system sees a component requesting a LoggerService in its constructor, it will be passed one that was created by instantiating a new instance of the LoggerService class. I know want to add a new service to the app so I can demonstrate a few other provider recipes. I'll open the terminal in Visual Studio Code and use the Angular CLI to create a new service named PlainLogger. I'll create it in the services folder with the command ng generate service and name it PlainLogger. I'll add the --spec false option to indicate I don't want any unit tests right now. Notice that I got a warning that my service was generated, but not provided, and that I'll have to provide it before I can use it. I'll do that in just a minute. I first want to open the new file in the services folder and add some code to it. I'm going to declare that the PlainLogger service should implement the LoggerService class. An interesting feature of TypeScript is that you can use the implements keyword with classes and interfaces. This effectively means that the PlainLogger service class must have all of the same methods and properties as the LoggerService class. I now need to import the LoggerService class at the top of the file. I've now got an error message that says the class incorrectly implements LoggerService. I can fix that using the quick fix light bulb over here on the left. I'll click Implement interface LoggerService and it stubs out the required functions for me. This class now has the same methods as the LoggerService, but I'm going to implement them a little differently. I've named it PlainLogger service, so I'm going to make the log messages as plain as possible, simple console.log statements with nothing added to them. Okay, I'll now go back to the app.module and provide the new service. I want to show you that the provider token isn't required to the be same as the class instantiated by the recipe. I'm going to change the provider for the LoggerService to use the PlainLoggerService class and then let Visual Studio Code add an import statement for me. I don't have to change any other code. All of the components that are currently requesting a LoggerService in their constructor will now get a PlainLoggerService instead. This will work because both classes have the same interface. I'm going to quickly add a new log statement to the dashboard.component. Notice that the constructor is still requesting a LoggerService instance. I'll just use the service that is passed to it to log a simple message in the constructor. I'll now open the terminal and start the app with the command npm start. Once it's up and running, I'll jump over to my browser and go to localhost:3000. Everything seems to load fine. I'll open the browser developer tools and we can see I got my log message using the PlainLoggerService. This one doesn't have the time appended to it, so I know it's not using the original LoggerService. Let's now go back to the app.module and look at a couple of other options for providing services. Let's suppose that the PlainLoggerService was built as a replacement for the LoggerService. I could provide it using the shorthand syntax you've already seen and then use the useExisting property of the LoggerService provider to make sure that the same PlainLoggerService I provided above is also used anywhere a LoggerService instance is requested. You've now seen the useClass and useExisting recipes. I'll comment out these logger providers and show you yet another option. I'll again set the provide property to LoggerService, but this time I'll use the useValue property to specify the recipe. This recipe works a little differently. It doesn't use the new keyword to create a new instance of a class, it uses whatever value I give it right here in the provider. The API for the LoggerService defines two methods, so I'm going to assign an object literal to the useValue property that includes those two methods. I'll assign an arrow function to the log method that just pre-pins the word MESSAGE to the string passed to it. For the error method, I'll call console.error and pre-pin the message with the word PROBLEM. The object literal will now be passed to all of the constructors requesting a LoggerService instance. To verify that, I'll hop back over to my browser, and refresh the app. You can see that the message added in the dashboard components constructors has the word MESSAGE added to it, so we know it's using the object literal I added to the provider. Okay, the last type of recipe I want to demonstrate is a service factory function. I'll use it to provide the data service. I'm going to create a new file in the services folder to hold the factory function. I'll name it data.service.factory.ts. I want to use the LoggerService inside the function, so I'll first import it. I'll then import the DataService itself. The factory function is just a function that will be executed to create a new DataService instance. By writing a custom function rather than just letting Angular use the new keyword to instantiate an instance, I have more control over how it gets created. Here I've exported a function called dataServiceFactory that will take a LoggerService instance as a parameter. I'll use a code snippet to paste in the body of the function. The first line of code creates a new DataService instance with the new keyword and passes it the LoggerService instance that was passed to the factory function. So far this is exactly what Angular would do if I used the useClass recipe we've already seen. However, if I wanted to further configure the service, maybe by setting some properties on it, then I could do that here. I'm not going to set any now, but I will add a log statement so we know the service was in fact created by the new function. I'll then just return the DataService instance. This will be the instance delivered to components that request the service. I'll go back to the app.module and configure it to provide the DataService with the new factory function. I'll convert the existing provider to use the object literal notation that gives me more control. I'll set the provide property to DataService and then I'll use the useFactory property to specify the dataServiceFactory function I just wrote. Because that function takes a parameter, I also need to set another property on the object named deps, short for dependencies. It's an array of the dependencies that need to be passed to the factory function. This one only needs a LoggerService instance. The red squiggly line here is just because it can't find the factory function. I'll use the quick fix light bulb to import it. Okay, I'm ready to refresh the app and make sure this new provider works. I've still got data on the screen, so the DataService is obviously working. We can also see the message in the console that the service was created with a factory function. It all seems to be working. You've now seen several ways to provide services to the dependency injection system that give you much more control than having Angular simply create a new instance with the new keyword. For my purposes in the Book Tracker app, letting Angular create the instance is perfectly adequate, so I'm going to comment out all of this code so you can still refer to it in the course download materials, but I'm going to change the providers array back to the simpler version I started the demo with.
Injectors and Metadata
Now that you've seen how to provide services to an injector, let's talk about the injectors themselves. Like providers, they also play several important roles in the dependency injection system. You've already seen that they deliver services when and where they're needed via constructor injection. They also maintain a single instance of each service provided to them. By doing this, the same instance of a service can be delivered each time it's requested from the same injector. We saw in the last module how this can be used to share data across components. Injectors determine what to inject based on metadata that's emitted when your TypeScript code is compiled. Once they determine what's being requested, they can delegate the injection to a parent injector if now service has been provided to it with a token that matches the one requested. We've talked about these first two points already, so let's look closer at the last two. Injectors use metadata output by the TypeScript compiler to determine what service types are being requested by components. The metadata outputs information about the number and type of parameters declared on methods. The dependency injection system can then look at the constructor parameter metadata to figure out what types to inject. All of this is enabled with a special TypeScript compiler option named emitDecoratorMetadata that's usually configured in a ts config.json file. In this example, the value of that option is set to true. If it's not set to true, Angular can't find out what to inject and your app isn't going to work. If you create your application with the Angular CLI, this option will be turned on for you by default. Notice that the name of the compiler option is emitDecoratorMetadata. Metadata will only emitted for a service or component if the class has a decorator on it. It doesn't matter which decorator, any decorator will cause the metadata to be emitted. This is why we add the injectable decorator to services, but we don't have to add it to components. Components already have the component decorator. That's enough to have the compiler output parameter metadata about the class, so that the injection system can figure out what types to inject into the component constructor.
Hierarchical Injectors
Angular applications have multiple injectors, and they're organized hierarchically to mirror the component structure in the app. Services can be provided to modules or components. If you provide a service to a module by adding it to the providers array inside the ngModuleDecorator, the service will be added to the Root Injector and available to the entire application. This is how I've provided all of the services in the demo app so far in the course. In this scenario, if I were to inject a service into the constructor of a nested component somewhere in the app. The dependency injection system would first try to locate an instance of that service in the injector associated with the component receiving the injection. It won't find it because the service was not provided at that level in the hierarchy. It will then move up a level and look for it in the parent component. It won't find it there either, and will finally move up to the root injector, which does have an instance of that service and will deliver it to the component. Let's look at another example. Instead of provider the LoggerService to a module, suppose I provided it to a component instead. If I attempted to inject the service into another component at the same level in the hierarchy, the injection system will try to locate the service in that component's injector first. It won't find it there and will then move up to the root injector. It also won't find it there since it was provided below that in the hierarchy. This will generate an error in your application, letting you know that Angular couldn't find a provider for the service. Another scenario to be aware of is providing the same service to two different injectors. This is permissible, but it will create two separate instances of the service. Which one is delivered to a constructor will depend on where the requesting component falls in the hierarchy relative to the two instances. In the next clip, I'll demonstrate some of these features of Angular's hierarchical injectors.
Demo: Hierarchical Injectors
In this demo, I'll show you how providing services to injectors at different points in the hierarchy of injectors affects where you can use them. To demonstrate this, I'm going to use the feature in the Book Tracker app that lets a user set the most popular book. The most popular book is currently stored on a property of the DataService, and as you can see here, the DataService is being provided in the AppModule. Any service provided inside a module is added to the application's route injector and may be used anywhere in the app. I'll quickly open the data.service, and you can see that the setMostPopularBook function I wrote in the last module just assign the book passed as a parameter to the mostPopularBook property on the service. Let's hop back over to the browser and review how this actually works. I'll open the developer tools so we can see the console. When the app first loads, it defaults the most popular book to the first book in the list, which is Goodnight Moon. I'll click the Edit link beside Green Eggs and Ham and then click the Set As Most Popular button. I get a message in the console letting me know that there is a new most popular book. I'll go back to the dashboard, and you can see that change shows up. This works because the data service is only being provided to the root injector. That is the top of the hierarchy of injectors, so every component in the app is using the same DataService instance and reading the same value from the MostPopularBook property on that service. Let's now go back to the code and make a change to that. I'm going to modify the edit-book.component and add a provider's array to the component decorator. I'll add the DataService to that array. What this means is that I will now have two instances of the DataService. I still have one being provided to the root injector, but this one is being provided further down in the injector hierarchy. When the constructor for this component requests DataService instance here, it will be given the first instance found in the hierarchy of injectors, which will be the one I just provided inside this component. The dashboard component will still be using the DataService provided to the root injector. Let's go see how this affects the app. I'll refresh it, which resets the most popular book to Goodnight Moon. I'll now edit Where The Wild Things Are and set it as the most popular book. You can see in the console that the change was made, however, this component is using a different instance of the DataService in the dashboard, so when I return to the dashboard, you can see that it still shows Goodnight Moon. This is why it's important to not only provide your services at the correct location in the hierarchy, but to also understand how many instances you're providing throughout that hierarchy. I'll go back to the code and remove the providers array from the EditBookComponent so that everything works as expected again.
Deciding Where to Provide Services
Now that you've seen how you can provide your services to injectors at different locations in the component hierarchy, let's talk about how you decide where to provide them. The first option is probably the most useful and common, and that's to provide the service to an NgModule if the service is needed throughout the application. Services provided to an NgModule will be added to the root injector and available everywhere. Don't be tempted to provide your services to the default AppComponent rather than the AppModule. If you're using lazy loaded modules, you won't be able to import services provided to the AppComponent. If you have services that really only make sense in the context of a particular component, then provide the service directly to that component rather than risk it being used incorrectly elsewhere in the app. Finally, consider creating a core module to provide all of the services that need to be available to the entire application. In the next two demos, I'll show you how to create a service specific to a particular application feature and how to create a core module that can provide all of the application wide services.
Demo: Providing Feature Services
In this demo I'll develop a feature service and provide it directly to the injector associated with the component where it's needed. I want to create a new service that will assign a badge to a reader based on how many minutes they've read. I'm going to use the Angular CLI to create the service, so I'll open a new terminal in Visual Studio Code and type the command ng generate service. I'll put the new service in the services folder and name it badge. I don't want unit tests for it right now, so I'll add the --spec false option. The service was created, but I got a warning telling me that the service wasn't provided. I'll provide it to an injector in just a minute. I'll first open the new service from the services folder. I'll add a method to it named getReaderBadge that takes the number of minutes read as a parameter and returns a badge as a string. I'll use a code snippet to paste in the body of the function. This is very simple, if the number of minutes read is greater than 5000, then it will return the Book Worm badge, greater than 2500 will return the Page Turner badge, and anything below that will return Getting Started. Right now we're going to imagine that this service is only needed inside the edit-reader.component. Since it's not needed anywhere else, I'm going to provide it only where it's needed. I'll open the component and import the new service. I'll then add a new providers array to the component decorator and add the BadgeService to the array. I now need to update the constructor to inject an instance of the BadgeService. I want to easily be able to display the new badge on the edit-reader screen, so I'll create a new property to store it in the component. I'll name the property currentBadge. I'll initialize the property inside the ngOnInit method by calling the getReaderBadge function on the BadgeService and passing it the totalMinutesRead by the selectedReader. In order to display the new property, I need to edit the template for the component. I'll open the edit-reader.component.html file. I'll make some space after the div that displays the total minutes read and use another code snippet to paste in some HTML. This is just another div, formatted like all the rest on this page. The label on the screen will say Current Badge. I'll use interpolation to display the property I created on the component. Back in the browser, I'll refresh the app and then as I click on the links to edit readers, you can see the new badge displayed in the edit-reader.component. Book Worm for Marie and if I go back, Page Turner for Daniel. That's all working great. I now want to demonstrate that the new badge service is currently only available to the edit-reader.component. I'll go back to the code and open the add-reader.component. This component exists at the same level in the component hierarchy as the edit-reader.component. I'll import the badgeService and then inject it into the constructor. I'll now go back to the browser and refresh the app. I'll open the console so we can see the output that's generated. If I click on the Add Reader link at the top of the page, you can see that I get a long, nasty looking error in the console. I'll scroll up to the top of it and you can see the helpful part of the message: No provider for BadgeService. I provided the BadgeService to the edit-reader.component, but not to the add-reader.component. Because they exist at the same level of the component hierarchy, no instance of the service was found as Angular searched up the hierarchy from the injector associated with the add-reader.component. Keep this in mind if you get errors telling you there's no provider for a service. It usually means you either forgot to provide it entirely, or you didn't provide it in the correct place. I'll go back to the code and remove the BadgeService from the add-reader.component. In the next clip, I'll show you how to combine application wide services into a core module.
Demo: Creating a Core Module
In this demo, I'll consolidate the application wide services in the demo app into a core module and show you how to prevent it from being loaded more than once. The official Angular style guide recommends creating a module called core where you can provide all of your application wide services. This can be a great way to clean up your main app module and keep your services better organized. I'll close the side bar and then open a new terminal. You've seen me use the Angular CLI to create new services and now I'm going to use it to create a new module. The command I'll use is ng generate module. I want to name the module core. I want it imported in the AppModule, so I'll add the --module option and pass it app as the module name. I don't want unit test or routing for this module, so I'll set the spec and routing options to false. You can see here the new core folder that was added to the project. It currently contains one file named core.module.ts. I want to keep all of the services that are used throughout the app together in this folder, so I'll quickly copy the data.service.factory, data.service, logger.service, and plain-logger.service files into the new folder. The badge.service is only provided to a single component, so I'll leave it where it is for now. I now need to move some things from the app.module into the new core module. I'll cut the import statements for the services I moved and then open the core.module file and paste them in there. They're not in the services folder anymore, so I'll adjust the paths to the files. Keep in mind that I've chosen to move the services into the same folder as the module where they'll be provided, but this isn't required. I could have left them in the services folder. Choose whatever file organization strategy makes the most sense to you. I need to add a providers array to the NgModuleDecorator that will provide these services to the root injector. I could type it all in again, but everything I need is over in the app.module, so I'll just go copy it. I'll also copy the providers I commented out earlier so they stay in the course download materials. I'll go back to the core.module and then paste the providers array just after the declarations array. That's all I need to do to the core.module. Back in the app.module, you can see that it's much smaller, cleaner, and easier to read now. All of those services have been moved out and replaced with the importing of the new CoreModule here. Since I changed the location of the service files, I need to quickly open up the files where they're being imported and fix the path to point to their new location. I'll start with the dashboard.component. I need to change the paths to look for the logger and data services in the core folder. The edit-book.component needs the same two changes made to it. Next up is edit-reader. I just need to fix the path to the DataService in it. Okay, that's all the cleanup work I need to do. I now need to code a bit of a safety net to use with the new core.module. If you're using lazy loaded modules in your app, it's possible for a module to be imported more than once. Importing our new core.module more than once would be a problem since it would lead to us having more than one instance of the services. I need to write a bit of code that will guard against that. The code I'm about to show you was taken directly from the Angular style guide that recommends the use of a core module. I'll first add a new file to the core folder named module-import-guard.ts. I'll use a code snippet to paste in the function. It's called throwIfAlreadyLoaded and will be passed two parameters. The first represents the parent module, if there is one, and the second is the name of the module being imported. If the parent module is not null, it throws an error and reports that the module has already been loaded. I now need to use the function in the core.module. I'll open it back up and use another code snippet to paste in a constructor. This code uses the Optional and SkipSelf decorators to make sure it's properly checking for a separate input of the core.module. SkipSelf tells the injection system to begin looking for an existing instance of the module in the parent injector. Optional instructs the injector to pass in null if no other instance is found. I'll use the little quick fix light bulb to import both of those decorators, as well as the throwIfAlreadyLoaded function. With the check in place, I'm ready to hop back over to my browser and make sure everything still works. I'll refresh the app and everything still loads as it should and the services are now better organized into a core module.
Summary
You can't build Angular applications without the dependency injection system, so it's important to understand how it works. Providers and injectors are the two biggest pieces of that system. We covered the role each play, but also went deeper and saw several ways to define the recipes used to create providers, as well as how you can use your understanding of Angular's hierarchical injectors to choose the best place to provide your services. Your new detailed understanding of providers and injectors will help you make sure your apps always deliver exactly the right service at exactly the right place. In the next module, I'll cover asynchronous services and I'll modify the demo app to retrieve data from a web server using Angular's HTTP client.
Creating Asynchronous Services
Introduction and Overview
Hey everybody. In this module, I'm going to cover creating asynchronous Angular services. Nothing makes users grumpier than a slow or unresponsive web application. Performing network calls and other long running tasks asynchronously will keep your apps responsive and your users happy. I'll start this module by discussing what asynchronous services are and why you should create them. I'll then move on to the two primary constructs you'll use when creating asynchronous services in Angular, observables and promises. I'll cover the syntax for each, as well as how they're similar and different. Both of them use callback functions to process the result of some asynchronous work, but I'll also show you a more linear technique using the asynch and await keywords. Let's first cover what asynchronous services are.
What Are Asynchronous Services?
So what are asynchronous services? It may sound obvious, but they're services with methods that execute asynchronously. It's really individual methods that are written to execute synchronously or asynchronously, and a single service can have examples of both. Services with asynchronous methods are actually very much like any other service. They're provided to injectors just like any other service, and they're injected into components, like any other service. The most obviously difference between synchronous methods and asynchronous methods are the different return types. Most will either return an observable or a promise. We'll talk more about both of these, but observables are a type included with a library named RxJS and promises were in addition to the ES2015 of JavaScript that can also be used in TypeScript. Regardless of which construct you use, callers of your asynchronous service methods will need to write some additional code to process the result of the work being performed asynchronously. I'll show you some examples of processing observables and promises a little later in the module. I want to talk briefly about why writing asynchronous code even matters. I use this same slide in my advanced TypeScript course, but I think it's applicable here as well, and I'll show you a tweaked version of it to make it more particular to asynchronous Angular code. Let's first consider the synchronous execution of some code. Let's suppose your client side code is running fast, responding to user events, and doing lots of cool work. That's all great until some slow running task comes along. If that code is executing synchronously, then the rest of your application is going to be blocked. To your user, it will appear that the application is frozen or locked up. We've all seen applications that do this and it is not a good user experience. Once the long running task is done, everything returns to normal. The UI becomes responsive again and you can resume doing cool work. The asynchronous scenario is much nicer. You start off doing the same cool work, but when the slow running task comes along, it doesn't interrupt the cool work and the responsiveness of the app. When the task is complete, it executes a callback function and everything continues running smoothly. This is much more desirable than the synchronous scenario above. Let's see how this applies to an Angular app and why asynchronous services matter. We'll again look at the synchronous case first, but instead of some generically cool work, let's imagine the more realistic scenario in which an Angular component is responding to user events. If that component makes a call to a synchronous Angular service to perform some long running task, it will block the component and prevent it from being as responsive as it should be. When the service is done with its work, the component will again be free to quickly respond to user events. Of course, this problem can be avoided with asynchronous services. The component calls the asynchronous service to perform the long running task, which leaves the component free to continue responding to user input. When the service is done, the results can be processed and seamlessly integrated back into the component. Observables and promises facilitate building asynchronous services. Let's look at observables first.
Observables
Network communication can be slow and is a perfect example of a task you should perform asynchronously. Just about every Angular application that needs to retrieve data over a network will do so with observables. Observables are not natively a part of JavaScript or TypeScript. They're actually part of a standalone library named RxJS. Angular depends heavily on RxJS, so it gets installed as a dependency when you install Angular. Observables play a big part in any HTTP communication you perform in your Angular apps. They're returned by methods on the built in HttpClient, so that requests for data and other server resources can be done asynchronously. Although HTTP communication is where you're most likely to encounter observables in Angular, you can make use of them in lots of other ways as well. Observables, and RxJS in general, have a very large API and I'm only going to demonstrate a small portion of it related to creating asynchronous services. If you want to learn more about RxJS, I recommend taking a look at Scott Allen's course titled Getting Started With Reactive Programming Using RxJS. Next, I'll show you how to return an observable from a simple HTTP request. As I mentioned earlier, the primary difference in a synchronous service method and an asynchronous service method are the method return types and how those return types are processed. Let's look at a bit of code that returns an observable. The code I'm going to show you will make an HTTP request for data, so I'll need to add a couple of new imports we haven't seen yet. The first statement here imports the built-in HttpClient. The second statement imports the observable type from the RxJS library. Let's now imagine that I've injected the HttpClient into a service and want to use it to retrieve some data. The get method it exposes takes a type parameter that specifies the type of the data that will be returned from the server. In this case, I'll be expecting a Reader object. The get method returns an observable with the same type parameter passed to the method. Since my getReaderById method here just returns whatever the get method returns, I've specified the return type of the method as an observable with the Reader type parameter. The get method will immediately create and return this observable, which means this getReaderById method will immediately return it as well, not waiting on the actual network call to happen. The get method will make that call asynchronously and we'll get the results by processing the returned observable. Let's now see how to do that. Let's imagine I'm writing some code in the edit-reader.component that's going to call the getReaderById method I just showed you on the data service. Inside the ngOnInit method, I'll retrieve the ID passed on the URL and assign it to a variable named readerID. In the synchronous version of the method, which I've commented out, I call the getReaderById method on the service and wait for it to return an actual reader object, which is then assigned to the selectedReader property on the component. That was okay when my data was hard coded inside my Angular app, but now I want to retrieve it from a server. I'll call the same method, but since it no longer returns a reader, I'm not going to assign the return value to a variable. It now returns an observable and I need to use that observable to get the data once it's returned from the server. I do that by calling a method on the observable named subscribe. Since the getReaderById method returns an observable, I can just chain a call to the value returned from the method. The subscribe method accepts three functions as parameters. The functions are callback functions and will automatically be called depending on what happens with the asynchronous work being performed. I'm going to pass them as arrow functions. The first parameter is the function that will execute for each piece of data returned. In the case of an HTTP request, the HTTP response will be the only data returned. That data is passed as a parameter to the callback function and I'm assigning it to the selectedReader property. This is what I did synchronously with the value returned directly from the function in the example I commented out above. The second function passed to subscribe will get called if an error occurs. It's passed the error as a parameter and I'm just logging it here. The third function will execute at the completion of the observable sequence. The sequence for an HTTP request is the single response, so this completion function will execute after one or the other of the first two functions above. This function doesn't take any parameters and it's pretty common to see it left out of the call to subscribe. It's a nice place to do any cleanup or logging related to the observable, but if you don't need to do anything like that, then feel free to leave it off. In the next demo, I'll show you a more complete and realistic example of making an asynchronous HTTP request to a server and processing the observable that's returned.
Demo: Processing an Asynchronous HTTP Request with an Observable
In this demo I'm going to change one of the data service methods to asynchronously retrieve data from the web server over HTTP and process the observable that's returned. Before I can use Angular's built in HttpClient, I need to import the HttpClient module. I'll open app.module.ts and add a new import statement at the top of the file. I'll import HttpClientModule from @angular/common/http. I'll then scroll down to the imports section of the NgModule decorator and add HttpClientModule to the list of imports. With that in place, I can now use the HttpClient throughout the application. I'm performing all data access in my data.service, so I'll open it next. I'll add an import statement at the top to import the HttpClient. I'm also going to be using an observable in this file, so I need to import the observable type from the RxJS library that gets installed with Angular. The HttpClient is a service just like the services I've written, so I need to inject it into the constructor of the DataService just like I did the LoggerService. I'll make it private and just name the parameter http. I'm going to change the code in the service to retrieve readers from the web server included with the demo app instead of just returning the hard coded reader data I've used so far. The node server application I've included with the demo project, serves up the Angular app, but it also contains several restful endpoints you can use to retrieve reader and book data. I'm not going to cover the server code, but it allows you to retrieve and update readers and books. The data is stored in simple text files inside the server folder. The method I'm going to change is getAllReaders. It's currently coded to return an array of readers, but since I now want it to asynchronously retrieve readers from the server, I'm going to change the return type of the method to be an observable with an array of readers as the type parameter. This effectively means that the data ultimately output by the observable will be an array of readers. I'll now get rid of this code that just returns the hard coded readers I imported at the top of the file. I'm going to replace that with a call to the HttpClient I injected. It has a method on it named get that will create an http.get request. It also takes a type parameter that will be the type of the data that will be returned in the body of the response. I'll pass the URL of the server endpoint as a parameter. The URL is /api/readers. This will return all of the readers from the server in JSON format. Notice that I'm just returning the value returned by the get method. I'll hover over it and you can see that it returns an observable containing an array of readers, which matches the return type I gave my getAllReaders method here on the data.service. This method is already being called from the dashboard component, but I've changed its return type, so I need to go over to the component and change it to handle the observable now being returned. I'll import the Observable type at the top of the file just like I did in the data.service. I'll scroll down to the NgOnInit method and you can see I've got an error on the line that calls getAllReaders, that's because the data type returned no longer matches the type for the allReaders property. Because the method now returns an observable, I don't want to assign its return value to the property, so I'm going to remove the assignment and just leave the function call. GetAllReaders returns an observable and I need to call the subscribe method that exists on that observable to register callback functions that will execute when the results of the asynchronous work are available. I'll use a code snippet to paste in the parameters to the subscribe function. The function accepts three parameters, all of which are functions. I'm using arrow functions for each of them. The first parameter is the function that will be called if the asynchronous work was successful and the data was returned as expected. The data will be passed to the function as a parameter and then I'm just assigning that data to the allReaders property that was receiving the hard coded data earlier. The second function will be called if an error occurs. I'm just logging the error passed to it. As I mentioned before, lots of people leave off the third parameter, which is just a function that will be called when all of the work is complete. I'm just using it to report that the attempt to get all readers is done. As a way of demonstrating that the call to getAllReaders is happening asynchronously, I'm going to use the loggerService to log a simple message at the end of the NgOnInit method. What I expect to see when I run this is the Done with dashboard initialization message will appear in the browser console before the All done getting readers! message. That's because the getAllReaders method will immediately return an observable, which lets the rest of the code in NgOnInit continue executing. It's only after the asynchronous HTTP request is done that the callback functions passed to subscribe will be called. I'll open the terminal and start the app with the command npm start. Once it's up and running, I'll hop over to my browser and go to localhost:3000. The app seemed to load fine. I'll open the Developer tools and refresh it so we can see the request it made to the server for the readers. I'm on the Network tab in the Developer tools, and you can see here the successful request to retrieve the readers. I'll click on it and it shows us the data that came back in the HTTP response. I'll now go over to the Console tab and we do, in fact, have evidence that the request executed asynchronously. The Done with dashboard initialization message appears in the console before the All done getting readers! message. The fact that the client app was able to log messages to the console while the HTTP request was executing also means it was available to respond to user events. Your users will appreciate that. Okay let's now go back to the code and see how it will handle an error generated as part of the HTTP request. I'll go back to the data.service and modify the URL endpoint used in the getAllReaders method. I've coded a couple of endpoints on the server that will always return an error. They're handy for testing how your client code handles errors. I'll change the URL to /api/errors/500. This URL will always return a server error with an HTTP response code of 500. That should trigger the error callback I passed to the subscribe function in the component. I'll go back to the browser and give it a try. I'll refresh the app and I get a 500 error from the server. The object logged here in the console is the result of the console.log statement I included on the error callback function in the component. The type of object it logs is an HttpErrorResponse object. I'll expand it and you can see it has lots of properties, like the HttpHeaders on the response, the HTTP status code, and the statusText. That's all good information, but it's currently being exposed to the dashboard component, even though it was my data.service that made the HTTP request. I would rather keep the details about how the data is retrieved hidden inside the data.service so that my component code doesn't have to change if I decide to change how or where I get my data. In the next demo I'll make a few changes that will abstract these HTTP details out of the component.
Demo: Abstracting Away HTTP Errors
In this demo I'm going to continue working with the observable code from the last demo, but I'm going to abstract away the HTTP error so that all of the HTTP details remain encapsulated in the data.service. The first thing I want to do is create a new custom error object I can send to the component when any error occurs in the service. This error won't be specific to the HTTP request like the error we just saw in the browser console. I'll add a new file to the models folder named bookTrackerError.ts. I'll use a code snippet to paste in a new class. It's a very simple class named BookTrackerError. You could put whatever properties make sense if you create a similar class in your projects, but I'm just adding an errorNumber property, a message property that can contain a technical message about the error, and a friendlyMessage property I can display to users. I now want to make sure an instance of this class gets returned to the calling component instead of the HTTP error response object we saw earlier. I'll go back to the data.service, I'll close the side bar to give myself a little more room. At the top of the file, I need to import my new BookTrackerError type. Back inside the getAllReaders method, I'm going to chain a call to a method named catch onto the observable returned by the get method. Catch is a method that exists on observables and lets you register an error handler function. You pass it a function that should execute if the observable encounters an error, I'll pass it a function I haven't written yet named handleError. I'll then come down a couple of lines and use a code snippet to paste in the new function. The function will be passed the error object that was generated. We've already seen that it will be an instance of the HttpErrorResponse calls that's included in Angular's HttpClient module. I'll use the quick fix light bulb to import that type. The error handler function also returns an observable, but I'm giving it a type parameter of BookTrackerError. Inside the function, I just instantiate a new instance of the BookTrackerError class and then assign values to its properties. I'm hard coding the errorNumber to 100, but I'm setting the message property to the statusText property from the HttpErrorResponse object that was passed in. My friendly message isn't as friendly as it probably should be, but I wanted to keep it short for demonstration purposes. To wrap this new BookTrackerError inside an observable that can be returned to the calling component, I return the result of calling Observable.throw and pass it the BookTrackerError instance. The ugly red squiggly line up here inside getAllReaders is there because the return type of the function no longer matches the possible return types. I need to modify the type parameter on the returned observable to be a union type that includes an array of readers and BookTrackerError since that's what the error handler executed by the catch function we'll return. Let's now go back to the dashboard.component and make some corresponding changes to it. I now know the types that will be returned in both the success and error cases, so I'm going to add type annotations to the parameters on the callback functions. If the HTTP request succeeds, then the data parameter will be an array of readers. If there's an error, I know it will be a BookTrackerError. That type hasn't been imported in this file yet, so I'll use the light bulb to do that. Also, now that I'm using a specific error type, I can get code completion help to log the most appropriate property on that object. Instead of logging the entire object, I'll just log the friendlyMessage property. Okay, let's go back to the browser and run it again. I'll refresh and this time I get the same 500 error, but it logged my user friendly error message and all of the details about how the data is retrieved, HTTP in this case, remain hidden inside the data.service. I think that is much nicer and makes the code more flexible because I can now swap out how the data.service retrieves data without affecting the component. Before we move on I'll just back to the code and change the URL back to the correct one that returns readers instead of an error. Okay, let's now go talk about promises.
Promises
Promises are conceptually similar to observables in that they encapsulate some asynchronous work and let you process the results with callback functions. Observables are part of the RxJS library, but promises are an official part of JavaScript. They were added to the ES2015 version of the language. Since many Angular apps are compiled to support the ES5 version of JavaScript, which did not include promises, Angular ships with polyfills that allow them to be used with ES5 as well. The Angular team decided to use observables with the built in HttpClient, but promises are still a good general purpose solution for performing asynchronous work. You process the results of a promise with callback functions, much like you do with an observable. The terminology used to describe whether a promise succeeds or fails are resolved and rejected. A resolved promise will pass the successful result of the promise, usually some data, to a callback function and a rejected promise will pass the reason the promise was rejected to a different callback function. Let's look at an example. Let's suppose I have a method named updateSchedule that will take an employeeID as a parameter and do some asynchronous work. I specify the return type for the function to be a promise and give it a type parameter that will represent the type of the data that will be returned if the promise is successfully resolved, a string in this case. Inside the function, I return a new promise by creating a new one with the new keyword and passing a function to the Promise constructor. The real work gets done inside the function passed to the Promise and it's common to pass an arrow function to the Promise constructor, but I think those can be a little hard to read if they're first expose to promises, so I want to show you the pieces individually first. Let's now look at this doWork function. The doWork function I passed to the Promise constructor must take two parameters and return void. Here I've named the two parameters resolve and reject and they represent functions that will be passed in automatically and used internally by the promise to report its results. Inside the function, you can do whatever work you want to execute asynchronously. Here I'm just calling a function named processCalendar and assigning its return value to a variable named result. At some point in the process of performing the work, you have to decide if the work is executed successfully or not and report that to the promise by calling either the resolve or reject functions that were passed in above. I'm going to see if the work was successfully by checking the value of the result variable. If the processCalendar function returned success, then I know everything worked as expected and I'll call the resolve function and pass it the string I want returned to the calling code. I'm passing it a string because that was the type of the type parameter on the promise I returned on the previous slide. If there as a problem performing the work, then I call the reject function and pass it the reason for the error. This reason will also be returned to the calling code. I'll show you how to do that in just a minute. So the important things to remember here are that the Promise constructor takes a function as a parameter and that function takes two parameters, resolve and reject, that must be called when you've determined if the work was successful or not. Let's now see what this would look like if I had followed the more common practice of passing it as an arrow function to the Promise constructor. Remember I started with a function named updateSchedule that returned a Promise. Inside the body of the function I'll return the Promise and pass an arrow function to the Promise constructor. Notice that the arrow function has two parameters for the resolve and reject functions. The code inside the body of the arrow function is identical to the previous slide. I do some work and then decide if I should call resolve or reject to report the results of that work. Let's now see how to process the Promise that's returned to the calling code. Processing the results of a Promise is conceptually very similar to the code we saw earlier to process an observable. I'm going to imagine I'm inside the ngOnInit method of a component and call the updateSchedule function I showed you earlier that returns a Promise. Remember that when I processed my observables earlier, I called the subscribe method on the observable returned from the service and passed it callback functions that handled the actual results. I'm going to do something similar here with the promise. However, instead of subscribe, I'm going to chain a call to a function named then. It's a function that exists on Promise objects and takes two other functions as parameters. The first function passed to then will automatically be called if the promise is successfully resolved, meaning you called the resolve function I showed you on the previous slide. The parameter passed to this function, I've named it data here, will contain the result you passed to the Resolved method. The second parameter to then, as you've probably already guessed, is the function that will execute if the Promise is rejected. The parameter passed to this function will be the reason you passed to the reject function inside the Promise. The then function also returns a Promise, which means we can continue to chain calls to other Promise methods onto it. Promises have another method named catch, chained onto a call to then like this, it will execute the arrow function inside it if any errors are thrown inside either of the methods passed to then above. Let's now go use a Promise to perform some asynchronous work in the Book Tracker app.
Demo: Asynchronously Executing a Task with a Promise
In this demo, I'm going to add a new asynchronous service method to the Book Tracker app that returns a Promise. I'm going to add the new method to the data.service, so I'll open it up first. I'll name the method getAuthorRecommendation and I'll have it take a readerID as a parameter. The return type for the method will be a Promise with string as a type parameter. We're going to use our imagination a little bit in this demo and pretend that this method does lots of fancy analysis on the type of books the readers likes, maybe also takes into account his or her age, et cetera, and comes up with the name of an author they might enjoy. The author's name will be the string I'm using as the type parameter for the Promise. Inside the body of the method, I'm going to return a new Promise instance. Remember that the Promise constructor takes a function and that function takes two parameters, which are the resolve and reject functions you call when you know the result of the work being performed. I'll pass an arrow function to the constructor and name the two parameters resolve and reject. I'll use a code snippet to paste in the body of the arrow function. This is where we define the asynchronous work that will be performed. It's also where you're going to have to imagine I'm doing lots of fancy data analysis to come up with the perfect author recommendation. The reality is that I'm calling the setTimeout function to simulate a long running task. I'll build in an artificial 2 second delay before the code inside setTimeout runs. When it runs, it will check to see if the readerID passed in is greater than 0, if so, it will successfully resolve the promise and return Dr. Seuss as the recommended author because he should be recommended to everybody, right? If the readerID is not greater than 0, then I call the reject function and reject the promise and pass Invalid reader ID as the reason for the rejection. Remember that even though this code will take 2 seconds to execute, the getAuthorRecommendation method itself will return to its caller immediately. It will return the promise that encapsulates this work, which will allow the rest of the app to remain responsive while this code executes asynchronously. Let's now go over to the dashboard component and call this new method. I'm going to call it inside the ngOnInit method, but before the last line that logs that the dashboard initialization is complete. I'll call the method on the data.service and pass it the hard coded readerID1. As we saw earlier, we process the results for a promise much like we process the results for an observable, with callback functions. I passed callback functions to the subscribe method on the observable above. Here I'm going to call the then function that exists on promises to register the functions that should execute when it's either resolved or rejected. I'll use a code snippet to paste in the functions passed to then. The first arrow function will be executed if the promise is successfully resolved. It will be passed the piece of data that was passed to the resolve function in the service. In this case, that's a string that will be the name of the recommended author. I'm just using the loggerService to log it. The second arrow function will be called if the promise is rejected. The parameter passed to it will be the reason passed to the reject function in the service. Here I'm using the error method on the loggerService to report that the promise was rejected and then I append the reason that was passed in. I'll now go back to my browser and try it out. I'll refresh the app and then open the Developer tools. You can see in the output that as expected, the promise was successfully resolved and logged Dr. Seuss as the recommended author. More importantly, notice that the author recommendation message, and the message was created earlier when retrieving readers with an observable, were both logged after the message that the dashboard initialization was complete. You can even see in the timestamp that the author recommendation was logged 2 seconds later. This lets us know that execution continued in the component, even as we offloaded work to the asynchronous methods on the data.service. Okay, let's go back to the code and test the code that handles a rejection of the promise. I'll change the call to getAuthorRecommendation to pass -1, which is an invalid readerID. I'll go refresh the app again and a couple of seconds later we get the error logged to the console. That seems to be working fine. I now want to show you one other technique related to handling errors with promises. The second function passed to the then method will handle the promise being rejected, but I'm now going to modify the function above it that runs in the success case to throw an error. I'll surround the body of the arrow function with curly braces so it can have multiple lines. I'll then have it throw a new error with the message Problem in the success handler. In order to handle an error that occurs while executing the function when the promise is successfully resolved, I need to chain a call to another method that exists on promises named catch. I'm going to pass it another arrow function that will handle any errors thrown in the callback functions passed to the this function. Before I run this, I'm going to change the readerID passed to the getAuthorRecommendation function back to 1 so that it successfully resolves the promise. I'll now go refresh the app and check out the new output. A couple of seconds later the author recommendation is logged correctly and you can see here that the hard coded error I threw was handled by the catch method. So even though you've written two functions to handle the resolution or rejection of the promise, you should also chain a call to catch to handle any errors that may occur inside those two other functions. I'll quickly go back to the code and remove the hard coded error I added. Let's now go see how we can make this code a little more linear using the async and await keywords.
Understanding async/await
Now that you've seen how to write asynchronous code with promises, I want to show you how you can use the async and await keywords to write code with promises in a slightly different style. Async and await are keywords available in TypeScript that are specifically made to work with promises. You've seen how you register callback functions to process a promise by calling the then function, you can avoid that with async/await and write your code using a more linear style that I think lots of developers find easier to read. Functions that contain code that returns a promise are declared with the async keyword. You then use the await keyword in front of the call that returns a promise. The function declared with the async keyword will return immediately so that the app remains responsive. However, rather than registering callbacks the code inside the aysnc function will pause on the line with the await keyword while the asynchronous work is performed. Execution will continue linearly when the returned promise is resolved or rejected. Let's go back to the Book Tracker app and rewrite the promise code from the last demo to use async/await instead of callback functions.
Demo: Handling a Promise with async/await
In this demo I'm going to rewrite the code from the last demo to process a promise using the async and await keywords. I'm not going to change how I created and returned a promise in the data.service, instead I'm going to change how I process it in the dashboard component. The getAuthorRecommendation method returns a promise, and you can see here the code I wrote earlier to process it by calling then and catch and passing arrow functions to them. The first thing I want to do is wrap the call to getAuthorRecommendation inside a new private function I'll name getAuthorRecommendationAsync. It's private because I only want to call it from inside this component. It will accept a readerID as a parameter, just like getAuthorRecommendation and it will return a promise with void as the type parameter. I'll use a code snippet to paste in the body of the function. Okay this isn't a lot of code, but there are a few interesting things I want to point out about it. The first is that the code reads, and will execute, very linearly. There are just three lines of code wrapped inside try catch blocks and it will generally execute from top to bottom. There are no callback functions that will execute at some point in the future. However, I'm still calling the getAuthorRecommendation function on the data.service, which we know from the last demo returns a promise, yet I appear to be assigning its return value to a variable named author. As you've probably guessed, the await keyword in front of the function call is the secret ingredient that makes this assignment possible. You can use the await keyword in front of any function call that returns a promise. Execution will effectively pause at that point while the asynchronous work is performed. If the promise returned by the function is successfully resolved, then execution will resume on this same line and then data passed to the resolve method will be available for assignment to a variable. If the promise is rejected, an error will be thrown inside this try block and execution will resume inside the catch block where you can handle the reason for the promise rejection. It's true that execution inside this function will pause while the asynchronous work is performed, and that sounds like it would cause the app to become unresponsive to user input, however, there's a way to prevent that. Any function that contains the await keyword must be declared as asynchronous with the async keyword. It's added just before the function name in the declaration. This, combined with the promise return type, let's the TypeScript compiler know that this function should execute asynchronously, so callers of this function will not wait for the result to be returned. I'll come back up to the ngOnInit method where I called getAuthorRecommendation in the last demo. I'm going to delete that code and replace it with a call to getAuthorRecommendationAsync. I don't have to call then on the result or register any callbacks. The promise returned from the data.service method is being processed inside getAuthorRecommendationAsync. It awaits the result and logs it to the console if it's resolved and catches an error and reports that if it's rejected. Let's go back to the browser and try it out. I'll refresh the app and then open the Developer tools. Everything looks the same, so we know it's working correctly. You can see that the author recommendation was logged at the bottom. Again, note that we can tell execution inside the ngOnInit method continued passed to the call to getAuthorRecommendationAsync because the message about the dashboard initialization completing happened 2 seconds earlier. Okay, let's go back to the code. I want to show you an alternative way you can handle errors when using async/await. Remember that functions declared with the async keyword return Promises themselves, as you can see here. Therefore, another option you have for handling errors is to call catch on the promise returned from this function. To demonstrate that, I'll first remove the try catch blocks from this code and just leave the two lines that call getAuthorRecommendation and log the result. Rather than catching and logging an error here, I'll now come up to where I call this function and chain a call to catch onto it. I'll pass it an arrow function that just reports the error in the console with the loggerService. Before I run this, I'll force it to generate an error by passing in -1 as the readerID. I'll go back to my browser and refresh. After a couple of seconds, we see the error logged in the console. To be more specific, this is the result of the promise inside the data.service being rejected because an invalid readerID was passed in. Chaining the call to catch handled that rejection for me. Let's quickly go back to the code and test it again by throwing a new error. I'll first change the readerID I'm using back to 1 so that the Promise resolves successfully. However, I'll now come to the end of the getAuthorRecommendationAsync method and hard code a new error at the end of it. I'll go back to the browser and refresh again. A couple of seconds later, we see that the call to catch also handles errors just like it does rejected promises. So that's another option you have for configuring error handling with async/await. You can use try catch blocks inside your async function or you can chain a call to catch onto the Promise returned from the asyn function. Use whichever style you like best. As for using the async/await keywords at all, that's also really a stylistic choice. If you find they make your code easier to read, then take full advantage of them, but there's also nothing wrong with calling the then function on Promises and registering callback functions. Use whichever style you prefer. I don't want to leave errors hanging around, so I'll jump back to the code and delete the hard coded error I added.
Summary
I've hopefully convinced you throughout this course that Angular services are the place to put reusable tasks that aren't specific to any particular component. They're also the place to put network calls and other long-running tasks. However, network calls and long-running tasks lead to unresponsive user interfaces, which is why asynchronous services are important. I hope you've seen that they're not terribly difficult to implement, whether you're using observables with the built in HttpClient or writing your own asynchronous task with Promises. I think asynchronous code greatly improves the user experience of your Angular app, so I hope you'll give these techniques a try. Do it for your users, they'll thank you. Okay, in the final module of the course, I'm going to cover some of the common built in Angular services and show you some of the functionality they allow you to easily incorporate into your apps. Stay tuned.
Consuming Common Built-in Services
Introduction and Overview
Hey everybody, this is Brice Wilson. In this final module of the course I'm going to show you how to find and use some of the built-in Angular services. Knowing how to create your own services is obviously very important, but I also want you to know how to take advantage of the services the Angular team has baked into the framework for you. I don't really have any new concepts to show you in this module, so I'm going to quickly get into a couple of demos and show you where you can go to browse the Angular API and begin exploring some common services you may not have known existed. In the first demo, I'll show you how to programmatically retrieve the version of Angular your app is using and how to use the title service to set the title of your app in the browser. In the second demo, I'll create a new service that uses Angular's built-in error handling functionality to create a new customized error handler service. As I walk through both demos, I hope you begin to get a taste of what's possible by working directly with the Angular API and how to find lots of cool features in the framework you may not have encountered before.
Demo: Finding and Using Built-in Services
In this demo, I'm going to show you how to begin exploring the Angular API and how to add and use the built-in title service in your apps. I'm going to start on the official Angular website. You can see the URL for it here in my browser, angular.io. I want to show you the documentation for the Angular API. To get to it I'll first click on the Docs link at the top of the page. Then from the links on the left I'll click API. That brings up a very long categorized list of items available in the API. Fortunately they've made it easy to filter the list based on what you're looking for using these boxes at the top. The first dropdown lets you filter the list by type. You can see in the last all of the types represented and the icon used for that type. I'll click on Class and you can see that it quickly filters the items on the page down to just the classes in the API. I'll change it back to All for now. The second dropdown box lets you filter based on the status of an item. You can see here the different choices, Stable, Deprecated, Experimental, and Security Risk. The last box is just a text box that lets you filter based on the item name. It works exactly as you might expect. I want to show you the title service in this demo, so I'll type title in the box, and you can see the results are clearly filtered to items with title in the name. I want the title class, so I'll click on it to see the details about how to use it. The detail pages for items in the API contain lots of good information to get you started using the item. Right at the top of the page you can see the npm Package the item belongs to and the import statement you'll need to add to your code to use it. You can easily copy and paste this into your app to begin using the item quickly. Below that is a link to the source code on GitHub, in case you want to see exactly how it's implemented. A little further down is an overview section. For this class, it shows us the members available in the class. There's also a helpful description section. We can see here that the title service is used to get and set the title of a current HTML document. It goes on to explain that this service is necessary because an Angular app can't be bootstrapped on the entire HTML document, so you can't bind the text property of the HTML title element. This service lets you work around that. I'll add it to the Book Tracker app in just a minute. I'll now go back to the API list and search for version. The only result it finds is the version class. You can see here that this class represents the version of Angular you're using. You can also see that it can be imported from the Angular core module. I'll click on the link to the source code and it takes me over to GitHub. As expected, we can see the code for the Version class here. It's being exported from the module, so we know we'll be able to import it. I also want to point out the additional item being exported at the very end of the file. This is a constant that's also named VERSION, but in all caps. The interesting thing to note about it is that it's actually an instance of the Version class defined above. Let's now go over to the code for the Book Tracker app and try to use both of these, as well as the title service. I'll first open up the dashboard.component. The version class and the version constant we just saw are in the Angular core module, so I'll add them to the existing list of items I'm already importing from Angular core. I also want to use the title service in this component, so I'll add a new import statement to make it available in the component. It's in the platform-browser module. I'll now scroll down to the constructor and attempt to inject the version class into the component. I'll attempt to inject it just like the other services here. I already know this isn't going to work, but I want to show you the error this generates so you'll hopefully recognize the problem quickly if you encounter something similar when working with the API. I'll open the terminal and start the app with the command npm start. Once it's up and running, I'll jump back over to my browser and go to localhost:3000. I don't get any data on the screen and if I open the Developer tools, we can see I've got an error in the console. I'll scroll up to the top of it and it lets me know there is no provider for Version. So what really happened here is that I tried to use Angular's dependency injection system to deliver an instance of the Version class to the dashboard component. As I demonstrated earlier in the course, classes intended to be used as services must be registered as a provider. That was application not done with the Version class, so I can't inject it into the dashboard component. If I want to use it there, I would just create a new instance myself using the new keyword. At this time, the API documentation doesn't make it clear which classes are registered as providers and which aren't, so figuring out what you can and can't inject may require a little experimentation. I'll now go back to the code and remove my attempt to inject the Version class. I'll replace it with an attempt to inject the Title class. I'll then scroll down just a bit and attempt to use the title instance inside the ngOnInit method. I'll call the setTitle method on it to change the title of the page. I'm going to set the title to Book Tracker with the version of Angular I'm using appended to the end of it. To get the Angular version, I'm going to use the VERSION constant I imported and reference its full property. I'll now go back to the browser and refresh the app. Everything appears to be working again. I didn't get any errors in the console related to injecting the title service, so I know there must be a provider for it. You can also see on the Browser tab the result of me setting the title for the app, which also includes the version of Angular I'm running. Using the version constant I showed you earlier makes it easy to programmatically check the version you're using. Okay, I hope that gives you a good idea about how to get started using the Angular API and using some of the services it offers. In the next demo, I'll create a new service that implements the very handy built-in error handler service.
Demo: Implementing a Centralized Error Handler
In this demo, I'm going to create a new centralized error handling service based on Angular's built-in error handler. I'll start back here on the Angular API list page. I'm going to use the filter box to filter on the word error. The error handler class in the core module is the one I'm looking for. I'll click on it to view the details about it. You can see here the import statement I'll need in order to use it in my code. Down a little further, we're told that the class provides a hook for centralized exception handling. I'll scroll down a little and read more about how it works. In the description, we're told that the default implementation of error handler prints error messages to the console. To intercept error handling, write a custom exception handler that replaces this default. The example code shows us how we can implement this. We need a new class that implements the ErrorHandler class, which just requires providing a method named handleError. We then replace the default error handler by providing the new class whenever an ErrorHandler instance is requested. I showed you how to provide services using this syntax in an earlier module of the course. Let's go back to the Book Tracker code and give this a try. I need to create a new service and I like doing that with the Angular CLI, so I'll open up the terminal in Visual Studio Code. The build task is running in this terminal, so I'll open a new one. The command I'll use to create the service is ng followed by the letters g and s, which are shorthand for generate service. I want the service to go in the core folder and I want to name it BookTrackerErrorHandler. I don't want unit test right now, so I'll pass --spec false, and I'll provide the service in the core module using the -m option. Once that's created, I'll go open the new file from the core folder. I first need to import the ErrorHandler class from the Angular core module. The injectable decorator is already being imported from it, so I can just add ErrorHandler to the list. I want to use the BookTrackerError class I created earlier in the course, so I'll also import it. As we saw in the example code on the Angular site, before I can replace the existing error handling, I need a class that implements the ErrorHandler class. I'll specify that this class does that and then I'll use the little quick fix light bulb to have Visual Studio Code stub out the required method for me. The method I need to implement is this handleError method. I can put inside it whatever I want to happen when Angular encounters an unexpected error. I'll remove this default line and then use a code snippet to paste in the code I want to use. I'm going to keep this pretty simple for demonstration purposes. The first thing I do is create a new instance of the BookTrackerError class I imported. You may recall from earlier in the course that it has three properties on it, errorNumber, message, and friendlyMessage. I'll hard code errorNumber to 200, I'll cast the object passed in as a parameter to an actual error, and then assign its message property to the message property on the BookTrackerError object. The friendly error message will just report that an error occurred and ask the user to try again. You can see in the signature of the handleError method that it doesn't return anything, so the last thing I'll do is just log the BookTrackerError object to the console so we can see if it's all working correctly. In a production app, you might want to do something like post the error to the server so it can be logged or stored in a database. In order to replace the existing error handler mechanism, I need to provide this new class whenever an instance of the built in error handler is requested. To do that, I'll go open the core module where I provided this service when I created it with the Angular CLI. I'll first reformat the providers array to make it a little easier to read. Rather than simply providing the BookTrackerErrorHandlerService, as is being done here, I'm going to change this to the object literal syntax. When some code requests an ErrorHandler, I'll configure the modules injector to deliver an instance of the BookTrackerErrorHandlerService class instead. This error is just because I need to import ErrorHandler. In order to test this, I now need to generate an error somewhere. I'll do that in the dashboard.component. I'll scroll down some and just throw a new error at the end of the ngOnInit method with the message Ugly technical error! I'll go back to my browser, over to the app, and refresh it. You can see in the console that I did successfully replace the centralized error handling because the custom BookTrackerError did get logged. I'll expand it and you can see the values I added to the errorNumber, message, and friendlyMessage properties. Okay, I hope you've seen in these demos that the built-in services are helpful on their own, but can also occasionally be used as extension points for adding custom behavior to your apps.
Summary
I hope these demos have peaked your interest in the Angular API. It already has a lot of features and I expect it will grow as the rest of the framework grows over time. The URL for it is angular.io/api. When looking for built-in services in the API, or anything else for that matter, I would encourage you not to be afraid to explore. You might find just the exact thing you need for your app that you didn't even know was available. While you're exploring, I'll also encourage you to read the documentation and experiment. I think the documentation on the Angular side is generally very good, but I know I always learn more when I actually write some code and try things out. It's easy to misinterpret words on a screen, the real truth lies in how the code runs. Okay, that brings us to the end of the course. As always, I hope you've enjoyed it and learned lots of new skills and techniques you can use in your Angular apps. Thanks for watching.
Course author
Brice Wilson
Brice has been a professional developer for over 20 years and loves to experiment with new tools and technologies. Web development and native iOS apps currently occupy most of his time.
Course info
LevelIntermediate
Rating
(90)
My rating
Duration2h 30m
Released3 Oct 2017
Share course