What do you want to learn?
Skip to main content
Angular: Getting Started
by Deborah Kurata
Angular is one of the fastest, most popular open source web app frameworks today, and knowing how to use it is essential for developers. You'll learn how to create components and user interfaces, data-binding, retrieving data using HTTP, and more.
Resume CourseBookmarkAdd to Channel
Table of contents
Hello. My name is Deborah Kurata and I'd like to welcome you to my course, Angular: Getting Started, from Pluralsight. This beginner level course takes you on a journey through the basic features of Angular. It guides you down the right path, making your own journey with Angular more pleasant productive. Along the way we build a sample application so you can code along or use it as a reference for your own development. You'll see how Angular provides a consistent set of patterns for building components, templates, modules, and services, helping you come up to speed quickly. This course covers how to build components, how to create the user interface for your application in a template, and power it up with data binding and directives. You'll discover how to build services for logic needed across components and inject those services where they are needed. You'll learn how to send requests to a web server using HTTP and observables, and you'll see how to set up routing to navigate between the views of your application. In addition, you'll see how to use the Angular command line interface or CLI to generate, execute, test, and deploy your Angular application. By the end of this course you will know the basics you need to get started your own Angular applications. I hope you'll join me on this journey through Angular: Getting Started from Pluralsight.
Anatomy of an Angular Application
In Angular an application is comprised of a set of components and services that provide functionality across those components. So what is an Angular component? Each component is comprised of a template which is the HTML for the user interface fragment defining a view for the application. Add to that a class for the code associated with the view. The class contains the properties or data elements available for use in the view and methods, which perform actions for the view such as responding to a button click. A component also has metadata, which provides additional information about the component to Angular. It is this metadata that identifies the class as an Angular component. So a component is a view defined with a template, its associated code defined with a class, and additional information, defined with metadata. We'll look at this in more detail in the upcoming modules. As we build these components, how do we pull them together into an application? We define Angular modules. Angular modules help us organize our application into cohesive blocks of functionality. Every Angular application has at least one Angular module called the application's root Angular module. An application can have any number of additional Angular modules including feature modules that consolidate the components for a specific application feature. We'll see much more about Angular modules along our journey. For now let's look at some tips for getting the most from this course.
Get the Most from This Course
To demonstrate the basic features of Angular we'll build a simple application step by step as we journey through this course. Let's see the finished sample application in action. Welcome to Acme Product Management. As its name implies, this application manages our current list of products. Here at the top is our menu for access to the key features of the application. In a full-featured application there would of course be more options here, but we want to limit this sample application to the features we can build within this course. Clicking on the product list option displays the product list page. We see the page title and a table containing the list of products. If the user clicks the Show Image button, the product images appear and the button changes to hide image. Click the button again to hide the images and compress the display. Notice the nice formatting of our price and instead of a number for the rating, we display the rating in stars. Here at the top is an input box where the user can enter a filter string. When the input box contains a value, a Filtered by message appears and the product list is filtered to only those with a product name that contains the entered string. Click on a product name to navigate to the product details. On the product detail page we see the page title with the name of the product and all of the product details including a product description, the nicely formatted price, and the star rating. Click the back button to navigate back to the product list page. So not a huge app, but big enough to demonstrate the basics of Angular. As with any Angular application, this application is comprised of a set of components including a nested component for the rating stars. It has a service to get the data and it incorporates routing to navigate between the pages. Now that we've seen the sample application, how do we build it? As stated earlier, an Angular application is comprised of a set of components and services that provide data and logic across those components. With that in mind, let's break the sample application into components and services. For the welcome page we'll build a welcome component. For the product list page, we'll build a product list component. Recall that we had a nice visual display of stars for our ratings. We want to reuse that feature so we'll build a separate nestable star component. Clicking on a product in the product list page displayed the product detail. We'll build a component for that as well and reuse the star component. Then we need an app component that ties our application pieces together. And our application gets data so we'll want a product data service. Lastly, we need the obligatory index.html file. We'll build the basics of each of these pieces and learn how to organize them into Angular modules as we journey through this course.
We start with first things first. We'll select a language and editor to use, then walk through how to set up an Angular application. Next we'll dive into components. We'll build the app component using a simple template and minimal component code and metadata. We'll see how to build the user interface for our application using templates, interpolation, and directives. We'll power up that user interface with data binding and nicely format our data with pipes. Next we'll tackle some additional component techniques. We'll define interfaces, encapsulate styles, and leverage life cycle hooks to build better components. We'll see how to build a component designed to be nested within other components and how to communicate between the nested component and its container. We often have logic or data that is needed across components. We'll learn how to build services specifically for this purpose and use dependency injection to inject those services into the components that need them. Most web applications need to communicate with a backend server to get or post data and to execute back-end business logic. In this module we'll leverage HTTP to retrieve the data for our application. Our sample application displays multiple views. We'll see how to set up routing to navigate between those views. Next is Angular modules. We learn about and use the root Angular module throughout this course, but as the application grows, we want to separate its concerns. This course module reviews the basics of Angular modules and refactors our application into logical blocks using multiple Angular modules. Through the majority of this course we create our components and other code manually, but spoiler alert! There is an easier way. We'll learn how to use the Angular CLI to build, test, and deploy our application. We're covering a lot of territory and by the end of our journey we'll have a simple, but fully operational Angular application. You can use that application as a reference for your own development. Let's start our journey through Angular.
First Things First
First things first. Before we can start coding with Angular, there are some preparatory steps. Welcome back to Angular: Getting Started from Pluralsight. My name is Deborah Kurata and in this module we set up what we need to work with Angular. A little preparation goes a long way toward a successful adventure. Before we take that first step on our journey with Angular we need to make some decisions, gather our tools, and get everything ready. In this module we'll evaluate several languages we could use to build an Angular application and select one. Once we've picked a language, we select an editor that fully supports development in that language. Then we set up the boilerplate code for an Angular application, and we'll talk about modules and what they mean in Angular. Let's get started.
Selecting a Language
Selecting an Editor
There are many editors that fully support TypeScript either out of the box or with a plugin including all of these. Select one of these or whichever editor suits you best, but keep in mind that your experience with TypeScript will be much more pleasurable if you select an editor that understands TypeScript. For this course, I am using Visual Studio Code. Visual Studio Code runs in Linux, Windows, and OSX. It has great features that support TypeScript such as auto completion, IntelliSense, syntax checking, and refactorings. If you want to use VS Code as your editor, you can download and install it from this URL. If you are interested in using Visual Studio Code and want to learn more, check out their website or this Pluralsight course, but you are free to use whichever editor you choose. Now that we've picked a language and selected and installed an editor, let's set up our environment.
Setting up Our Environment
Setting up an Angular Application
Depending on the tools you select, setting up an Angular application could be somewhat laborious. Before we begin, let's list the required steps. Then we'll look at ways to set up Angular without actually performing these steps. To manually set up an Angular application we'd need to create an application folder, add package definition and configuration files, install the packages, create the application's root angular module because every Angular application needs at least one Angular module, then create the main.ts file to load that Angular module and create the host web page, normally index.html. Luckily, we have some options. We could manually perform each of these steps, though it would be time consuming and prone to error so this option is not recommended. Alternatively we could use tooling such as the Angular CLI. The Angular CLI is the command line tool for generating the setup files and boilerplate code for an Angular application. The CLI also generates your components, modules, services, and other files. It scaffolds and executes your unit and end-to-end tests and provides options to minimize, package, and prepare the files you need for deployment. The Angular CLI can be found here. The CLI is the recommended tool for building, testing, and deploying Angular applications. We'll look more at the CLI later in this course. However, to code along with this course, I recommend that you instead download the sample application starter files. These files are available in my GitHub repository. I used the Angular CLI to create these files, but then included some extra files we'll need as we build our sample application throughout this course. Let's go with this last option. I've navigated to my GitHub repository following the URL from the slide. If you are comfortable with Git, you can clone this repository. Otherwise click this button to download all of the files as a single zip file. The APM Final folder contains the completed code for our sample application. Use these files if you want to see the end result. The APM Start folder contains the starter files. These are the files we will use as the starting point for building our Angular application. After downloading the files, copy or extract the files in the APM Start folder to a working directory called just APM. Your APM folder should then look like this. Now let's open the APM folder with a code editor.
Installing an Angular Application
I've opened the APM working folder with VS Code. First, let's talk about the directory structure. By convention, all of our source files are under a folder called SRC. Under that folder is an app folder that contains the source files specific for our application. We only have a few folders and files here now, but we'll add more as we progress through this course. For applications of any size we'll have subfolders under the app folder for each major feature in the application. The other files here are configuration and setup files, often called boilerplate files. To get us going quickly, we won't dive into all of these files now. We'll learn more about them in the building, testing, and deploying with the CLI module later in this course. Before we can execute this code, we need to install of the libraries required to develop and run our application. Where are those defined? In the package.json file here. This file contains a list of all of the application's dependencies. The @angular entries are Angular system libraries. Notice I have also included Bootstrap in this list. We'll use Bootstrap in the sample application to make it look nice. Down here are some additional dependencies that are only required for development. This is where TypeScript is defined. Toward the top of this file is a set of scripts. We can execute these scripts using npm. We'll learn more about these scripts throughout this course. For now, let's install all of these libraries. First, open a command prompt or terminal. VS Code has an integrated terminal we can use. View, Integrated Terminal. Next, navigate to the folder containing the package.json file. VS Code did that for us. Then type npm install. This installs all of the dependencies defined in the package.json file along with any of their dependencies. Note that you may see some warnings and messages during this installation process, like these. In most cases, you can ignore them. If you see something like this tree structure at the end, the installation completed successfully. Notice that we now have a node modules folder here. This is where npm installed all of our libraries. This folder is large so you may want to exclude it when you check your files into a source control system. Now that we have everything installed, let's try running the sample application.
Running an Angular Application
I've included enough additional code with the starter files to run the sample application so let's give it a try. Remember the scripts area in our package.json file? Here is the start script. When we type npm start, it will execute the command defined here. The ng executes the Angular CLI. The string after the ng is the CLI command. The serve command builds the application and starts a web server. The -o is a command option that opens the URL in our default browser. The CLI has many more commands and options; we'll see more of them as we progress through this course. Are we ready to make it go? Let's try out this start script. Back at the command prompt or terminal, type npm start. This executes the start script, builds the application, starts a web server, and opens the URL in the default browser, which in my case is Chrome. If all is well, the application appears in the browser and displays some text as shown here. If the text does not appear or you see errors in the console, ensure that you ran npm install successfully as defined in the prior clip. As you can see, our application doesn't look like much, but we'll fix that on our journey through Angular. I've moved the windows around so that we could see the editor, the browser, and the terminal window. Let's see what happens when we make a code change. In the editor, open the app.component.html file. Don't worry too much about the syntax here yet; we'll talk about it shortly. For now we'll just change the welcome text. We immediately see here that our code is recompiled, the browser refreshes, and our updated text appears so any time we make a change to our application, we'll be able to immediately see the effect of that change. That will be helpful. When we are finished working with our files, we can close the browser, but the server keeps running. To stop it, go back to the command prompt or terminal and press Ctrl + C or Command + C and y for yes. Then you can exit. Any time you want to run the application and keep it running to watch the effect of your code changes, simply open the terminal and use npm start again. So now we know how to build and run our code. Yay! You may have noticed that I didn't save the file after I made the code change. When using VS Code we can set it to automatically save our changes Here under File, Preferences, Settings, WorkSpace Settings, I have it set to automatically save after a short delay. Before we go any further, let's take a moment and talk about modules.
In this module we discussed several languages and selected TypeScript. TypeScript has all of the productivity features of ES 2015 plus strong typing for better tooling. We then selected an editor. Which editor you use for TypeScript is a personal preference. For the best experience, be sure your selected editor fully supports TypeScript. We then installed npm and looked at the laborious process required to manually set up the boilerplate code for an Angular application. Instead of manually setting this up, we downloaded the starter files from my GitHub repository. We then saw how to install, run, and edit the sample application. We covered ES modules and how TypeScript uses ES module syntax to export and import functionality from our ES modules. We introduced Angular modules and how they organize our application and promote application boundaries. Recall the architecture for the sample application that we are building that we outlined in the first module? Since I used the Angular CLI to create the starter files, it created the index.html file and our root app component. In the next module we'll examine these files and start writing some Angular code.
Introduction to Components
In the last module we set up the infrastructure of our Angular application. Now we are ready to build our first component. Welcome back to Angular: Getting Started from Pluralsight. My name is Deborah Kurata and in this module we walk through building a very basic component with a focus on clearly defining the component's parts, their meaning, and their purpose. We can think of our Angular application as a set of components. We create each component, then arrange them to form our application. If all goes well, those components work together in harmony to provide the user with a great experience. In this module we take a closer look at what an Angular component is and examine the code we need to build one. We walk through how to create the component's class and how and why we need to define metadata. We look at how to import what we need from external modules and we discover how to Bootstrap the app component to bring our application to life. We'll continue to add to this application throughout this course. Looking again at our application architecture that we defined in the first module, the Angular CLI created the index.html file and the app component. In this module we'll rebuild this app component. Let's get started.
What Is a Component?
An Angular component includes a template which lays out the user interface fragment defining a view for the application. It is created with HTML and defines what is rendered on the page. We use Angular binding and directives in the HTML to power up the view. We'll cover binding and directives in a later module. Add to that a class for the code associated with the view. The class is created with TypeScript. The class contains the properties or data elements available for use in the view. For example, if we want to display a title in the view, we define a class property for that title. The class also contains methods, which are the functions for the logic needed by the view. For example, if we want to show and hide an image we'd write the logic in a class method. A component also has metadata, which provides additional information about the component to Angular. It is this metadata that defines this class as an Angular component. The metadata is defined with a decorator. A decorator is a function that adds metadata to a class, its members, or its method arguments. So a component is a view defined in a template, its associated code defined with a class, and metadata defined with a decorator. Want to see what a component looks like in TypeScript? Here is a simple component. It might look complex at first so let's break this component into chunks, starting at the bottom. Here is our class. It defines the properties and methods needed by our view. Here is the component decorator that defines the metadata. The metadata includes the template that lays out the view managed by this component. And here we import the members that we need. Let's examine each of these chunks in more detail, starting at the bottom with the class.
Creating the Component Class
If you have done any object-oriented programming in languages such as C#, VB.NET, Java, or C++, this code should look familiar. A class is a construct that allows us to create a type with properties that define the data elements and methods that provide functionality. We define a class using the class keyword followed by the class name. A common Angular convention is to name each component class with the feature name, then append the word component as the suffix. Also by convention, the root component for an application is called AppComponent as shown here. This class name is used as the component name when the component is referenced in code. The export keyword here at the front exports this class, thereby making it available for use by other components of the application. And as we learned in the last course module, since this file exports something, this file is now an ES module. Within the body of the class are the properties and methods. In this example, we only have one property and no methods. A property defines a data element associated with a class. We start with the property name which by convention is a noun describing the data element and it is in CamelCase whereby the first letter of the name is lowercase. In this example it is the title of the page. Using TypeScript's strong tying, we follow the property name with a colon and its data type. In this example, the page title property is a string. We can optionally assign a default value to the property as shown in this example. Methods are normally defined within the class body after all of the properties. Method names are often verbs that describe the action the method performs. Method names are also in CamelCase whereby the first letter of the name is lowercase. So that's it for the class, but a class alone is not enough to define a component. We need to define the template associated with this component class. How do we provide this extra information to Angular? With metadata. Let's look at that next.
Defining the Metadata with a Decorator
Importing What We Need
Before we can use an external function or class, we need to define where to find it. We do that with an import statement. The import statement is part of ES 2015 and implemented in TypeScript. It is conceptually similar to the import statement in Java or the C# using statement. The import statement allows us to use exported members from external modules. Remember how we just exported our class using the export keyword? That means that other modules in our application can import our exported class if needed. We'll use the import statement throughout our code to import any third-party library, any of our own modules, or from Angular itself. We can import from Angular because Angular is modular. It is a collection of library modules. Each library is itself a module made up of several related feature modules. When we need something from Angular, we import it from an Angular library module just like we import from any other external module. Use this link if you want to view the list of available Angular library packages and their current versions. In our component code we use the component decorator function from Angular to define our class as a component. We need to tell Angular where to find this function so we add an import statement and import component from Angular Core like this. We start with the import keyword. We identify the name of the member we need within curly braces. In this case we need the component decorator function and we define the path to the module containing that member. In this case the Angular Core library module. If we need multiple members from the same module, we can list them all in the imports list separated by commas. We'll see examples of that later in this course. So this is a component. Now we're ready to build the first component for our sample application. Let's jump in.
Demo: Creating the App Component
In this demo we build our app component, which is the root component for our application. Here we are in the editor with the APM folder open. This is the folder we set up in the last module from the starter files I provided. Let's open the src folder and under that, the app folder. Since I used the Angular CLI to create the starter files, it created the root app component. It named the file app.component.ts. The file naming convention that we'll follow throughout this course is to start with the feature name. This is our root application component so by convention it's called app, then a dot, then the type of ES module defined in this file, in this case, component to identify this ES module as a component, another dot, and the extension. Since we are using TypeScript, we'll use ts as the extension. Let's open that file. In VS Code I can click the Explorer icon to close the explorer and see more of the code. I can reopen the Explorer by clicking the icon again. Now I'll delete the starter code for this file so we can build the app component from scratch. I like to start coding by building the class, but the order of these steps really doesn't matter. When we build a class, we first type in the export keyword to ensure that other parts of the application can use this class. Next we type in the class keyword, then the name of the class. Since this is our application component class, we'll follow conventions and name it AppComponent. Inside this class we'll define one property, the page title. We type the property name followed by a colon and the property data type, which for the page title is a string. Notice how IntelliSense helps us here. For this property we want to define a default value for the page title. Next we define the component decorator above the class. The component decorator always begins with an @ sign, then the name of the decorator and we're using the component decorator. The component decorator is a function so we type parentheses and we're going to pass in an object so we type in curly braces. Notice how TypeScript has underlined the component decorator and flagged it as an error. The error is, cannot find name component. Any guesses on what the problem is? If you said that we are missing the import statement, you are right. We need to import the component decorator from Angular Core. Now the error underline goes away and we can complete the metadata. In the component metadata we specify a selector for the name of this component when used as a directive in the HTML. Now that we've imported the appropriate module, we get IntelliSense for these properties. We set the selector to pm-root. The current convention is to prefix each selector with something to identify it as part of our application so we selected pm for product management. We end with a name that represents this component so we used root since this is our root app component. We'll see how to identify our desired selector prefix to the Angular CLI in the Building, Testing, and Deploying with the CLI module later in this course. Next we define the template. To specify another property here we enter a comma and then the name of the other property. We want to define the template. Any valid HTML can be specified in the template. We'll dive deeper into templates in a later module. So for this example, I'll just paste in the HTML and we're done. We have now created the first component for our application, yay! But how do we use it? How do we display its template?
Bootstrapping Our App Component
You may have heard the saying, pulling yourself up by your bootstraps, originally meaning to improve your situation by your own efforts. In tech it has come to mean a self-starting process that loads and goes. We need to tell Angular to load our root component through a process that is called Bootstrapping. We first set up the index.html file to host our application. Then we define our root Angular module to Bootstrap our root component. Let's look at both of these steps. Most Angular applications have an index.html file that contains the main page for the application. This index.html file is often the one true web page of the application. Hence an application is often called a single-page application or SPA, but don't worry. It will look to the user like we have lots of pages as we saw in the demo at the beginning of this course. What we do is insert bits of HTML into the one HTML page defined in index.html. Let's see how that works. Here again is our app component just shifted to the right. Recall that the selector is the name of the component when we use it as a directive in HTML and the template defines the HTML that we want to display. So in the index.html file we simply add the selector where we want our template displayed. Here in the template we call this a directive. A directive is basically a custom element. As soon as the loading is complete, the HTML defined in the component template is inserted between the selector element tags and appears on the page, but how does the Angular compiler know about this custom HTML element? It looks in an Angular module. Angular modules help us organize our application into cohesive blocks of functionality and provide boundaries within our application. They also provide a template resolution environment. What does that mean? When the Angular compiler sees a directive in a template, it looks to the Angular module for the definition so we declare the AppComponent in an Angular module so the compiler can find it. We also use the module to Bootstrap our startup component, which is our AppComponent and we want our application to work correctly in the browser so we add Angular's browser module to our Angular module's imports. Okay, pictures are nice, but what does that look like in code? Here is our application's root Angular module. As with most everything in Angular we define an Angular module using a class. We identify the class as an Angular module by attaching the NgModule decorator and passing in metadata defining the details of this Angular module. For the NgModule decorator, the properties are arrays. In the declarations array we define which of our components belong to this module. By convention, our root application component, AppComponent, belongs to the application's root Angular module, AppModule, so we declare it here. We can add other components here as well. We'll cover best practices for organizing our application into Angular modules later in this course. For now, all of our components will be declared here. In the imports array we define the external modules that we want to have available to all of the components that belong to this Angular module. External modules could be modules provided by Angular, a third party, or our own Angular modules. Here we import browser module, which every browser application must import. Browser module register is important to application service providers such as error handling. The Bootstrap array defines the startup component of the application, which is our AppComponent. The startup component should contain the selector we use in the index.html file, which in this case it does. Now let's check it out in this sample application.
Demo: Bootstrapping Our App Component
In this demo we'll set up index.html to host our application and examine the root Angular module that Bootstraps our AppComponent. Here we are back with our sample application exactly how we left it. Since I used the Angular CLI to create these starter files, the index.html file is already hosting our app component and the app.module.ts file already Bootstraps our app component. Let's take a look starting with the index.html file. To host our application we use the component selector as a directive here within the body element. We can think of a directive as simply a custom HTML tag. Since I used the Angular CLI to create the starter files, the directive is already included here. Now let's open the app.module.ts file. Here is the class and here is the NgModule decorator defining this class as an Angular module. The declarations array declares the AppComponent so that Angular can locate its selector. The imports array includes BrowserModule so the application runs correctly in the browser. The providers array is for services. We'll talk more about those later in this course so it's empty here and the Bootstrap array lists our AppComponent as the starting component for our application. It looks like we are ready to run. We saw in the last module how to start the application by typing npm start in a terminal or command window. When the root app component is loaded, the HTML from our component appears and the binding in that HTML is replaced with the value of our page title property. We now have a working, albeit very small, application. Before closing the browser, let's take a moment and look at the browser developer tools. In Chrome I'll press F12. The exact tools you see here depend on the browser you are using. I'm using Chrome. Most modern browsers provide a console tab as part of the development tools. This is always the first place to look if the page does not appear as expected or doesn't appear at all. Errors, warnings, and other information is displayed here. Use the elements tab or DOM explorer to view the HTML displayed in the page. This is a depiction of the DOM or document object model. The document object model is a document model loaded into the browser and represents our HTML as a node tree where each node is a part of our HTML. Notice these scripts listed here. These aren't in our source index.html file. We'll talk about these bundles, what they are and how they got here in the Building, Testing, and Deploying with the CLI module later in this course. Here is our selector tag. Open the selector tag and we see the HTML that we defined in our component's template. This view is a great way to see the HTML for the rendered page. There is also a debugger option available here on the sources tab to view and debug our code. Since the Angular CLI uses Webpack, our TypeScript files are listed under the Webpack node here. And because the Angular CLI serve feature generates the map file, we can debug our TypeScript directly. We can set a break point, refresh the browser, and it hits that break point. We can step through and check the value of any of our properties. Click here to resume. Use these debugging features any time you need them. Now let's finish up this module with some checklists we can use as we build our own components.
Checklists and Summary
Templates, Interpolation, and Directives
To build the user interface for our application in Angular, we create a template with HTML. To really power up that user interface, we need Angular's data binding and directives. Welcome back to Angular: Getting Started, from Pluralsight. My name is Deborah Kurata, and in these next two modules, we create the user interface using templates, directives, and data binding. Web applications are all about the user interface, and Angular makes it easier to build rich and powerful user interfaces. Angular gives us data binding so we can easily display information and respond to user actions. With Angular directives, we add logic to our HTML, such as if statements and for loops. And with Angular components, we build nested user interface fragments, such as an image rotator or rating stars. We've seen that an Angular component is a view defined with a template, it's associated code defined with a class, and additional information defined with metadata and set using a Component decorator. In these next two modules, we'll focus on techniques for building the template. In this module, we evaluate the different ways we can build a template for our component and demonstrate how to create a linked template for our view. Then we'll build a component associated with that template and use it as a directive. We'll detail how to set up data binding using interpolation and display the value of our component class properties in the view. We need some basic logic in the template, so we'll leverage Angular's built-in directives. In the next module we'll look at additional data binding techniques. Looking at our application architecture, we currently have the index.html file and our root app component in place, so we have a working sample application, but it doesn't look like much. What we really want is a list of products. In this module, we'll begin work on the product list component to display that list of products. Let's get started.
Building a Template
In the prior module, we built an inline template for our app component. We used the template property to define the template directly in the component's metadata. But this is not the only way we can build a template for our components. We can use the template property and define an inline template using a simple quoted string with single or double quotes, or we can define an inline template with a multiline string by enclosing the HTML and ES2015 backticks. The backticks allow composing a string over several lines, making the HTML more readable. We used this technique to build our template in the last module. There are some advantages to defining an inline template using one of these two techniques. The template is directly defined within the component, keeping the view and the code for that view in one file. It is then easy to match up our data bindings with the class properties, such as the page title in this example. However, there are disadvantages as well. When defining the HTML in a string, most development tools don't provide IntelliSense, automatic formatting, and syntax checking, especially as we define more HTML in the template. These issues become challenges. In many cases, the better option is to define a linked template with the HTML in its own file. We can then use the templateUrl property in the component metadata to define the URL of our HTML file. Let's use this technique and build a linked template for our Product List view. Here is our ultimate goal for the Product List view. The view has a nice heading. A Filter by box at the top allows the user to enter a string, the user-entered string is displayed here, and the list of products is filtered to only those with the product name containing that string. The products are listed in a neat table with a nicely formatted header. The Show Image button shows an image for each product. The product name is a link that displays the Product Detail view, which we'll build later in this course. To make this page look nice with very little effort, we use the Twitter Bootstrap styling framework. If you want to find out more about Bootstrap, check out this link, and for the stars we use the Font Awesome icon set and toolkit. To find about more about Font Awesome, check out this link. We'll install both of these in the upcoming demo. Now let's jump into a demo and start building the template for our Product List view. First, let's install Bootstrap and Font Awesome so we can use them in our templates. Open the integrated terminal or command window. I still have the application running in this window, so I'll click plus to open another command window. Then, type npm install bootstrap font-awesome; this installs both packages. You may see some warnings here. These warnings tell us that Bootstrap requires jQuery and Popper, but we only plan to use the styling parts of Bootstrap, not the interactive features, so we don't need these dependencies. Installing the packages does not provide access to their style sheets. For that, we import the styles for these packages into our global application style sheet, which is the style.css file here. We'll import the minimized version of the styles from the bootstrap/dist/css folder, and the minimized version of the styles from the font-awesome.css folder. The style sheets are then available to any template in our application. Now we are ready to add an external template file for the product list component. By convention, each feature of the application has its own folder under the app folder. So let's add a new folder here and name it products. In that folder we'll create the template for our product list component. By convention, the name of the template is the same name as the component with an HTML extension. We'll call our product list component product-listcomponent.html. Let's widen that up a little bit. Now, we are ready to create the HTML for our template. Let's start with the heading. We're using Twitter Bootstrap style classes here. In the heading we display Product List. If you don't want to type in all of this code, you can copy it from the APM-Final folder provided in my GitHub repository, as detailed in the First Things First module earlier in this course. Next is the Filter by. We define an input box for entry of the filter string and we add text that displays the user-entered filter. We again use Twitter Bootstrap style classes to lay out the input box and text into rows. Now let's build the table. We'll use Twitter Bootstrap's table style classes. We have a table header, the first column header is a button to show the product image, and here is the table body. Hmm, we definitely don't want to hardcode in the products here, so let's leave the table body empty for now. So we have the start of a template defined for our component, now what? If you said we need to build the component, you are exactly right.
Building the Component
Remember the steps for building a component that we covered in the last module? We define a class, we add a Component decorator to define the metadata and specify the template, and we import what we need. The only thing that's really different from the component we created in the last module is the template property. Here we are using templateUrl to define the location of our linked template instead of defining an HTML string. Notice the syntax of the path here. If we follow the convention of defining the template.html file in the same folder as the associated component, we can use a relative path by specifying ./. Let's jump right back to the demo and give this a try. We are back with the sample application, exactly where we left it, and we are ready to build a new component. We start by creating a new file in the products folder. We'll name it using the component naming convention, .component, because it is an Angular component, and .ts for the extension. Then we create the class, export class ProductListComponent. We're exporting this class so it is available to other parts of the application. Next, we decorate the class with a Component decorator. It is the Component decorator that makes this class a component, and we know what that underline means, we need the import statement. Let's pass an object into the Component decorator with the appropriate properties. For the selector, we'll set pm-products. We use the same prefix as in the app component to distinguish the selector as part of the product management application. Then we define the templateUrl. Here we provide the path to our HTML file. Since we defined the HTML file in the same folder as the component, we can use the ./ relative past syntax here. So now we have our template defining our view, our class, which defines our associated code, and the Component decorator that defines the metadata. Our component is complete and we're ready to use it, but how?
Using a Component as a Directive
Here is our newly created product list component, and here is the app component we created earlier. Note that I've excluded some of the code here on this slide, such as the import statements and class details for a better fit. We'll see the complete code when we get back to the demo. When a component has a selector defined, as we have here, we can use the component as a directive. This means that we can insert this component's template into any other component's template by using the selector as an HTML tag, like this. The product list component's template is then inserted into this location in the app component's template. So, this is the first step when using a component as a directive. Use the name defined in the selector as an HTML tag in another component's template. When this template is displayed, Angular looks for a component that has a selector with this name. We could have hundreds of components in our application, how does our application know where to look for the selector? The application looks to the Angular module that owns this component to find all the directives that are visible to this component. Every Angular application must have at least one Angular module, the root application module, commonly called AppModule. Currently, our AppModule declares our route application component, AppComponent. A component must belong to one, and only one, Angular module. Because the AppModule declares the AppComponent, the AppComponent belongs to the AppModule. The AppModule bootstraps the application with this component, so it is this first component that is loaded for our application. Our AppModule also imports the system BrowserModule to pull in the features it needs to run this application in a browser. So, this is what our AppModule currently looks like. An Angular module defines the boundary or context within which the component resolves its directives and dependencies. So when a component contains a directive, Angular looks to the component's module to determine which directives are visible to that component. What does that mean for us? Well, for Angular to find the PM products directive used in the AppComponent, the ProductList-Component must also be declared in this Angular module. This is the second step when using a component as a directive. We need to ensure that the directive is visible to any component that uses it. There are two ways to expose a directive in an Angular module. We can declare the component in the Angular module, as we show here, or if the component is already declared in another Angular module, we can import that module, similar to how we import BrowserModule here. Now let's jump back to the demo and give this a try. We are back in our sample app. We defined a selector for our product list component here, so we can use it as a directive in any other component. Let's use it in the app component. Open the app.component file. So instead of displaying my first component, we'll display our new product list template here. Replace the div tags with pm-products. Are we ready to see our result in the browser? And, our page does not display. Let's use our F12 tools to see why. The key part of this error is that we have a Template parse error, pm-products in not a known element, and with this error, Angular gives us a solution. If pm-products is an Angular component, and in our case it is, then verify that it is part of this module. Ah, yes. We didn't do step two and declare it in our application's Angular module. Let's go back to the code. We'll open the app.module, and add ProductListComponent to the declarations array. Well, we're getting a squiggly line here, that means we have an error. Any guess as to what's wrong? If you said we're missing the import, you are correct. Everything we declare must be imported; with VS Code this is easy. Notice the light bulb icon on the left, it is indicating that it has a quick fix for this underlined issue. Click the light bulb and select the desired fix, and VS Code adds the appropriate import line for us. Now that our syntax error is gone, let's try it again. There's our page. It's not complete, and it's not interactive yet, but we have the basics in place. So we successfully used our product list component as a directive. We added the selector as the directive in the containing component's template. We declared the component to the application's Angular module, and we added the appropriate import statement. Now, we are ready to power up our user interface with data binding and some built-in Angular directives.
Binding with Interpolation
In Angular, binding coordinates communication between the component's class and its template, and often involves passing data. We can provide values from the class to the template for display, and the template raises events to pass user actions or user-entered values back to the class. The binding syntax is always defined in the template. Angular provides several types of binding, and we'll look at each of them. In this module, we cover interpolation. The remaining data binding techniques are covered in the next module. The double curly braces that signify interpolation are readily recognizable. The page title in this example is bound to a property in the component's class. Interpolation is a one-way binding, from the class property to the template, so the value here shows up here. Interpolation supports much more than simple properties. We can perform operations such as concatenation or simple calculations. We can even call a class method, such as getTitle, shown here. We use interpolation to insert the interpolated strings into the text between HTML elements, as shown here. Or, we can use interpolation with element property assignments, as in this example. Here we assign the innerText property of the H1 element to a bound value. Both of these examples display the same result. The syntax between the interpolation curly braces, is called a template expression. Angular evaluates that expression using the component as the context. So Angular looks to the component to obtain property values or to call methods. Angular then converts the result of the template expression to a string and assigns that string to an element or directive property. So, anytime we want to display read-only data, we define a property for that data in our class and use interpolation to display that data in the template. And if we need to perform simple calculations or get a result from a method, we can do that with interpolation as well. Let's give this a try. Looking at the product list template, from our sample application, we hardcoded in the page title here in the heading. Binding the heading to a property in the class instead of hardcoding it in the HTML makes it easier to see and change when working on the code, and we could later retrieve this text from a file or database. Let's start by adding a property in the class for the page title. We'll open the component to the right and close down the Explorer. Here in the class we specify the property name, we'll call it pageTitle, and because we are using TypeScript, we define the type for this property. Lastly, we assign a default value, Product List. With the pageTitle property in place, we can now bind to the pageTitle property in the template. We replace the hardcoded Product List here with interpolation and specify the name of the property. Now, when this template is displayed, Angular assigns the string value of the pageTitle property to the inner text property of this div element, and Product List will be displayed. Let's see the result in the browser. With our binding, the page title appears as before. So, we can confirm that it works. I've rearranged the windows so that we see both the code and the browser. Now let's make a change to our page title here, and we immediately see it in the browser. So our interpolation works! So anytime we want to display the value of a component property, we simply use interpolation. Now we're ready to add some logic to our template.
Adding Logic with Directives: ngIf
We can think of a directive as a custom HTML element or attribute we use to power up and extend our HTML. We can build our own custom directives or use Angular's built-in directives. Previously in this module, we've seen how to build a component and use it as a custom directive, we used to pm-products directive to display our product list template. In addition to building our own custom directives, we can use Angular's built-in directives. The built-in Angular directives will look at our structural directives. A structural directive modifies the structure, or layout, of a view by adding, removing or manipulating elements and their children. They help us to power up our HTML with if logic and for loops. Notice the asterisk in front of the directive name. That marks the directive as a structural directive. Let's look at ngIf first. NgIf is a structural directive that removes or recreates a portion of the DOM tree based on an expression. If the expression assigned to the ngIf evaluates to a false value, the element and its children are removed from the DOM. If the expression evaluates to a true value, a copy of the element and its children are reinserted into the DOM. For example, say we only wanted to show the HTML table if there are some products in a list of products. We use ngIf on a table element and set it to products and products.length. If the product's variable has a value and the product's list has a length, the table appears in the DOM. If not, the table element and all of its children are removed from the DOM. But didn't we just say that an Angular module defines the boundary or context within which the component resolves its directives and dependencies? How will our application find this ngIf directive? Looking back at the illustration of our AppModule, we see that it imports BrowserModule. Luckily for us, BrowserModule exposes the ngIf and ngFor directives. So, any component declared by the AppModule can use the ngIf or ngFor directives. With that settled, let's try out the ngIf directive. We are back in the sample application looking at the product list component and its template. We only want to display this table of products if there are some products to display. So the first thing we need is a property to hold the list of products. Where do we define that products property? In the component's class, of course. We'll add a products property here. Hmm, but what is the type of this property? Well, we want an array of product instances, but we don't currently have anything that defines what a product is. We'll have a better solution in a later module, but for now, we'll just define products as an array of any. In TypeScript, we use any as the data type anytime we don't know or don't care what the specific data type is. We need to populate our array, but where do we get the data? In many cases, we would communicate with a back-end server to get this data. We'll look at how to do that later in this course. For now, we'll just hardcode in a set of products. If you are coding along, consider copying a few of the products from the products.json file provided with the starter files under the api, products folder. With a products property in place, we're ready the use it in the HTML. We want to put it on the table element because that is the element we want to add or remove from the DOM. Type *ngIf=, and then our expression enclosed in quotes. We only want to show the table if there is a list of products, and that list of products contains some elements. Let's see what this looks like in the browser. We see the table header, so we know our table is displayed. Let's try this. Let's comment out the product property assignment, bring up the browser again, and we see that the table disappeared. Now if we uncomment out our table and look again at the browser, our table reappears. With ngIf, the associated element and its children are literally added or removed from the DOM, but notice that we still aren't populating the table with our products. Let's do that next.
Adding Logic with Directives: ngFor
Another structural directive is ngFor. NgFor repeats a portion of the DOM tree, once for each item in an iterable list. So we define a block of HTML that defines how we want to display a single item and tell Angular to use that block for displaying each item in the list. For example, say we want to display each product in a row of a table. We define one table row and its child table data elements. That table row element and its children are then repeated for each product in the list of products. The let keyword here creates a template input variable called product. We can reference this variable anywhere on this element, on any sibling element, or on any child element. And notice the of instead of in here, we'll talk more about that in a moment. For now, let's jump back to our demo. We are, once again, looking at the product list component and its template. Here in the table body, we want to repeat a table row for each product in the list of products. In the table body, we'll add a tr element for the table row, and in the tr element we'll specify the ngFor, *ngFor='let product of products'. Next, we'll add the child elements. We'll insert a td, or a table data element, for each property of the product that we want to display in the table. We'll need to match them up with the table header elements. The first column displays the product image, let's skip the image for now, we'll add that in the next module, but we'll still add the td element as a placeholder. The next table header says Product, so in this column we want the product name. We'll use interpolation to bind to the product's name by using the local variable, product, and a dot to drill down to the product properties. We want productName here. How did we know that property name? Looking here at the product list component, we see the product property names here. So these are the names we use in the interpolation template expressions. Next, I'll add td elements for some of the other product properties. So for each product in our list of products, we will get a tr element for a table row and td elements for table data. Want to see how this looks in the browser? Wow! We have our products. Doesn't that look nice? Well, our price is not very well formatted and it doesn't have a currency symbol. We'll fix that with pipes in the next module. Looking back at the component, we defined an array for our list of products. In the template, we laid out the HTML to display one product, the product is displayed in a table row with product properties in the appropriate columns. Using an ngFor structural directive, we repeat this table row and its columns for each product in the list of products. So why is this ngFor syntax product of products and not product in products? The reasoning for this has to do with ES2015 for loops. ES2015 has both a for of loop and a for in loop. The for of loop is similar to a for each style loop. It iterates over an iterable object, such as an array. For example, say we have an array of persons nicknames, if we use for of to iterate over this list, we'll see each nickname logged to the console. The for in loop iterates over the properties of an object. When working with an array such as this example, the array indexes are innumerable properties with integer names and are otherwise identical to general object properties, so we see each array index logged to the console. To help remember the difference, think of in as iterating the index. Since the ngFor directive iterates over iterable objects, not their properties, Angular selected to use the of keyword in the ngFor expression. Now let's finish up this module with some checklists we can use as we work with templates, interpolation, and directives.
Checklists and Summary
Checklists are a great way to recheck our understanding and our work. Let's start with a template. Use an inline template when building shorter templates, then specify the template property and the component decorator. Use double or single quotes to define the template string, or use the ES2015 backticks to lay out the HTML on multiple lines. When using inline templates, there is often no design time syntax checking, so pay close attention to the syntax. Use linked templates for longer templates. Specify the templateUrl property and the component decorator and define the path to the external template file. This one is a more visual checklist. After building the template, we build its component and learn how to use that component as a directive. Remember our steps? First, we use the directive as an element in the template for any other component. We use the directive component selector as the directive name. We then declare the component so it is available to any template associated with this Angular module. We add the component to the declarations array passed in to the NgModule decorator of the Angular module. Angular's data binding was introduced in this module with a look at interpolation. Interpolation is one-way binding from a component class property to an element property. Interpolation is defined with curly braces and a template expression. That expression can be a simple property, a concatenation, a calculation or a method call. Note that no quotes are needed when using interpolation. And, we saw how to use two of Angular's structural directives, ngIf and ngFor. When using these structural directives, be sure to prefix them with an asterisk and assign them to a quoted string expression. Use ngIf to add or remove an element and its children from the DOM based on an expression. If the assigned expression is evaluated to be a true value, the element is added to DOM. If false, the element is removed from the DOM. Use ngFor to repeat an element and its children in the DOM for each element in an iterable list. Define the local variable with let, and use of, not in, when defining the ngFor expression. In this module, we evaluated the differences between an inline template and a linked template, and we created a linked template. We then built a component for that template and learned how to use that component as a directive. We took a first look at Angular data binding through interpolation and powered up our template by using built-in Angular directives. Here, once again, is our application architecture. In this module, we started the product list component. Next up, let's discover more of Angular's data binding features and add interactivity to the product list template.
Data Binding & Pipes
There's more to data binding than just displaying component properties. Welcome back to Angular: Getting Started from Pluralsight. My name is Deborah Kurata, and in this module, we explore more data binding features and transform bound data with pipes. To provide a great interactive user experience, we want to bind DOM elements to component properties so the component can change the look and feel as needed. We can use bindings to change element colors or styles based on data values, update font size based on user preferences, or set an image source from a database field, and we want notification of user actions and other events from the DOM so the component can respond accordingly. For example, we respond to a click on a button to hide or show images. And sometimes, we want the best of both worlds using two-way binding to set an element property and receive event notifications of user changes to that property. In this module, we'll use Angular's property binding to set HTML element properties in the DOM, we walk through how to handle user events such as a button click with event binding, and how to handle user input with two-way binding. Lastly, we'll discover how to transform bound data with pipes. And here once again is our application architecture. We have the first cut of our product list component, but it doesn't have any interactivity. In this module, we'll use data binding features to add interactivity to the product list component. Let's get started.
Property binding allows us to set a property of an element to the value of a template expression. Here we bind the source property of the image element to the imageUrl property of the product, effectively defining the source of the image from information in our component class. Our binding target is always enclosed in square brackets to the left of the equals and identifies a property of the element. The binding source is always enclosed in quotes to the right of the equals and specifies the template expression. For comparison, here is a similar binding using interpolation. Note that in this case, the element property is not enclosed in square brackets and the template expression is enclosed in curly braces with no quotes. If you need to include the template expression as part of a larger expression, such as this example, you may need to use interpolation. Like interpolation, property binding is one way from the source class property to the target element property. If effectively allows us to control our template's DOM from our component class. Now let's add some property binding to our sample application. Here we are back in the editor looking at the product list component and its associated template. Let's use property binding to bind the source of our product image. We use an image element to display our product image, and we use property binding to bind the image's source, or src property. So we enclose the src in square brackets. On the right side of the equals, we define the template expression in quotes. We want to bind to the product's imageUrl property from the ProductListComponent class. Let's also use property binding to bind the title property of the image to the product's name. Let's check out the result in the browser. Whoa, they're big, but we do see images. If we hover over an image, we see the image title, but this image is rather large for display in our table. Let's use property binding to set some style properties. Let's add properties for the image width and image margin to our component class. The image width is a number, so we specify its type with a colon and then number. Let's set it to 50. The image margin is also a number, and we'll set it to 2. Back in the template, we use property binding to bind the image styles. We want to bind the style property width in pixels. We'll bind that to the image width property from the component class. Notice that we don't prefix this property with product because imageWidth is a property of the component class, not the product object. And we do the same with the style margin and pixels, and set that to the imageMargin class property. So now we've seen how to use property binding to bind several properties of the image element to properties of the component's class. Looking again at the browser, that looks much better, but our images are always displayed. The Show Image button doesn't work yet. To hook up the button, we need to respond to user events. Let's do that next.
Handling Events with Event Binding
Handling Input with Two-way Binding
When working with user-entry HTML elements such as an input element, we often want to display a component class property in the template and update that property when the user makes a change. This process requires two-way binding. To specify two-way binding in Angular, we use the ngModel directive. We enclose ngModel in square brackets to indicate property binding from the class property to the input element, and parentheses to indicate event binding to send a notification of the user-entered data back to the class property. We assign this directive to a template expression. To help us remember which order to put the two sets of brackets, visualize a banana in a box, square brackets for the box on the outside, and parentheses for the banana on the inside. Now we have another Angular directive that we can use. Every time we want to use an Angular directive in a template, we need to consider how to make that directive visible to the component associated with that template. Recall that an Angular module defines the boundary or context within which the component resolves its directives and dependencies. The illustration of our AppModule currently looks like this. We want to use the ngModel directive in our ProductList Component, which is owned by AppModule. So in the AppModule, we need to import the appropriate system module that exposes the ngModel directive. Since the ngModel directive is most often used when building data entry forms, ngModel is part of FormsModule, so we import that here. Now the ngModel directive and any of the other Angular forms directives are visible to any component declared by AppModule, including our Product-List Component. Now let's give this a try. We are back in the editor and looking at the product list component and its associated template. Recall that we defined a Filter by input box here and display the entered filter here. We'll later use the filter to filter our list of products. Let's start by adding a component class property for the list filter. This property is a string and will set a default initial value for filtering the list of products. We hardcode the filter string here, but you can imagine that we store the user's last entered filter and use that as the default instead. With that, we can set up the two-way binding. On the input element, we draw a banana in a box, then specify the ngModel directive. We bind to the component class listFilter property. We want to display the list filter here, so we use interpolation. Recall that interpolation has no quotes. Are we done? Not quite. Let's see what happens if we try to run. The page doesn't appear. If we press F12, we see the error is, Can't bind 'ngModel' since it isn't a known property of input. This is telling us that Angular can't find the ngModel directive. Recall from the slides that the ngModel directive is part of the Angular module for forms called FormsModule. To expose this directive to our product list component, we need to import the FormsModule in the module that owns the product list component, which is our AppModule. We start by importing FormsModule from angular/forms. We then add FormsModule to the imports array for the ngModule decorator. Why the imports array and not the declarations? Our directives, components, and pipes are declared here in the declarations array. Directives, components, and pipes we use from other sources, such as Angular itself or third parties, are defined in external Angular modules we add to the imports array here. Now that we've told Angular where to find the ngModel, let's see the result. When the page displays, we see cart as the default value. It is displayed here and here. If we modify the entry, notice that the displayed filter text is also updated. That's because we are using two-way binding. Notice that the list of products is not yet filtered. We'll do that in the next module. There is one more thing we do want to address now, and that is the data formatting. That price should really look like a price and show the appropriate currency symbol.
Transforming Data with Pipes
With Angular's data binding, displaying data is easy. Just bind an element property to a class property, and we're done! Well, not always. Sometimes the data is not in a format appropriate for display. That's where pipes come in handy. Pipes transform bound properties before they are displayed, so we can alter the property values to make them more user-friendly or more locale-appropriate. Angular provides some built-in pipes for formatting values, such as date, number, decimal, percent, currency, uppercase, lowercase, and so on. Angular also provides a few pipes for working with objects, such as the json pipe to display the content of an object as a json string, which is helpful when debugging, and a slice pipe, which selects a specific subset of elements from a list. We can also build our own custom pipes as we'll see in the next module. Let's start with a simple example. Say we want to display the product code in lowercase. We can add the pipe character after the property in the template expression, and then specify the lowercase pipe. The product code is then transformed into lowercase before it is displayed. We can also use pipes in property bindings. Add the pipe after the property in the template expression and specify the desired pipe. In this example, we specified the uppercase pipe, so the image title will appear in all caps. If needed, we can chain pipes. In this example, the price is transformed into a currency. By default, the currency pipe adds the all caps, three-letter abbreviation of the local currency to the amount. If we want to display that abbreviation in lowercase, we can transform it again by simply adding another pipe. Some pipes support parameters. Parameters are defined by specifying a colon and the parameter value. For example, the currency pipe has three parameters: the desired currency code, a string defining how to show the currency symbol, and digit info. The digit info consists of the minimum number of integer digits, the minimum number of fractional digits, and the maximum number of fractional digits. The value here of 1.2-2 means that at least 1 digit to the left of the decimal, and at least 2 digits to the right of the decimal, and no more than 2 digits to the right of the decimal, effectively defining 2 decimal places. With that, let's jump back to the demo. We specify the pipes in the template, so we are looking at the product-list.component template. Let's add a lowercase pipe for the product code and a currency pipe for the price. For the product code, we simply insert the pipe character after the property in the template expression then type lowercase. That's it. For the price, we insert a pipe character and currency. That's all that is required. But let's try out a few of the parameters. We'll specify USD symbol to display the dollar sign instead of the currency abbreviation, and 1.2-2 to specify that we want at least 1 number to the left of the decimal place and 2 and only 2 numbers to the right of the decimal place. Let's see how we did. Ah. Looking at the result, we now see the product code in lowercase and the price displayed nicely as a currency. So we can easily perform simple data transformations using the built-in pipes in the template expressions for our bindings. Feel free to try out some of the other pipes from the slides. Let's finish up this module with some diagrams and a checklist we can use as we work with bindings and pipes.
Checklists and Summary
Data binding makes it easy to display class properties from our component and set DOM element properties to our class property values to better control the view, and the component can listen for events from the DOM to respond as needed for an interactive user experience. There are four basic types of binding in Angular. Here is a diagram as a memory aid. Interpolation inserts interpolated strings into the text between HTML elements or assigns element properties. Be sure to wrap the template expression in curly braces and no quotes. Property binding sets an elements property to the value of a template expression. The binding target specifies a property of the element and must be enclosed in square brackets. The binding source specifies the template expression and must be enclosed in quotes. Event binding listens for events and executes a template statement when the event occurs. The target event specifies an event name and must be enclosed in parentheses. The template statement often defines the name of the method to call when the event occurs and must be enclosed in quotes. Two-way binding displays a component class property and updates the property when the user makes a change. Use the banana in a box syntax with the ngModel directive. The binding source specifies the template expression and must be enclosed in quotes. Here are some things to remember when using ngModel. Define ngModel within the banana in a box for two-way binding. Be sure to add FormsModule from the angular/forms package to the imports array of an appropriate Angular module, in this case, AppModule. This ensures that the ngModel directive is available to any template defined in a component associated with that module. We'll talk more about Angular modules later in this course. The data we have in our component may not be in the format we want for display. We can use a pipe and a template to transform that data to a more user-friendly format. To use a pipe, specify the pipe character, the name of the pipe, and any pipe parameters separated with colons. Here is an example of the currency pipe with three parameters. This module covered data binding and pipes. We looked at property binding, event binding, and two-way binding. Lastly, we discovered how to transform bound data to a more user-friendly format using built-in pipes. And here, once again, is our application architecture. In this module, we finished more of the product list component, but it could be better. Next up, we'll see several techniques for improving our component.
More on Components
When building clean components, we want to ensure everything is strongly typed, our styles are encapsulated, we respond to appropriate lifecycle events, and we transform data to user-friendly values as needed. Welcome back to Angular: Getting Started, from Pluralsight. My name is Deborah Kurata, and in this module we learn several ways to improve upon our components. Components are one of the key building blocks of our application. The cleaner, stronger, and more durable we make these blocks, the better our application. So, how can we make our components better? Strong typing helps minimize errors through better syntax checking and improved tooling, but what if there is no predefined type for a property? To strongly type a property that has no predefined type, we define the type ourselves using an interface. If a component needs special styles, we can encapsulate those styles within the component to ensure they don't leak out to any other component in the application. A component has a lifecycle managed by Angular. Angular provides a set of lifecycle hooks we can use to tap into key points in that lifecycle, adding flexibility and responsiveness to our application. As we saw in the last module, pipes provide a convenient way to transform bound data before displaying it in the view. We may have other application- unique data transformation requirements. Luckily, we can build our own custom pipes. Any time we build and test a component once and nest it in several places in the application, we have minimized development time and improved the overall quality of the application. In this module, we explain interfaces and demonstrate how to use them to strongly type our objects. We look at how to encapsulate component styles, we introduce the component lifecycle and how to hook into its events, and we detail how to build a custom pipe. We cover building nested components in the next module. Looking at our application architecture, in this module we'll add features to improve the product list component. Let's get started.
Encapsulating Component Styles
When we built a template for a component, we sometimes need styles unique to that template. For example, if we build a sidebar navigation component, we may want special li or div element styles. When we nest a component that requires special styles within a container component, we need a way to bring in those unique styles. One option is to define those styles directly in the template's HTML, but that makes it harder to see, reuse, and maintain those styles. Another option is to define the styles in an external style sheet that makes them easier to maintain, but that puts the burden on the container component to ensure the external style sheet is linked in the index.html. That makes our nested component somewhat more difficult to reuse, but there is a better way. To help us out with this issue, the Component decorator has properties to encapsulate styles as part of the component definition. These properties are styles and styleUrls. We add unique styles directly to the component using the styles property. This property is an array, so we can add multiple styles separated by commas. A better solution is to create one or more external style sheets and identify them with the styleUrls property. This property is an array, so we can add multiple style sheets separated by commas. By encapsulating the styles within the component, any defined selectors or style classes are only applicable to the component's template and won't leak out into any other part of the application. Let's try this out. Before we change any code, let's look again at our Product List view in the browser. Notice the table headers. They could use a little color, so let's build an external style sheet for our product list component. We'll add a new file in the products folder, and since this file only contains the styles for our product list component, we'll call it product-list.component.css. In this style sheet, we add a table header style. We can modify the thead element styles directly because the style sheet is encapsulated in this component and the styles defined here won't affect any other component in the application. We could add any other styles as needed to jazz up our product list component. To use this new style sheet, we modify the product list component. In the Component decorator, we specify our unique style sheet. We add the styleUrls property and pass it an array. In the first element of the array we specify the path to our style sheet. Since we defined the CSS file in the same folder as the component, we can use the ./ relative path syntax. We could add more style sheets here, separated with commas. Let's review the result in the browser, and we see that the table header is now a nice blue color. We can use the styles or styleUrls property of the Component decorator any time we want to encapsulate unique styles for our component. Next up, let's dive into lifecycle hooks.
Using Lifecycle Hooks
Building Custom Pipes
Filtering a List
Checklists and Summary
We've covered several discrete topics in this module, so we have a checklist for each one. First, interfaces. We use interfaces to specify custom types, such as product in our sample application. Interfaces are a great way to promote strong typing in our applications. When creating an interface, use the interface keyword. In the body of the interface, define the appropriate properties and methods along with their types, and don't forget to export the interface so it can be used anywhere in our application. We implement an interface, including built-in Angular interfaces, to ensure that our class defines every property and method identified in that interface. Add the implements keyword and the interface name to the class signature, then be sure to write code for every property and method in the interface. Otherwise, TypeScript displays a syntax error. We can encapsulate the styles for our component in the component itself, that way the styles required for the component are associated with the component alone and don't leak into any other parts of the application. Use the styles property of the Component decorator to specify the template styles as an array of strings. Use the styleUrls property of the Component decorator to identify an array of external style sheet paths. The specified styles are then encapsulated in the component. Lifecycle hooks allow us to tap into a component's lifecycle to perform operations. The steps for using a lifecycle hook are: import the lifecycle hook interface; implement the lifecycle hook interface in the component class, the step is not required, To build a custom pipe, import Pipe and PipeTransform. Create a class that implements the PipeTransform interface. This interface has one method, transform. Be sure to export the class so the pipe can be imported from other components. Write code in the transform method to perform the needed transformation and decorate the class with the Pipe decorator. We can use a custom pipe in any template anywhere we can specify a pipe. In an Angular module, import the pipe. In the metadata, declare the pipe in the declarations array, then any template associated with a component declared in that Angular module can use that pipe. In a template, immediately after the property to transform, type a pipe character, specify the pipe name, and enter the pipe arguments, if any, separated by colons. This module provided a set of techniques for improving our components. We walked through how to use an interface to strongly type our custom objects. We saw how to encapsulate styles within a component. We were introduced to the component lifecycle and how to tap into that lifecycle with lifecycle hooks. And we discovered how to build a custom pipe that we can use in any component template. So in this module, we've completed the product list component. Yay! Next up, we'll see how to build nested components and build this star component.
Building Nested Components
Our user interface design may include features that are complex enough to be separate components, or that are reusable across our views. Welcome back to Angular: Getting Started, from Pluralsight. My name is Deborah Kurata, and in this module, we see how to build components designed to be nested within other components, and will discover how to establish communication between the nested component and its container component. Just like nesting dolls, we can nest our components. We can nest a component within another component, and nest that component within yet another component, and so on. And because each component is fully encapsulated, we expose specific inputs and outputs for communication between a nested component and its container, allowing them to pass data back and forth. There are two ways to use a component and display the component's template. We can use a component as the directive. We saw how to use a component as a directive when we displayed the AppComponent template in the index.html file. The pm-root directive is defined as the AppComponent selector. The template is then displayed within the directive tags. We use this same technique with nested components. Alternatively, we can use a component as a routing target, so it appears to the user that they've traveled to another view. The template is then displayed in a full-page style view. We'll use this technique later in this course to route to our Product List view. Our Product List view is currently used as a directive, but that's only because we have not yet covered routing. In this course module, we'll focus on building a nested component. So what makes a component nestable? Technically speaking, any of our components could be nested if they have a selector defined in the Component decorator. But does it really make sense to nest a large few such as our product list? For our purposes, we'll define a component as nestable if its template only manages a fragment of a larger view, if it has a selector so it can be used as a directive, and optionally, if it communicates with its container. In this module, we'll build a nested component, then we'll review how to use that nested component as a directive in a container component. We'll examine how to pass data to the nested component using a property with the Input decorator, and how to pass data out of the nested component by raising an event defined with the Output decorator. In our sample application, to improve the user experience we want to replace the rating number displayed in the product list component with stars. In this module, we'll build the star component and nest it within the product list component. Let's get started.
Building a Nested Component
Here is a visual representation of a component that is nestable. Here is another component. It wants to use the nestable component in its template. We then refer to the outer component as the container or parent component, and we refer to the inner component as the nested, or child component. When building an interactive application, the nested component often needs to communicate with its container. The nested component receives information from its container using input properties, and the nested component outputs information back to its container by raising events. In our sample application, we want to change the display of the 5 Star Rating from this, to this. Displaying the rating number using a visual representation such as stars makes it quicker and easier for the user to interpret the meaning of the number. This is the nested component we'll build in this module. For the star component to display the correct number of stars, the container must provide the rating number to our star component as an input. And if the user clicks on the stars, we want to raise an event to notify the container. Let's jump right in and build our star component. When we last saw our sample application, we had completed the product list component. Now of course, we want to change it. Instead of displaying a number for the rating here, we want to display stars. Instead of adding a code to the product list component to display the stars, we want to build it as a separate component. This keeps the template and logic for that feature encapsulated and makes it reusable. So let's begin by creating a star component. The star component can be used by any feature of the application, so it really doesn't belong in our products folder. We'll instead put it in a shared folder where we'll put all our shared components. If you are using the starter files, I've already created this folder and included the template and style sheet for our component here. Now we are ready to build the star component. We begin by creating a new file. We'll name it star.component.ts. We then create this component just like we'd create any other component, starting with the class, export class StarComponent. What's next? Yep, we decorate the class with the Component decorator. Recall that it is this Component decorator that makes this class a component. As always, it shows us a syntax error here because we are missing, yep, our import. Time to set the Component decorator properties. For the selector, we'll set pm-star. For the templateUrl, we provide the path to the HTML file provided with the starter files. We'll add the styleUrls property, and in the array we'll set the first element to the path of the style sheet that was also provided. Since both files are in the same folder as the component, we can use relative pathing. Now let's take a peek at the star component template. Here it displays five stars. It then crops the stars based on a defined width. This technique can then display partial stars, such as four and a half of the five stars by setting the width such that only four and a half of the stars appear. Recall what this syntax is called? This is property binding. We're using it here to set the style width and here to bind the title property to display the numeric rating value. For these bindings to work, we need two properties in the components class, the rating and the starWidth. Let's add these two properties. We want a rating property, which is a number and defines the rating value. Since we don't yet have a way to get this value, let's hardcode it to 4 for now so we'll see some stars. And we need the starWidth. This value is calculated based on the rating. So where do we put that calculation? Well, we'd want the starWidth recalculated any time the container changed the rating number. So let's tap into the OnChanges lifecycle hook as we discussed in the last module. We'll implement the OnChanges interface, and we'll write code for the ngOnChanges method identified in the OnChanges interface. In this method, we'll convert the rating number to a starWidth based on the width of our stars. Our component is now complete, and we're ready to nest it in another component. How do we do that?
Using a Nested Component
Now we are ready to nest our new component within another component. We do that by using our nested component as a directive and following the same steps we covered earlier in this course. Here is a shortened form of the code for a container component and its template, and here is the nested component we just created. Instead of displaying the star rating number, we want to display the stars. So the container component uses the nested component by specifying its directive here. This identifies where in the container to place the nested component's template. As we've seen in prior demos, when we use the component as a directive, we need to tell Angular how to find that directive. We do that by declaring a nested component in an Angular module. How do we know which Angular module? Well, we still only have one Angular module, AppModule. In our example, the Product-List Components template wants to use the star component, so we add the declaration to the same Angular module that declares the product list component. We define the nested component in the declarations array of the ngModule decorator, and as always, define what we need by adding an import statement. Let's jump right back to our demo. Our star component is now shown here on the right. We want to use our star component in the product list template that is here on the left. In the table data element, we want to replace the display of the star rating number with our star component. To do that we simply replace the binding with our directive. Now our product list template will display our stars here. Next, we need to tell Angular where to find this directive. Since we only have one angular module, we'll add the declaration for the nested component there. Add the star component to the declarations array passed into the ngModule decorator. These are the same steps we followed earlier in this course to use a component as a directive. Nothing new here so far; let's see what we did. We have stars. Yes! Hmm, but we see five of them. Let's clear out the filter. Yes, we see five every time. Hover over the stars, and we see our hardcoded star rating is 4. It seems that our star width is not being set. Let's look at the code again. We set the starWidth property in the ngOnChanges method when the OnChanges lifecycle event occurs. But the OnChanges lifecycle event never occurs because OnChanges only watches for changes to input properties. We don't have any input properties, so we have two problems here. Our OnChanges event does not fire, and we don't currently have a way to get the correct rating number from the container. Let's see how input properties can solve both of these issues.
Passing Data to a Nested Component Using @Input
If a nested component wants to receive input from its container, it must expose a property to that container. The nested component exposes a property it can use to receive input from its container using the aptly named Input decorator. We use the Input decorator to decorate any property in the nested component's class. This works with any property type, including an object. In this example, we want the rating number passed in to the nested component, so we mark that property with the Input decorator. The container component then passes data to the nested component by setting this property with property binding. When using property binding, we enclose the binding target in square brackets. We set the binding source to the data that the container wants to pass to the nested component. In this example, the product list template passes the product's star rating number. The only time we can specify a nested component's property as a property binding target on the left side of an equals is when that property is decorated with the Input decorator. So in this example, the product list template can bind to the rating, but not the star width. Let's give this a try. The star component wants to expose the rating property to its container so the container can provide the rating number. We'll add the Input decorator then to the rating property. The Input decorator is a function, so we add parentheses. We don't need to pass anything to this function, so that's it. And let's remove this default value. Now that we can get the value from the container, we don't need that here. In our example, we decorate only one property of the nested component with the Input decorator, but we are not limited to one. We can expose multiple input properties as needed. In the containers template, we use property binding and define the nested component's input property as the target of the binding, then we set the binding source to the value we want to pass into the nested component. In this example, we pass the product's star rating. That's it. The product.starRating property is now bound to the rating input property of the nested component. Any time the container data changes, the OnChanges lifecycle event is generated and the star width is recalculated. The appropriate number of stars are then displayed. Let's check it out in the browser. That looks better. But what if we want to send data back from our nested component to our container? Let's look at that next.
Passing Data from a Component Using @Output
We just saw how the container passes data to the nested component by binding to a nested component property that is decorated with the Input decorator. If the nested component wants to send information back to its container, it can raise an event. The nested component exposes an event it can use to pass output to its container using the aptly named Output decorator. We can use the Output decorator to decorate any property of the nested component's class. However, the property type must be an event. The only way a nested component can pass data back to its container is with an event. The data to pass becomes the event payload. In Angular, an event is defined with an EventEmitter object. So here we create a new instance of an EventEmitter. Notice the syntax here. TypeScript supports generics. If you are not familiar with generics, this syntax allows us to identify a specific type that the object instance will work with. When creating an EventEmitter, the generic argument identifies the type of the event payload. If we want to pass a string value to the container in the event payload, we define string here. If we want to pass an integer, we define number here. If we want to pass multiple values, we can specify an object here. In this example, we define a notify event with a string payload. When the user clicks on the stars, the star component receives that click event. We use event binding in the star component template to call the star component's onClick method. In the onClick method, we use the notify event property, and call its emit method to raise the notify event to the container. If we want to pass data in the event payload, we pass that data into the emit method. In this example, the onClick method raises the notify event and sets the event payload to a string message. The container component receives that event with the specified payload. In the container component's template, we use event binding to bind to this notify event and call a method. We access the event payload using $event. The only time we can specify a nested component's property as an event binding target on the left side of an equals is when that property is decorated with the Output decorator. Lastly, the container component provides the method to execute when the notify event occurs. Since the event payload is a string, this function takes in a string. Here we can perform any desired action. Hmm, lots of moving parts here. Let's jump into the code and try it out. We are back in the sample application looking at the star component. What is our goal? When the user clicks on one of the rating stars, we want to display that rating in the header. This feature may not be incredibly useful, but it will demonstrate how to pass events from our nested child component to the parent container component. So our first task is to respond to the user's click event on the star rating. We do that using event binding in the star component's template. We'll bind the div element's click event to an onClick method in the StarComponent class. Next, let's implement this onClick method in the component. Hmm. Somehow in this method we need to send out a notification of this click to the container. For now, let's just log out that the rating was clicked. In this example, we use the ES2015 backticks to define a template string. This allows us to embed the rating directly into this string. Let's try it out in the browser. Open the Developer Tools and click on the rating stars. We see it logged to the console here. Going back to the code, we need to send out a notification to the container when the user clicks on the star rating of the nested component. Recall from the slides how the nested component communicates with its container? It uses an event with the Output decorator. Let's define an event property in the nested component. We'll call it ratingClicked. Since this must be an event, we define the type of this property to be EventEmitter. We'll use the provided quick fix to add EventEmitter to the import statement. We want to pass a string to the container as part of this event, so we specify string as the generic argument. We then set the ratingClicked property to a new instance of EventEmitter. This sets up our event. Next, we decorate our event property with the Output decorator so that the container can respond to this event. The Output decorator is a function, so we add parentheses. In this example, we are decorating only one property of the nested component with the Output decorator, but we are not limited to one. We can expose multiple output properties as needed, as long as they're events. Here in our onClick method, we want to raise this event to the container and pass along our message. We use the event property, and call its emit method passing in the desired string. The emit method raises the event. Now that we are raising this ratingClicked event to our container, how does the container listen for and respond to this event? It uses event binding. In this example, our container is the product list component. In the product list component template, we bind to the event raised from the star component using event binding. For event binding, we use parentheses and specify the name of the event to listen for. We want to listen for the ratingClicked event raised by the star component. Now, what do we want to do when the event is raised? We'll need to define a method in the ProductListComponent Let's call that method onRatingClicked. Recall that we are passing a string when raising this event, so let's pass that string into our onRatingClicked method. We do that using $event. Dollar event passes along any information associated with a generated event. Next, we need to write the code for this method in the ProductListComponent class. Our template is expecting that we have a method called onRatingClicked and is passing a string message with the event. Our method returns no value, so we define the return type as void. Now that we have the message from the event, what do we want to do with it? Our goal was to display it on the page title, so we'll modify the page title to display Product List, and the message from the nested star component. Okay, yeah, that is not a very real-world example, but I wanted to keep this as straightforward as possible. We'd have a better example if the nested component contained an input box and we could pass the user's input in the event payload. But you get the general idea here. Let's see how this works in the browser. Click on the star rating, and we see the page title changed to display the message received from the nested component. Success! We just saw how the container passes data to the nested component by binding to a nested component property that is decorated with the Input decorator, and how the nested component uses an event property decorated with the Output decorator to raise events. We can think of the properties decorated with the Input or Output decorators as the public API of the nestable component. Everything else in the component is encapsulated, and only accessible to the component's template and class. Let's finish up this module with some checklists we can use as we build nestable components.
Checklists and Summary
We build a nested component using the same techniques we used to build any other Angular component. We won't review that again here. Let's instead lay out a checklist for inputs and outputs, decorate a nested component property with the input decorator any time it needs input data from its container. Any type of component property can be decorated with the input decorator. Don't forget the @ prefix, and since the Input decorator is a function, follow it with open and closing parentheses. Decorate a nested component property with the Output decorator any time it needs to raise events, and optionally pass information back to its container. Only properties of type EventEmitter should be marked with the Output decorator. Use the EventEmitter's generic argument to specify the type of the event payload, and use the new keyword to create an instance of the event emitter. Don't forget the @ prefix, and since the Output decorator is a function, suffix it with open and closing parentheses. In a container component's template, use the nested component as a directive. For the name of the directive, see the nested component's selector property. Use property binding to pass data to the nested component. Any property of the nested component that is decorated with the Input decorator can be used as a binding target. Use event binding to respond to events from the nested component. Any event that is decorated with the Output decorator can be used as the binding target. And use $event to access the event payload passed from the nested component. To learn more about nested or child components, check out the Angular Component Communication course, here in the Pluralsight library. This module was all about nested components. We began by building a component that could be nested within other components. We then walked through how to use the nested component within a container. We saw how to pass data into the nested component by binding to a property marked with the Input decorator. And we discovered how to pass data out of the nested component by raising an event marked with the Output decorator. In this module, we built the star component and nested it within the product list component. We can reuse this component and any other component of the application, such as the product detail component. Next up, let's check out how to build an Angular service so we won't need hardcoded product data in our component.
Services and Dependency Injection
Components are great and all, but what do we do with data or logic that is not associated with a specific view or that we want to share across components? We build services. Welcome back to Angular: Getting Started, from Pluralsight. Deborah Kurata here, at your service, and in this module, we create a service and use dependency injection to inject that service into any component that needs it. Applications often require services such as a product data service or a logging service. Our components depend on these services to do the heavy lifting. Wouldn't it be nice if Angular could serve us up those services on a platter? Well, yes it can. But what are services exactly? A service is a class with a focused purpose. We often create a service to implement functionality that is independent from any particular component, to share data or logic across components or encapsulate external interactions such as data access. By shifting these responsibilities from the component to a service, the code is easier to test, debug, and reuse. In this module, we start with an overview of how services and dependency injection work in Angular, then we'll build a service, we'll register that service, and we'll examine how to use the service in a component. We currently have several pieces of our application in place, but we hardcoded our data directly in the product list component. In this module, we'll shift the responsibility for providing the product data to a product data service. Let's get started.
How Does It Work?
Before we jump into building a service, let's take a look at how services and dependency injection work in Angular. In this diagram, our service is here and our component that needs the service is here. There are two ways our component can work with this service. The component can create an instance of the service class and use it. That simple, and it works. But the instance is local to the component, so we can't share data or other resources, and it will be more difficult to mock the service for testing. Alternatively, we can register the service with Angular. Angular then creates a single instance of the service class, called a singleton, and holds onto it. Specifically, Angular provides a built-in injector. We register our services with the Angular injector, which maintains a container of created service instances. The injector creates and manages the single instance, or singleton, of each registered service as required. In this example, the Angular injector is managing instances of three different services, log, math, and myService, which is abbreviated SVC. If our component needs a service, the component class defines the service as a dependency. The Angular injector then provides, or injects, the service class instance when the component class is instantiated. This process is called dependency injection. Since Angular manages the single instance, any data or logic in that instance is shared by all of the classes that use it. This technique is the recommended way to use services because it provides better management of service instances, it allows sharing of data and other resources, and it's easier to mock the services for testing purposes. Now let's look at a more formal definition of dependency injection. Dependency injection is a coding pattern in which a class receives the instances of objects it needs, called its dependencies, from an external source rather than creating them itself. In Angular, this external source is the Angular injector. Now that we've got a general idea of how services and dependency injection work in Angular, let's build a service.
Building a Service
Are we ready to build a service? Here are the steps. Create the service class, define the metadata with a decorator, and import what we need. Look familiar? These are the same basic steps we followed to build our components and our custom pipe. Let's look at the code for a simple service. Here is the class. We export it so the service can be used from any other parts of the application. This class currently has one method, getProducts. This method returns an array of products. Next, we add a decorator for the service metadata. When building services, we often use the Injectable decorator. Lastly, we import what we need, in this case Injectable. Now let's build our service. I've opened the folder for the sample application in the editor once again. Since our service will only provide product data, we'll add it to the products folder. We'll create a new file and call it product.service.ts to follow our naming conventions. We're then ready to create the service class. I bet you can do it in your sleep by now. Export class, and the class name. Since the service provides products, we'll call it ProductService. Next, we decorate the class with the Injectable decorator and we'll add the import statement for that decorator. Now that we have the structure in place, we can add properties or methods to the class as needed. Unless marked private or protected, the properties and methods defined in the class are accessible to any class that uses this service. For our product service, we want a getProducts method that returns the list of products. We strongly type this return value using our IProduct interface, so we need to import this interface. In the next module, we'll see how to retrieve the products using HTTP. For now, we'll hardcode them in here. If you're coding along, feel free to copy some products from the products.json file in the api, products folder. Notice that we have no properties defined in this class, so we are not using this particular service to share data, we are using it to encapsulate the data access features. By using this service to provide the list of products, we take the responsibility for managing the data away from the individual component. That makes it easier to modify or reuse this logic. So we're done with our service for now. How do we use it? Well, a service is just really an ordinary class until we register it with an Angular injector. Let's do that next.
Registering the Service
As we illustrated in this diagram, we register the service with the Angular injector, and the injector provides the service instance to any component that injects it using the constructor. The injector represented here is the root application injector. But wait, there's more. In addition to the root application injector, Angular has an injector for each component, mirroring the component tree. A service registered with the root application injector is available to any component or other service in the application. A service registered with a specific component is only available to that component and its child or nested components. For example, if a service is registered with the for injection in the product list component and its child, the star component. So, when should you register your service with a root injector versus a component injector? Registering a service with a root injector ensures that the service is available throughout the application. In most scenarios, you'll register the service with the root injector. If you register a service with a component injector, the service is only available to that component and its child or nested components. This isolates a service that is used by only one component and its children, and it provides multiple instances of the service for multiple instances of the component. For example, we have multiple instances of the star component on the Product List page, one for each row. If we had a service that tracks some settings for each star component instance, we would want multiple instances of the service, one for each instance of the component. But this is not a common scenario. With that, the next question is, how? How do we register a service? That depends on which injector we use. We register the service with the root application injector in the service. We pass an object into the Injectable decorator and set the providedIn property to root. We can then access this service from any component or other service in the application. We want to use our product service in several components, so we'll register it with the root application injector. Let's do that now. Here in the service, we add the providedIn property to the Injectable decorator and set it to root. An instance of the product service is then available for injection anywhere in the application. But what if we only wanted to access this service from one component instead? For most scenarios, we'll register our service in the service using the providedIn property. The service is then available to the entire application. To register our service for a specific component, such as the product list component, we register the service in that component, like this. The service is then available to the component and its child components. Note that the providedIn feature is new in Angular version 6. In older code, you'll see the service registered in a module like this. The syntax is still valid, however, the recommended practice is to use the new providedIn feature in the service instead. This provides better tree shaking. Tree shaking is a process whereby the Angular compiler shakes out unused code for smaller deployed bundles. We'll talk more about tree shaking later in this course. Now that we've registered the service, let's see how to inject the service so we can use it.
Injecting the Service
Here again is our diagram. In the last clip we saw how to register the service with the Angular injector. Now we just need to define it as a dependency so the injector will provide the instance in the classes that need it. So, how do we do dependency injection in Angular? Well, the better question is, how do we do dependency injection in TypeScript? The answer is, in the constructor. Every class has a constructor that is executed when an instance of the class is created. If there is no explicit constructor defined for the class, an implicit constructor is used. But if we want to inject dependencies such as an instance of a service, we need an explicit constructor. In TypeScript, a constructor is defined with a constructor function. What type of code normally goes into the constructor function? As little as possible. Since the constructor function is executed when the component is created, it is primarily used for initialization and not for code that has side effects or takes time to execute. We identify our dependencies by specifying them as parameters to the constructor function, like this. Here we define a private variable to hold the injected service instance. We create another variable as the constructor parameter. When this class is constructed, the Angular injector sets this parameter to the injected instance of the requested service. We then assign the injected service instance to our local variable. We can then use this variable anywhere in our class to access service properties or methods. This is such a common pattern that TypeScript defined a shorthand syntax for all of this code. We simply add the accessor keyword, such as private here, to the constructor parameter. Then this is a shortcut for declaring this variable, defining a parameter, and setting the variable to the parameter. Neat. You'll see this technique used throughout the Angular documentation and other code examples. Ready to give it a try? We want to use our service to get products in the product list component, so we'll define our product service as a dependency in the product list component. We don't have to add the providers array here because the product service is already registered. All we need is a constructor, and we already have one here. We'll use the shorthand syntax to define the dependency, private productService. Then, because we are using TypeScript, we type colon and the type, which is ProductService. Note that the accessor doesn't have to be private. The shorthand syntax works with public and protected as well. So now we have a syntax error here. I bet you know why. Yep, we need to import ProductService so we can use it as the data type here. Now, when an instance of the product list component is created, the Angular injector injects in the instance of the ProductService. We are at the point now where we can actually use the ProductService. First, let's delete the hardcoded products from here. We'll instead get them from the service. Now the question is, where should we put the code to call the service? One thought might be to put it in the constructor. But ultimately our product service will go out to a back-end server to get the data. We don't want all of that executed in the constructor. Other thoughts? Remember our discussion about lifecycle hooks? Earlier in this course, we said that the OnInit lifecycle hook provides a place to perform any component initialization, and it's a great place to retrieve the data for the template. Let's use the OnInit lifecycle hook. We want to set the product's property to the products returned from our service. To call the service, we use our private variable containing the injected server instance. We then type a dot and the name of the method we want to call. Notice how IntelliSense helps us with all of this. There is a small problem with our code at this point. Since the constructor is executed before the ngOnInit, the list of products will be empty here. We want to set our filtered list of products to this list of products, so we need to move this line of code to the ngOnInit as well. Let's make one more little change. Let's remove the default listFilter value so we'll see all of the products in the list. We should be all set to see our result in the browser. And here are our products. Yay! Notice that we have more products displayed now because I hardcoded more products into the service. Let's finish up this module with some checklists we can use as we build our services.
Checklists and Summary
We build a service using the same techniques as when we build components and custom pipes. We start by creating the service class, we specify a clear class name appropriate for the services it provides, use PascalCasing where each word of the name is capitalized, append Service to the name, and don't forget the export keyword. We then decorate the service class with the Injectable decorator. Don't forget the app prefix, and since the decorator is a function, follow it with open and closing parentheses, and be sure to define the appropriate imports. The first step to registering a service is to select the appropriate level in the injector hierarchy that the service should be registered. Use the root application injector if the service is shared throughout the application. If only one component and its children need the service, register it with that component's injector. Pick one or the other, not both. Register a service with the root injector using the Injectable decorator of the service, set the providedIn property to root, register a service for a specific component and its children using its component decorator, use the provider's property to register the service, and any class that needs the service, specify the service as a dependency. Use a constructor parameter to define the dependency. The Angular injector will inject an instance of the service when the component is instantiated. This module was all about services. We began with an overview of how services and dependency injection work in Angular, then we walked through how to build a simple service. We defined how and where to register the service with Angular, and we examined how to define the service as a dependency so the service instance is injected into any class that needs it. In this module, we built the product data service, so our product list component no longer has hardcoded products. Next up, we'll see how to modify the service to retrieve data using HTTP.
Retrieving Data Using HTTP
The data for our application is on a server somewhere, in the cloud, at the office, under our desk. How do we get that data into our view? Welcome back to Angular: Getting Started, from Pluralsight. My name is Deborah Kurata, and in this module, we learn how to use HTTP with observables to retrieve data. Most Angular applications obtain data using HTTP. The application issues an HTTP GET request to a web service. That web service retrieves the data, often using a database, and returns it to the application in an HTTP response. The application then processes that data. In this module, we begin with an introduction to observables and the reactive extensions. We then examine how to send an HTTP request and map the result to an array. We add some exception handling, and we look at how to subscribe to observables to get the data for our view. We finished the first cut of our product data service in the last module, but it still has hardcoded data. Now, we'll replace that hardcoded data with HTTP calls. Let's get started.
Observables and Reactive Extensions
Sending an HTTP Request
We often encapsulate the data access for our application into a data service that can be used by any component or other service that needs it. In the last module, we did just that, but our product data service still contains a hardcoded list of products. We instead want to send an HTTP request to get the products from a back-end web server. Angular provides an HTTP service that allows us to communicate with a back-end web server using the familiar HTTP request and response protocol. For example, we call a get method of the HTTP service, which in turn sends a GET request to the web server. The web server response is returned from the HTTP service to our product data service as an observable. What does this look like in code? This is the ProductService we built in the last module, modified to retrieve the products using Angular's HTTP service. Let's walk through this code. First, we specify a URL to the products on the web server. This defines where we send our HTTP requests. Note that this URL is shown for illustration purposes only and is not a real URL. Next, we add a constructor. Recall from the last course module that we use a constructor to inject dependencies. In this case, we need Angular's HTTP service, so we inject it here. Recognize the syntax? It creates a private HTTP variable and assigns the injected service instance to that variable. And since we are strongly typing this variable to HttpClient, we import HttpClient from the angular/common/http package here. Recall also from the last course module that before we can inject a service in as a dependency, we need to register that service's provider with Angular's injector. The HTTP service provider registration is done for us in the HttpClientModule included in the angular/common/http package. To include the features of this external package in our application, we add it to the imports array of our application's Angular module, AppModule. Recall that the declarations array is for declaring components, directives, and pipes that belong to this module. The imports array is for pulling in external modules. Now our Angular module illustration looks like this. We declare our components, we declare the directives and pipes that those components require, and we import the external modules that we need. Going back to the product service, in getProducts, we use the injected http service instance and call the get method, passing in the desired URL. The HTTP service then sends a GET request using the specified URL. Often, the data returned from the back-end server is in JSON format. Lucky for us, the HTTP client get method makes it easy to receive that JSON data. We specify the expected type of response by setting the get method's generic parameter. Since we are expecting an array of product objects, we set the generic parameter to IProduct array. The get method then automatically maps the response object returned from the back-end server to the defined type, so we don't have to. Does this generic syntax look familiar? We used it earlier in this course to define the event payload when passing event information from a nested component to its container. We aren't quite finished. What does our method now return? Since we are using strong typing, we should have a function return value here. We define the get method generic parameter as IProduct array. So will that be what we get back? Not quite. The getProducts method returns an observable of IProduct array. That's because the HTTP calls are asynchronous operations. And it's important to note that HTTP calls are single asynchronous operations, meaning that the observable sequence returned from the get method contains only one element. This element is the HTTP response object mapped to the type specified in the generic parameter. Notice that the observable also takes advantage of generics to define the type of data it is observing in the observable sequence. In our case, it's the array of products. Now let's add HTTP to our product service.
Demo: Sending an Http Request
In this demo, we'll send an HTTP request to get the products for our Product List page. We are looking at our application's Angular module we called AppModule. Recall from the slides that Angular registers its HTTP service provider in an Angular module called HttpClientModule. In our application's Angular module, we import that HttpClientModule. We then pull that module into our application by adding HttpClientModule to the imports array here. Now we can inject the Angular HTTP service into any class that needs it. Here is the product data service we created in the last module with all of the hardcoded data. We want to modify our product service to get the product data using HTTP. Let's start at the top with the import statements. We want Angular to provide us an instance of the HTTP client service, so we identify it as a dependency in the constructor. We don't have a constructor yet, so let's add that first. Then we specify the parameter. Angular will then inject the HttpClient service instance into this variable. Now we need to identify the location of our web server. Hmm, this doesn't look like a valid URL to a web server, and to keep things simple, the demonstration reads the data from a local JSON file that was provided with the starter files. That way, we don't need to set up an actual web server. However, we do need to define the location of this JSON file so that the Angular CLI can find it when it serves up the application. I've already done this in the provided starter files. Here in the angular.json file, we can see the path here in the assets array. To change this code to work against a web server, simply change this URL to point to an appropriate web server, and of course, you need to write the server-side code to return the list of products. Ah, it's finally time to delete our hardcoded data, so let's delete the hardcoded products from the getProducts method. Gone. We'll call the HTTP get method here instead, passing in the defined URL. Since we expect the response to be a JSON structure containing an array of products, we set the get method generic parameter to IProduct array. When we get a response back, this method will then automatically map the returned response to an array of products. We'll need to change the return type as well. Now this method returns an observable of IProduct array. We can't try this out at this point because we are not yet subscribing to the observable returned from the service. Plus, looking at the console, we have a syntax error where we are calling this method since we changed the return type. Let's add some exception handling first, then modify the product list component to subscribe to the observable list of products.
As you can imagine, there are many things that can go wrong when communicating with a back-end service, everything from an invalid request to a lost connection. So let's add some exception handling. There are two key observable operators that we'll need. Tap taps into the observable stream and allows us to look at the emitted values in the stream without transforming the stream. So tap is great to use for debugging or logging. CatchError catches any error. We import them both from rxjs/operators. As we discussed earlier in this course, to use these operators, we access the pipe method of the observable. We then pass in the operators, separated by commas. Here, the tap operator logs the retrieved data to the console. That way we can verify it's been retrieved correctly, and the catchError operator takes in an error handling method. The error handling method gets one parameter, the error response object. In the error handling method, we can handle the error as appropriate. We can send the error information to a remote logging infrastructure or throw an error to the calling code. Now let's add exception handling to our product service. We are back in the editor with the product service, just as we left it. This code is not really complete without the exception handling, so we'll add the appropriate imports for both the catchError and tap operators. We then call the pipe method and pass in both of the operators, just like we saw on the slide. I'll paste in the handleError method, and we need the import for HttpErrorResponse and throwError. In this method, we handle logging our errors any way we want. For our sample application, we'll just log to the console and throw an error to the calling code. So our getProducts method is complete. We can add other methods here to post or put data as well, but we still have that syntax error here, and we can't see the result of our hard work because we are not yet subscribing to the observable. Let's do that next.
Subscribing to an Observable
Observables are lazy. An observable doesn't start emitting values until subscribe is called. So when we are ready to start receiving values in our component, we call subscribe. The subscribe method takes up to three arguments, each providing a handler function. The first argument is often called a next function because it processes the next emitted value. Since observables handle multiple values over time, the next function is called for each value the observable emits. The second argument is an error handler function, and yep, you guessed it, it executes if there is an error. In some cases, we want to know when the observable completes, so observables provide an optional third handler that is executed on completion. The subscribe function returns a subscription. We could use that subscription to call unsubscribe and cancel the subscription if needed. Now that our product data service is returning an observable, any class that needs product data, such as our product list component, can call our service and subscribe to the returned observable, like this. As we stated earlier, an observable doesn't start emitting values until subscribe is called, so this line of code calls the product data service getProducts method and kicks off the HTTP get request. It is then set up to asynchronously receive data and notifications from the observable. The first function passed to the subscribe method specifies the action to take whenever the observable emits an item. The method parameter is that emitted item. Since HTTP calls are single async operations, only one item is emitted, which is the HTTP response object that was mapped to our product array in the service. So the parameter is our array of products. This code then sets the local product's property to the returned array of products. The second function is executed if the observable fails. In this example, it sets a local errorMessage variable to the returned error. A third function, not used here, specifies the action to take when the observable ends with a completed notification. The third function is rarely used when working with HTTP requests, since they automatically complete after emitting the single response, and we aren't using the return value here since we have not provided an option for the user to cancel the request. Let's give this a try.
Demo: Subscribing to an Observable
In this demo, we modify our component to subscribe to the observable provided by the product service. Here in the product list component, we see a syntax error. Type Observable of IProduct array is not assignable to IProduct array. Now that we've changed the product service to return an observable, we cannot assign the result to our product property directly. Rather, we subscribe to the returned observable. When the observable emits the data, we set our product property to the returned array of products. But things don't always go as expected. To handle any errors, let's add an errorMessage property. If our request for products fails, our errorMessage property is set to the error. What is this any syntax? This is a casting operator. We are casting the error returned from the observable to the any data type. Are we ready to try it out? Oh, we have no data. Why is that? Let's look again at our code. Recall that we said that HTTP is an asynchronous operation. What does that mean exactly for this code? Let's take it by the numbers. Angular first initializes the component and executes the ngOnInit method. We call the getProducts method of the product service. The product service returns an Observable of IProduct array. We subscribe to that observable and the HTTP GET request is submitted. This is the asynchronous operation. This code is then complete, and we execute the next line, which is setting our filtered products. But at this point, our products property is not yet set, so the filtered products is empty and we see no data. At some future point in time, the service receives the HTTP response from our request. The response data is mapped to an array of products and the observable emits that mapped data to any subscribers. Our subscriber receives the emitted data and assigns our product property to the emitted array of products. But since we are binding to the filteredProducts property, we are not notified that we now have the retrieved list of products. There are several ways we can handle this. One option is that we can move this line into the subscribe function, that way we won't assign the filteredProducts until the retrieved set of products are emitted from the service. Right now, the first argument passed into the subscribe method is a single line function. To pass in a multiple line function, we need to add curly braces, like this, then we can add additional lines into the function, like this line. Now our filteredProducts property is set after the products property is set to our list of products. Let's check it out. There are our products. Success. We have more products here now because we are retrieving them from the provided product.json file. Sweet. If we open the F12 Developer Tools, we see that data in the console, here. So the recommended way to use HTTP is to encapsulate it in a service, like our product service, then expose an observable for use by any class that needs product data. The class simply subscribes to the observable and waits for data or a notification. Let's finish up this module with some checklists we can use as we work with HTTP and observables.
Checklists and Summary
Before we can use Angular's HTTP client service, some setup is required. We need to ensure that the service provider is registered with the Angular injector. This registration is done for us in the HttpClientModule, so all we need to do is pull the HttpClientModule into our application. We do this by adding HttpClientModule to the imports array of one of our application's Angular modules. Build a data access service to wrap HTTP requests. In that data service, specify the needed imports, define a dependency for the Angular HTTP client service using a constructor parameter. Create a method for each HTTP request. In the method, call the desired HTTP method, such as get, and pass in the URL to the desired server. Use generics to specify the response return type. This will transform the raw HTTP response to the specified type. And do error handling as desired. And any class that needs data from a data service, call the subscribe method to subscribe to the observable. Provide a function to execute when the observable emits an item. This often assigns a property to the returned JSON object. And if that property is bound to a template, the retrieved data appears in the view, and add an error function to handle any returned errors. For more information on using HTTP with an Angular application, including create, read, update, and delete or CRUD operations, see the Angular: Reactive Forms course. It demonstrates how to display, edit, and save data in Angular. For more information on observables and RxJS, see this Play by Play course. This module was all about HTTP and observables. We began with an overview of observables and reactive extensions. We examined how to build a data access service that sends requests for data over HTTP. We walked through how to set up some basic exception handling, and we saw how to subscribe to the returned observable and ultimately display the resulting data in the view. We've now removed the hardcoded data from the product data service, yay, and instead retrieved the data using HTTP. In our sample application, we are using HTTP to retrieve the data from a local JSON file, but the techniques are the same for retrieving data from a back-end service. Next up, we'll see how to display multiple pages with navigation and routing.
Navigation and Routing Basics
A single view does not an application make. Welcome back to Angular: Getting Started, from Pluralsight. My name is Deborah Kurata, and in these next two modules, we define routes to navigate between multiple views in our application. Users like to have all of the information they need at their fingertips, so our applications often provide multiple sets of data, in multiple layouts, across multiple views. Routing provides a way for the user to navigate between those many views of the application, whether there are 5, 10 or hundreds. In this module, we start with an overview of how routing works in Angular, we examine how to configure routes and tie routes to actions, and we define where to place the routed component's view. Currently, our app component embeds our product list component as a nested component. We instead want to define a set of routes so the user can navigate to the Welcome view, Product List view or Product Detail view. We've already built the product list component, and I've provided the welcome component with the starter files in the GitHub repository for this course. As part of the demos in this module, we'll build the shell for the product detail component. When we're finished with this module, we'll have a simple application that routes to multiple views.
Generating Code and Handling Undefined
Before we jump into routing, we need more components so we have something to route to. I provided the welcome component as part of the starter files. Let's build the product detail component. The product detail component should ultimately look like this, but we want to focus on routing, not building another component. So let's just create a shell for the product detail component so we can route to it. We could use the Explorer and create new component class and template files manually, or we could use the Angular CLI to automatically create those files. We are going to spend more time on the CLI later in this course, but if you want to try it out now, start by opening a terminal window. I'll use the integrated terminal built in to VS Code. If you don't already have the CLI installed, install it now using npm install -g, for global, @angular/cli. I already have it installed, so won't install it again. Next, we type ng for the Angular CLI, g for generate, c for component, and the name of our component. Since we want to create this component under the products folder, we add the path to the desired component name, products/product-detail, following our naming convention. That's all that is required. By default, however, Angular will create a new folder for this component. For the sample application, we want the contents of our product folder to be flat. We'll use the --flat option to achieve this. We'll learn much more about the Angular CLI later in this course. For now, press Enter, and the Angular CLI creates the component files for us, and it updated our Angular module. Yay! We can see here in the Explorer that it created the style sheet and the CSS file, the template in an HTML file, and the class in a TypeScript file. It even created the start of a unit test for us. Let's check out the generated template. Not much here. We'll replace the generated HTML with some placeholder text and use interpolation to bind to a page title. Here, we include the product's name in that title. If we retrieve the product to display using HTTP, which we most likely will, we could have a problem with this code. Any idea what could go wrong here? Recall from the last module that HTTP is asynchronous. This means that our page could attempt to display the product's name before the product is retrieved. At that point, the product property is undefined, so we would get a runtime error telling us it can't read the property product name of undefined. How do we ensure this error doesn't happen to us? There are several common ways to prevent undefined property errors. We could use the safe navigation operator defined with a question mark. The safe navigation operator guards against null and undefined values when navigating an object's properties. If the product object is null or undefined, the safe navigation operator simply returns null and does not attempt to access the productName property, hence we don't see an undefined property error. The safe navigation operator is great, but it is not always the best option. For example, it does not work when used with the ngModel two-way binding. It can also be quite tedious when we display many properties, like in our detail template. So there is another option. We can use ngIf and check for the existence of the product object. We add the ngIf to an HTML element that encloses all references to the product object, then we don't need to use the safe navigation operator. Next, let's look at the TypeScript file. The generated component class has all of the basic syntax in place. All it needs is the page title and product properties that we are referencing in the template. Notice that the CLI generated the selector property here. The selector property is only required if the component will be nested within another component. We won't need to nest this component, we'll instead display the component's view as part of the routing, so let's delete the selector. Now let's check out the Angular module. Notice that the CLI added the appropriate import and declared our new component. Very nice! Every time we add a component to the application, we need to declare the component in an Angular module. We currently have only one Angular module, AppModule, so the product detail component and the welcome component must be added to the declarations array for the AppModule. If we use the Angular CLI, as we did in this example, it automatically adds the appropriate declarations. Cool! If we don't use the CLI, like for our welcome component, we need to add the component to the declarations array ourselves. Let's do that now. We add the welcome component to the declarations array, then add the associated import statement. Now that we have the basic components in place, we are ready to add routing to our application.
How Routing Works
An Angular application is a single-page application. That means all of our views are displayed within one page, normally defined in the index.html file. So each of the 5, 10 or hundreds of views take turns appearing on that one page. How do we manage which view to display when? That's the purpose of routing. We configure a route for each component that wants to display its view on the page. As part of our application design, we provide a menu, a toolbar, buttons, images or data links that allow the user to select the view to display. We tie a route to each option or action. When the user selects the option or performs the action, the associated route is activated. Activating a component's route displays that component's view. So for example, the user selects a menu option to display the product list. The product list route is activated, and it displays its view. Hmm. Let's look at that process again with an illustration. Here is the menu that we'll add to our sample application. We tie a route to each menu option using a built-in router directive called routerLink. When the user clicks on the Product List option, for example, the Angular router navigates to the product's route. The browser's location URL changes to match this path segment, and we see /products appear in the address bar. By default, Angular uses HTML5 style URLs, which don't require the hash symbol to indicate local navigation. By using the HTML5 style URLs, you need to configure your web server to perform URL rewriting. How this is done depends on your web server. See the documentation for your web server on how to configure URL rewriting. Angular also supports hash style routing, which does not require URL rewriting. We'll look at how to use hash style routing later in this module. When the browser's URL changes, the Angular router looks for a route definition matching the path segment, products in this example. The route definition includes the component to load when this route is activated. In this case, the product list component. The Angular router then loads the component's template. Where does it display this template? Where we specified with the built-in routing directive called router-outlet, and the product list appears. So, that's how routing works. We'll examine these steps in further detail and try them out in demos as we journey through this course module.
Routing is component-based, so we identify the set of components that we want to provide as routing targets and define a route for each one. Let's see how this is done. An Angular application has one router that is managed by Angular's router service, and we know that before we can use the service we need to register the service provider in an Angular module. Similar to the HTTP module, Angular provides a RouterModule in the angular/router package that registers the router service provider. To include the features of this external module in our application, we need to add it to the imports array of our application's Angular module. In addition to registering the service provider, the RouterModule also declares the router directives. In the last clip, we mentioned two router directives, routerLink and router-outlet. By importing the RouterModule, our component templates can use these or any other router directives. RouterModule also exposes the routes we configure. Before we can navigate to a route, we need to ensure that the routes are available to the application. We do this by passing the routes to RouterModule, like this. We call the RouterModule's forRoot method and pass our array of routes to that method. This establishes the routes for the root of our application. If we want to use hash style routes instead of HTML5 style routes, we change this code to set useHash, as shown here. With that, we are ready to configure some routes. The router must be configured with a list of route definitions. Each definition specifies a route object. Each route requires a path. The path property defines the URL path segment for the route. When this route is activated, this URL path segment is appended to the URL of our application. The user can type in or bookmark the resulting URL to return directly to the associated component's view. In most cases, we also specify a component, which is the component associated with the route. It is this component's template that is displayed when the route is activated. These are all examples of route definitions. The first route simply maps the specific URL path segment to a specific component. So this URL displays the template from the ProductListComponent. The :id in the second route represents a route parameter. The Product Detail page displays the detail for one product, so it needs to know which product to display. The product detail component reads the ID from this path segment and displays the defined product. We can define any number of parameters here, separated with slashes. What does this route do? Yep, this URL displays the template from the welcome component. This one defines a default route. The redirect here translates the empty route to the desired default path segment, in this example, the welcome route. A redirect route requires a pathMatch property to tell the router how to match the URL path segment to the path of a route. We only want this default route when the entire client-side portion of the path is empty, so we set the pathMatch to false. The asterisks in the last route denote a wildcard path. The router matches this route if the requested URL doesn't match any prior paths defined in the configuration. This is useful for displaying a 404 Not Found page or redirecting to another route. A few things to note here. There are no leading slashes in our path segments, and the order of the routes in this array matters. The routers uses a first match win strategy when matching the routes. This means that more specific routes should always be before less specific routes, such as the wildcard route. Ready to try this out?
Demo: Configuring Routes
In this demo, we configure the basic routes for our application. We are back in the sample application with the index.html file open. The first step to set up routing is to define a base element in the head tag of the index.html file. Notice that the Angular CLI already did that for us here. This element tells the router how to compose the navigation URLs. Since the app folder is the application route, we'll set the href for the base tag to slash. Now we are ready to configure the route definitions. For that, we go to our Angular module, add the appropriate import statement, then add RouterModule to the imports array. This registers the router service provider, declares the router directives, and exposes the configured routes. How does the RouterModule know about our configured routes? We pass them in to the RouterModule by calling the forRoot method. We then configure the routes here by passing them in using an array. Let's start with the product routes. For each route, we specify the path and a reference to the component. The template defined in the specified component will display when the router navigates to this path. Next we add the route to display our welcome page. We'll set the path to welcome and specify the WelcomeComponent. When the application loads, we want to default to the template from the WelcomeComponent, so we'll specify a default route that redirects to our WelcomeComponent, and let's define a wildcard path in case the requested URL doesn't match any prior paths defined in the configuration. This is often used for displaying a 404 Not Found page, but in our simple example, we'll use it to redirect back to the Welcome page. There's a lot of stuff here now in our route application module. In a later course module, we'll look at how to refactor this module into multiple Angular modules for a separation of concerns.
Tying Routes to Actions
With routing, the user can navigate through the application in several ways. The user can click a menu option, link, image or button that activates or navigates to a route. The user can type the associated URL segment in the address bar after the application URL, or use a bookmark to that URL. Or the user can click the browser's forward or back buttons. The route configuration handles the URLs, so the last techniques will just work. We need to handle the first technique by tying routes to the user actions. We need to decide how we will show the routing options to the user. We could display a navigation pane with links, we can provide a toolbar or images, or we can build a navigation menu, like this one. In a more full-featured application, the menu could have many more options and sub options, but this will do for our purposes. We define that menu as part of this component's template. We then need to tie a route to each menu option. We do that using the routerLink directive. The routerLink is an attribute directive, so we add it to an element such as the anchor tag here, and we enclose it in square brackets. We bind it to a template expression that returns a link parameters array. The first element of this array is the string path of a route. Additional elements can be added to this array to specify optional route parameters. The router uses this array to locate the associated route and build up the appropriate URL based on any provided parameters. When the user selects the option, the associated route is activated. Activating a component route displays that component's view. Now let's add a menu and use the routerLink directive so the user can navigate to the views in our sample application. First, we need to decide how to show the routing options to the user. For our sample application, we'll build a menu. We want to add that menu at the root of our application, so we'll add it to the app component. In the app component template currently, we're nesting the product list component. Now that we are implementing routing, we'll route to the product list component instead. That means that it no longer needs a selector. In the product list component, let's remove that selector. Going back to the app component, we'll replace the nesting with a navigation menu. This menu uses the nav element and the navbar classes from the Twitter Bootstrap styling framework. Let's see how this looks in the browser. Here is our new menu, but clicking on the menu options don't do anything yet and our product list no longer appears. We need to tie routes to these menu options. We'll use the routerLink directive to tie a route to each of these menu options For the Home menu option, we'll add the routerLink to the anchor element, but we could use any clickable element. We want to tie the welcome route to the Home menu option, so we specify welcome here. Pay close attention to this syntax. We assign the routerLink directive to an array defined within quotes. The first element of the array is a string, so it is also enclosed in quotes. We'll use similar syntax to tie the products route to the Product List menu option. Now, we just need to tell Angular where to place our views and display the routed component's template.
Placing the Views
When a route is activated, the associated components view is displayed. But displayed where? How do we specify where we want the routed component to display its view? We use the router-outlet directive. We place that directive in the host component's template. The routed components view then appears in this location. Let's add the router-outlet to our sample application. We are back looking at the app component because it is the host for our router. We add the router-outlet in the template where we want to display the routed components view. We'll put it here. Whenever a route is activated, the associated components view displays here. Let's see how that looks in the browser. Oh my, where did that page come from? That's the welcome page provided in the starter files. When the application launches, the default route is activated and the welcome view displays. If we click on the Product List menu, the routerLink directive activates the product list route and the product list view appears. Sweet! Our application component can now route to multiple views. Notice the URL. The URL segment we defined for the route is displayed here. If we type in something, like welcome, the welcome components view is displayed. Now that we have our routing in place, let's review how these routing features work together. When the user navigates to a feature tied to a route with the routerLink directive, the router link uses the linked parameters array to compose the URL segment. The browser's location URL is changed to the application URL, plus the composed URL segment. The router searches through the list of valid route definitions and picks the first match. The router locates or creates an instance of the component associated with that route. The component's view is injected in the location defined by by the router-outlet directive and the page is displayed. We now have basic routing in our sample application. Yay! As we've seen in this course module, routing is rather intricate, requiring code in multiple files, and strings such as parameter names and route paths that must match across those files. So let's finish up this module with some checklists that can help ensure all of the bits of routing are in the right places.
Checklists and Summary
To route or to nest, that is the question. When creating components, we need to think about how they will be displayed. For a component's design to be nested within other components, we need to define a selector as part of the component decorator. The selector provides the name of the directive, and then we nest the component within another component using a directive to define where the component template appears. The component does not then need a route. For a component's design to be displayed as a view within our single application page, the component needs no selector, but we do need to configure routes. We then tie those routes to actions; an action activates a route to display the view. If we want to do routing in our application, we need to configure the route definitions, tie routes to actions, and place the view. Let's do checklists for each of these tasks. The first step for doing routing in an application is to configure the routes. Begin by defining the base element in the index.html file. Add RouterModule to an Angular module's imports array, then add each route to the array passed to the router module's forRoot method, and remember that order matters. The router will pick the first route that matches. Each route definition requires a path, which defines the URL path segment for the route. Be sure the path has no leading slash. Use an empty path for a default route and two asterisks for a wildcard route, which is matched if no prior path matches. Most route definitions also include a component. The component is a reference to the component itself. It is not a string name and is not enclosed in quotes. Once we have the routes configured, we need to tie those routes to actions. First we identify which actions to tie to which routes, then we add the RouterLink directive as an attribute to any clickable element in a component's template. We can use them in menu options, toolbars, buttons, links, images, and so on. Be sure to enclose the router link in square brackets, bind the router link to a linked parameters array. The first element of the linked parameters array is the route's path. All other elements in the array are values for the route parameters. Use the RouterOutlet to identify where to display the routed component's view. This is most often specified in the host component template. When a route is activated, the route component's view is displayed at the location of the router outlet. This module was all about navigation and routing. We began with a look at how routing works. We then walked through how to configure routes, tie routes to actions, and define where the routed component's view should appear. Our app component had embedded our product list component as a nested component. In this module, we were finally able to remove that nesting. We set up routing so we can now navigate to our welcome and product list components. We've covered the basics of routing in this module, but there is so much more. In the next module, we'll look at some additional routing techniques and add navigation to the product detail component.
Navigation and Routing Additional Techniques
Now that we know the basics of routing, we are ready for more. Welcome back to Angular: Getting Started, from Pluralsight. My name is Deborah Kurata, and in this module, we look at several additional routing techniques. With what we learned in the prior module, a user can now navigate to any defined route in our application, but that only covered the very basic routing scenarios. What if we need to pass parameters to a route or activate a route with code instead of with a clickable element? Or what if we need to protect a route and only allow access to it in special cases or by certain users? Or ask a user to save changes before leaving a route? In the second module on routing, we examine how to pass parameters to a route, how to activate a route with code, and how to protect our routes with guards. When we're finished with this module, we'll know how to handle additional routing scenarios, including routing to a component that requires parameters, such as our product detail component. Let's get started.
Passing Parameters to a Route
We sometimes need to pass parameters to a route. For example, to navigate to the Product Detail view, we need to define which product's details to display. The first step to passing parameters to a route is to configure the route with parameters. We've already done this step to route to our product detail component. Here we define a slash, a colon, and a placeholder for the parameter. If multiple parameters are needed, we'd repeat this with another slash, colon, and placeholder. With the route definition in place, we can decide where we want the user to activate this route. Will we add a menu option or a data link? It is there we set the routerLink and pass in the required parameter. In the product list component template, we display a table of products. Each table row contains the product name, so we add a routerLink to this anchor tag and assign it to the link parameters array. The first element to the array is the string path of the route. The second element of the array is the value for the route parameter. When the router composes the URL, it uses this array element to construct the defined parameter. To display the appropriate product, the product detail component reads this parameter from the URL. It then uses the parameter to retrieve the appropriate product and display it in the view. To get the parameter from the URL, we use the ActivatedRoute service provided by the router. We want an instance of the service, so we'd define it as a dependency in our constructor. We've seen this syntax before. This line of code defines a private variable called route and assigns it to the instance of the ActivatedRoute provided by the Angular service injector. Then we use the instance of the ActivatedRoute service to get the desired parameter. There are two different ways to get the parameter. We could use a snapshot or we could use an observable. Use the snapshot approach if you only need to get the initial value of the parameter. The code is then a one-liner, as shown here. In our example, the user is always returned to the list page before navigating to another product, so the snapshot approach would be sufficient. If you expect the parameter to change without leaving the page, use an observable. For example, if we had a Next button on the Product Detail page to display the next product, the URL will change to the next product's ID, so you'd want to use an observable instead. We use the ActivatedRoute snapshot method here and access the appropriate parameter from its parameter array. The string specified here must match the name of the parameter from the path. Let's give this a try.
Demo: Passing Parameters to a Route
Activating a Route with Code
We want to add a Back button to our Product Detail page that navigates back to the Product List page. We could activate this route using the RouterLink directive in the component template, just like we did with the menu options, but it's also possible to route with code, so let's try that out instead. When would you want to navigate with code instead of the RouterLink directive in a template? One example is a Save button where you need to execute some code to save the data, and then route. To route with code, we use the Router service. We import the Router service from angular/router. We define a dependency on the Router service using a constructor parameter. The router instance is then injected into this component class. Every time we inject a service dependency into a class, we should ask ourselves, hmm, did we register this service with the Angular injector? In the case of router, it is registered in router module, which we added to our application's Angular module imports array. We use this router instance to activate a route. Here we define a method that we can call from the template based on a user action. The code uses the navigate method of the Router service and passes in the same link parameter array we used when binding the RouterLink. In this example, we route to the products route and don't pass any arguments. Let's give this a try. Here we are looking at the product detail component. We want the product detail component to navigate back to the product list component. We define the router as a dependency by adding another constructor parameter. When this component class is constructed, we'll get an instance of both the ActivatedRoute service and the Router service. Now we can build a method that navigates with code. Since the purpose of this method is to navigate back, we'll call it onBack. It doesn't return anything, so we'll set its return type to void. In this method, we use the this.router instance and call the navigate method. We pass it a linked parameters array. In this example, we want to navigate back to the product list component, and we don't need any parameters. We just define the route path, which is products. In the product detail template, we'll add a button. We again use the Twitter Bootstrap style classes to give the button some style. We use event binding to bind the click event of the button to the onBack method we defined in the class. Now let's check it out in the browser. Click on the Menu option to display the product list, then click on a product. The Product Detail page is displayed. We click our Back button, and we are back on the Product List page. Let's try another one. We see the Detail page. Now we're back on the Product List page. Our code-based navigation is working. So routing with code involves importing the router and using its navigate method to activate the route. Now that we have several routes in place, let's look at how to protect them with guards.
Protecting Routes with Guards
There may be times that we want to limit access to a route. We want routes only accessible to specific users, such as an administrator, for example, or we want the user to confirm a navigation operation, such as asking whether to save before navigating away from an edit page. For that, we use guards. The Angular router provides several guards, including CanActivate to guard navigation to a route, CanDeactivate to guard navigation away from the current route, Resolve to pre-fetch data to before activating a route, and CanLoad to prevent asynchronous routing. In this clip, we work through how to implement the CanActivate guard. You can use the same techniques we're covering here to implement any other type of route guard. We'll build a guard that prevents navigation to the product detail route, unless a specific condition is true. Building a guard clause follows the common pattern used throughout Angular. Create a class, add a decorator, and import what we need. Here we define a guard class. Since we are implementing this guard as a service, we use the Injectable decorator. This class implements CanActivate. To create one of the other kinds of guards, change this to implement one of the other guard types. We then implement the CanActivate method. For simple cases, this method can return a Boolean value, true to activate the route, and false to cancel the route activation. For more complex cases, we could return an Observable or a promise from this method. Using a guard is simple. We build the guard to protect the product detail route, ao we add the guard to the product detail route. We add CanActivate and set it to an array containing the guards to execute before this route is activated. In our case, there is only one. Let's give this a try.
Demo: Protecting Routes with Guards
In this demo, we protect our product detail route with a guard. We want to build a guard that prevents navigation to the product detail component if the provided URL parameter is not valid. Recall that the route definition for the product detail component includes an ID, but there is nothing here that defines this ID to be numeric or greater than 0, so our guard should prevent navigation to the product detail route if the ID is 0 or not a number. Our first step is to build our guard. We could build it manually, but why not use the Angular CLI? We open the integrated terminal and type ng for the Angular CLI, g for the generate, g for guard, and the name of our guard. Since this guard is for the product detail route, we add it to the products folder, so products/product-detail. That's all that is required. Press Enter and we see that the CLI created the guard and the start of a unit test for that guard. Let's open the resulting guard. The CLI added all of the basic boilerplate here for us. Yay! Let's talk through it. Since a guard is a service, it needs to be registered with an Angular injector. The CLI registers this guard with the route application injector, using the providedIn property. By default, the CLI generates the code for the CanActivate guard. It implements the CanActivate interface and builds the start of the canActivate method. We can change this as needed if we want to implement a different type of guard. The canActivate method has two parameters, the ActivatedRouteSnapshot to provide current route information, and the RouterStateSnapshot to provide router state information. The method can return an Observable, a promise or a simple Boolean value. All that's left is to write the logic for the guard. What do we want this method to do? We need to check the route URL and ensure that the ID passed in is valid. If it is not valid, we want to navigate back to the Product List page. Navigation requires the router, so the first thing we need is a constructor. Then we inject in the router, letting VS Code import the appropriate package. Next, in the canActivate, we need to read the parameter from the route. Luckily for us, canActivate has a parameter that gives us the ActivatedRouteSnapshot. The ActivatedRouteSnapshot contains the information about a route at any particular moment in time. Now I'll paste the code for the method body and we can talk through it. The product detail route URL is comprised of two segments, product and the requested ID. We only care about the ID, so we pull the path from the second element, which is index of 1. The plus here at the beginning converts the URL path string to a number. If the resulting value is not a number, or less than 1, we display an alert, direct the user to the Product List page, and return false to abort the current operation. Notice that this code is the same code we use to activate a route with code that we saw on the last clip. And here, we return true to continue activating the route. Now, we don't normally want to display an alert from our application. In a real application, we'd route to an error page that would notify the user of the problem, and optionally provide a button for navigating back to the Product List page. But this is good enough for our purposes. Next, we need to hook up this guard to the appropriate route. We add the canActivate property to the route definition for the product detail component and set it to an array. In the array, we specify each guard we want to execute when activating the product detail route. In this case, we have only one. That should do it. Let's give it a try. If we view the Product List page and select a product, our URL is valid and we navigate to the page. If we instead type in a URL that is not a number, we see our message. The product detail route navigation is canceled and we are redirected to the Product List page. It works. Use route guards any time you want to prevent access to a route, confirm navigation away from a route or preload data for a route. So let's finish up this module with some checklists.
Checklists and Summary
We can pass any number of parameters to a route separated by slashes. Add the parameter to the route configuration path by specifying a slash, a colon, and the parameter name. Pass the parameter value by adding it to an element of the link parameters array bound to the RouterLink directive. Read the parameter value in the navigated component using the ActivatedRoute service. Notice here that the parameter name, id in this example, is exactly the same as in the route definition. To activate a route with code, use the Router service. Be sure to import the service and define it as a dependency on the constructor. Create a method that calls the navigate method of the Router service instance, and pass in the link parameters array. Add a user interface element and use event binding to call the created method. We can use guards to prevent access to a route, confirm navigation away from a route, or to preload data for a route. To create a router guard, we build a guard service, implement the guard type, in our case CanActivate, and create the associated method. We then register the guard service provider. Lastly, we add the guard to the desired route. In this module, we covered some additional routing techniques, including passing parameters to a route, activating a route with code, and protecting routes with guards. But we've only just touched on the basics. If you are interested in learning more about routing, check out my Angular Routing course, here on Pluralsight. You'll learn how to pass required, optional, and query parameters on a route, how to fetch data with route resolvers, how to define child and secondary or named router outlets, and more on router guards. Plus, you'll see how to improve your application performance with lazy loading. We now have routing to our product detail component. Yay! However, the product detail component and its associated template are not finished. We only wired up the bare minimum to demonstrate routing. As a homework assignment, try building the remainder of the product detail template and code. To check your answer, you can find the completed application on my GitHub as described in the First Things First module. There you will also find a version of the product data service that retrieves one product by ID. You'll need that to get the data for the product detail component. At this point, our AppModule is looking a little cluttered and hard to manage. Up next, let's spend some more time with Angular modules and look at how to refactor our application into more manageable pieces.
As we've seen throughout this course, Angular modules are a key part of any Angular application. Welcome back to Angular: Getting Started, My name is Deborah Kurata, and in this course module, we focus on Angular Modules and how to use them to better organize our code. Ooh, it's so beautiful. No, it's not a Van Gogh, not even a Picasso. And yeah, it sort of looks like a metro map. This is a picture of what our application could look like if we leverage the power of Angular modules. Instead of one large AppModule like we have now, there are multiple modules, and each piece of our application has a logical place within one of those modules. This keeps each module smaller and more manageable. In this course module, we take another look at the definition and purpose of an Angular module. We then focus in on the Angular module metadata to better understand how to use it. We leverage that knowledge to create a feature module for our application, and take it one step further defining a shared module to reduce duplication. Lastly, we re-examine our application's root Angular module. Let's get started.
What Is an Angular Module?
What is an Angular module? As we've seen earlier in this course, an Angular module is a class with an NgModule decorator. Its purpose? To organize the pieces of our application. Arrange them into cohesive blocks of functionality and extend our application with capabilities from external libraries. Angular modules provide the environment for resolving the directives and pipes in our components' templates. We'll talk more about this in a few moments. And modules are a great way to selectively aggregate classes from other modules and re-export them in a consolidated convenience module. BrowserModule, HttpModule, and RouterModule are all examples of this, and we'll create our own convenience module when we build a shared module a little later. An Angular module can be loaded eagerly when the application starts, or it can be lazy loaded asynchronously by the router. Lazy loading is out of the scope of this course, but is discussed in detail in my Angular Routing course, here on Pluralsight. How does an Angular module organize our application? An Angular module declares each component, directive, and pipe that it manages. Every component directive and pipe we create belongs to an Angular module. An Angular module bootstraps our route application component, defining the component needed to display our first template. An Angular module can export components, directives, pipes, and even other Angular modules, making them available for other modules to import and use. An Angular module imports other Angular modules. This brings in the exported functionality from those imported modules. An Angular module can register service providers with the Angular injector, making the services available to any class in the application. We can think of an Angular module as a box. Inside that box we declare each of our components. If those components need any functionality, that functionality also needs to be defined within this box. The AppComponent sets up the routing for our main menu using routerLink, and includes the router-outlet directive, so it needs the routing directive's router service and routes, which are defined in RouterModule. The Product-List Component uses ngModel, so it needs the FormsModule. The product list component also uses ngFor and ngIf, so it needs the BrowserModule. The product list component uses the pipe, so it needs that too. The product list component also uses the star rating components directive, so it needs that as well, and so on until the box contains everything that each of our components needs. Saying this another way, for each component that belongs to an Angular module, that Angular module provides the environment for template resolution. The module defines which set of components, directives, and pipes are available to the component's template. Each declared component's template is resolved using only the capabilities provided within that module. Let's look at our product list component as an example. The product list components template uses ngModel, so ngModel must be available within this module. We achieve that by importing the Angular FormsModule. The product list components template also uses a directive we created, the StarComponent, so the StarComponent must be available within this module. Since the StarComponent is one we created, we can either declare the StarComponent within the module directly, or we can import another module that exports the StarComponent. Importing an Angular module brings in the functionality exported by that module. And we need to do one or the other, never both. We didn't need to think about template resolution much in our sample application up until now because all of the pieces of our application are in one Angular module. But we will need to keep this in mind as we split our application into multiple Angular modules. Let's take a quick look at our current AppModule. Here is the Angular module we defined throughout this course. It is the application's root Angular module, and by convention is called AppModule. The AppModule imports the system Angular modules we need, including the RouterModule, which is where we configured our routes. It declares each component and pipe that we created in this course, and it bootstraps the application with the root application component, AppComponent. We have a lot of information in here, and we're mixing up basic application pieces, such as our welcome component, with pieces specific to our product feature. Let's journey through the ngModule metadata to better understand how Angular modules work, so we can then refactor our AppModule into multiple modules for better code organization.
As we have seen, every Angular application has at least one Angular module called the root application module, or AppModule. And an Angular application has at least one component, called the root application component, or AppComponent. The AppModule bootstraps the AppComponent to provide the directive used in the index.html file. We covered the bootstrapping process in the Introduction To Components module earlier in this course. The bootstrap array of the ngModule decorator defines the component that is the starting point of the application. This is the component that is loaded when the application is launched. Here are some things to keep in mind when using the bootstrap array. Every application must bootstrap at least one component, the root application component. We do this by simply adding the root application component to the bootstrap array of the root application module. The bootstrap array should only be used in the root application module, AppModule. As we build other Angular modules, we won't use the bootstrap array.
Every component directive and pipe we create is declared by an Angular module. We use the declarations array of the ngModule decorator to define the components, directives, and pipes that belong to this Angular module. Here are some things to keep in mind when using the declarations array. Every component directive and pipe we create has to belong to one and only one Angular module. In our sample application, all of our components are defined in one Angular module, AppModule. It would be better to divide the components into multiple modules, with basic application pieces in the AppModule and feature pieces and appropriate feature modules. We'll do that a little later in this course module. As we separate out our pieces, it is important to remember that each component, directive, and pipe belongs to one and only one Angular module. Only declare components, directives, and pipes. Don't add other classes, services or modules to the declarations array. Never redeclare components, directives or pipes that belong to another module. This is a corollary to truth number one. If we redeclare, then the component directive or pipe no longer belongs to one and only one Angular module. For example, the StarComponent directive belongs to Module B, so we should never redeclare StarComponent in Module A. We should only declare components, directives, and pipes that belong to this module. All declared components, directives, and pipes are private by default. They are only accessible to other components, directives, and pipes declared in the same module. So if we declare the StarComponent in Module B, by default that component is not available to components in other Angular modules. We share components, directives, and pipes by exporting them. We'll talk more about exporting in a few moments. The Angular module provides the template resolution environment for its component's templates. When we include a component in the declarations array of an Angular module, the component belongs to that Angular module. That component's template, directives, and pipes are then resolved within that module. When we use a directive in a component's template, Angular looks to the module for the definition of that directive. If the component defining that directive is not declared within the same Angular module or exported from an imported module, Angular won't find the directive and will generate an error. For this example, the StarComponent must be declared in the same module as the product list component, or the StarComponent must be exported from an imported module, never both.
The exports array of the ngModule decorator allows us to share an Angular module's components, directives, and pipes with other modules. We can export any of this module's components, directives, and pipes so they can be pulled in when another module imports this module. We can also re-export system Angular modules, such as FormsModule and HttpModule. We can re-export third-party modules such as material design. Material design is a set of high-quality user interface components, including buttons and dialogs. And we can re-export our own modules. Here are some things to keep in mind when using the exports array. Export any component, directive or pipe if another component needs it. A module can export any of its declared components, directives or pipes. Re-export modules to re-export their components, directives, and pipes. This is useful when consolidating features for multiple modules to build a convenience or shared module. We can re-export something without importing it first. An Angular module only needs to import the components, directives, and pipes that are required by the components declared in the module. But the Angular module can still provide capabilities to other modules that import it by re-exporting. In this example, a shared module exports the FormsModule even though it did not import it. So any module that imports the shared module will have access to the ngModel and other forms directives. We'll see this in an upcoming demo. Never export a service. Services added to the providers array of an Angular module are registered with the root application injector, making them available for injection into any class in the application. So there is no point in exporting them, they are already shared throughout the application.
An Angular module can be extended by importing capabilities from other Angular modules. The imports array of the ngModule decorator allows us to import supporting modules that export components, directives or pipes. We then use those exported components, directives, and pipes within the templates of components that are declared in this module. Many Angular system libraries are Angular modules, such as the FormsModule and HttpModule we've used in this course. We can import Angular modules to use their capabilities. Many third-party libraries are also Angular modules, such as material design. We can import third-party Angular modules to use their capabilities. We can import our own modules to extend our application with additional features or share capabilities across several modules. We'll see that in an upcoming demo. And we could separate out our route configurations into its own module or set of modules and import that. Here are some things to keep in mind when using the imports array. Importing a module makes available any exported components, directives, and pipes from that module. Recall that we are using ngModel in our product list component for two-way binding. The ngModel directive is exported in the FormsModule. By importing the FormsModule into our AppModule, we can use ngModel in any component declared in our AppModule. Only import what this module needs. Only import modules whose exported components, directives or pipes are needed by this module's component templates. Don't import anything this module does not need. Importing a module does not provide access to its imported modules. Hmm, let's look at that with the picture. Here we have AppModule, which declares the product list component, and a shared module that declares and exports the StarComponent. AppModule imports the shared module, so the shared module's exports are available to the AppModule's component templates. This means that the product list component can use the StarComponent directive. If the shared module imports FormsModule, then the FormsModule's exports are available to the shared module, and the StarComponent could use the ngModel directive. But the FormsModule exports are not available to the AppModule, so the product list component could not use the ngModel directive. I've heard this rule also stated another way, imports are not inherited. Note, however, that if the shared module re-exported the FormsModule, then the FormsModule exports are available to the AppModule. And the product list component could use the ngModel directive. So when thinking about the relationship between modules, think of a module more as a box than as a tree structure.
Angular modules can also register service providers for our application. However, this is no longer recommended practice. Starting with Angular version 6, the recommended way to register service providers for our application is to use the providedIn property of the service itself, not the provider's array of the Angular module. Because you may see older code, use the providers array to register services. I'll still cover it. Here are some things to keep in mind when using the ngModule providers array. Any service provider added to the providers array is registered at the root of the application, so the service is available to be injected into any class in the application. Say for example we have a feature module called ProductModule. We add the product service to the providers array of this module. At first glance we may think we have encapsulated the product service into the ProductModule, but that is not the case. Any service provider added to the providers array is registered at the root of the application and is available to any class, even classes and other feature modules. So if we want to ensure a particular service is encapsulated and only accessible within a specific component or set of components, add the service provider to the providers array of an appropriate component instead of an Angular module. Note that this is not the case for lazy loaded services. See the Angular documentation for more information on lazy loading. Don't add services to the providers array of a shared module. As discussed in the Services and Dependency Injection course module, there should only be one instance of a service that is an application-wide singleton. So a service should not be included in the providers array for any module that is meant to be shared. Instead, consider building a core module for services and importing it once in the AppModule. This will help ensure that the services are only registered one time. We could even add code to the code module's constructor to ensure that it is never imported a second time. See the Angular documentation for details. Now that we've covered the basics of the ngModule decorator, let's refactor our application into multiple Angular modules.
So far in this course, we created the route application module, AppModule. It declares all of our components and our pipe. It imports the system Angular modules that our components need. But this is getting a little unwieldy. We have no separation of responsibilities. Here we are mixing our basic application features, such as the welcome component, with our product features, such as the product components, with our shared features, such as the StarComponent. As we add more feature sets to this application, such as customer management, invoicing, and so on, this is only going to get harder to manage. So let's reorganize and refactor to break this into multiple Angular modules. The first thing we want to do is extract some of these pieces into feature sets. We can then create a feature module for each feature set. Using feature modules helps us partition our application into logical groupings, with separate concerns. Our first step is to define a new feature module. Creating a feature module involves defining a new module file, ProductModule in this example, and reorganizing the pieces of the application so that all of the associated feature pieces and everything they need are here in this module. In the declarations array of the feature module, we add the appropriate components that provide the features for the application. In this example, we add the product list component and product detail component. Then as we did with the box example at the beginning of this course module, we start to look at what each component needs. In this example, the product list component uses the pipe, so we need that. And both the product list and product detail components use the StarComponent, so we'll need that here as well. But that's not enough. The product list Component uses ngModel and ngFor, and both components use ngIf and routing. How do we get that? We import these needed capabilities from other Angular modules. Our product components use routing, so we import the system RouterModule. The product list component uses ngModel, so we import the system FormsModule. And we need ngFor and ngIf, so do we pull in the system BrowserModule? Nope, the BrowserModule should only be imported into the root application module, AppModule. Instead, we import the system CommonModule. The CommonModule exposes the ngFor and ngIf directives. Not surprisingly, the BrowserModule itself actually imports and exports the CommonModule, which is why we have access to ngFor and ngIf when we import BrowserModule in our AppModule. Our feature module is looking pretty good here, but now that we've removed these features from the root application module, how will the application find all of these features? What's that, imports array did you say? That's correct. We need to import the ProductModule into the AppModule. That extends the AppModule with the ProductModule features. Want to try this out?
Demo: Feature Modules
In this demo, we'll build a feature module for our product features. We are back in the sample application. Here is our AppModule. Let's create a new feature module for our product feature. Want to try generating it with the Angular CLI? We open the integrated terminal and type ng for the Angular CLI, g for generate, m for module, and the name of our module. Since we are creating the ProductModule, we want it in the products folder, so products/product. That's all that's required. But by default, Angular will create a new folder for this module because it rightly assumes that we'll create the module when we define the feature and the feature folder. But we already have the products folder, so we'll use the --flat option, and we want to import this module into the AppModule to pull in its functionality, so we use the -m flag, specifying the module name. Press Enter and we see that the CLI created the module. It also updated our AppModule. We can see that here. It added ProductModule to our imports array. Yay! Let's open the new ProductModule. The CLI already created the class with the NgModule decorator and the required import statements. Since this module is for our product features, in the declarations array we add the ProductListComponent, ProductDetailComponent, ConvertToSpacesPipe, and StarComponent. Now we can remove these declarations from the AppModule. Going back to our ProductModule, we can see that the CLI already included CommonModule here, since we need that in every feature module. We will add the FormsModule and RouterModule. Now we can remove the FormsModule from the AppModule and its associated import statement. When we added the RouterModule to the imports array in the AppModule, we called forRoot to pass in the configured routes for our root component. Now that we are adding the RouterModule to the imports array of a feature module, we don't call forRoot, rather, we call forChild, and there we pass in the routes related to products. Let's cut the product routes from the AppModule and paste them here in our ProductModule. And we need to import the ProductDetailGuard. Recall that the RouterModule registers the router service provider, declares the router directives, and exposes our configured routes. But as we've discussed previously, we never want to register a service more than once. So when we use forRoot to pass in our configured routes, the RouterModule knows to register the router service provider. When we use forChild, as we did here, the RouterModule knows not to re-register the router service. Note that we could also consider moving the routes into their own modules. We'll look at that a little later. Do you think our application will run? And our application works as expected. So we now have our first working feature module. But let's think about this for a moment. As we build our application, we'll build more features. Each logical set of features will have their own feature module, and each feature module will most likely need the CommonModule for common directives such as ngFor and ngIf, the FormsModule for ngModel and two-way binding, and we may have other features that want to reuse our StarComponent. Do we really want to repeat all of this in each feature module? There has to be a better way. Yep, we can define a SharedModule.
The purpose of a SharedModule is to organize a set of commonly used pieces into one module and export those pieces so they are available to any module that imports the SharedModule. This allows us to selectively aggregate our reusable components and any external modules and re-export them in a consolidated convenience module. Creating a SharedModule involves defining a new module file, SharedModule in this example, and reorganizing the pieces of the application so that the shared pieces are here in this module. First, we add the components, directives, and pipes that we want to share throughout our application to the declarations array. In this example, we only want to add the StarComponent. Then we add to the imports array anything that this shared component needs. In this example, we import the CommonModule because our StarComponent may need it. We don't import FormsModule because we don't need it here. If our StarComponent did use two-way binding or we added another component here that did, we'd need to import FormsModule as well. We then need to export everything that we want to share. The exports array defines what this Angular module shares with any module that imports it. We export the StarComponent. That way it is available to the components and any module that imports the shared module. We re-export the CommonModule and FormsModule so their directives and other features are available to any module that imports the SharedModule. And notice here that we can export something without importing it first. To use the SharedModule, we import it into every feature module that needs the shared capabilities, such as our ProductModule. Let's give this a try. We want to build a shared module, and we'll again use the CLI. Do you recall the correct CLI command to generate a module? We type ng for the Angular CLI, g for generate, m for module, and the name of our module. Since we are creating the shared module, we want it in the shared folder, so shared/shared. We already have the shared folder in place, so we'll specify the --flat option, that way the CLI won't create another folder. And we want to import this module into the product module to pull in its functionality, so we use the -m flag, specifying the module path and name. Press Enter, and we see that the CLI creates the shared module and it updates our product module. We can see that here, it added shared module to our imports array. Let's open the new shared module. The CLI already created the class with the NgModule decorator and the required import statements, and it included CommonModule in the imports array here. Now what did we want to share? Well, we want to share the StarComponent, so we add that to the declarations array here. To share the StarComponent, we need to export it. Let's add an exports array and export the StarComponent. There's a few more things that we want to share. So we don't have to import them into every feature module, we'll add CommonModule and FormsModule to the exports array. If there were other modules we wanted to share, such as reactive forms module or material design, we could add them here as well. We could also share the ConvertToSpacesPipe. I'll leave that up to you to add here if you wish. Now we can remove the StarComponent, CommonModule, and FormsModule from the ProductModule, along with their associated import statements since these are now already accessible from the imported SharedModule. Are we good to go? Yep, our application comes up as it did before. Looking back at our code, notice now that our feature module, ProductModule, only contains product pieces and the shared module. And the SharedModule is clean only including the pieces we want to share. We can reuse the SharedModule and any future feature modules as we add functionality to our application.
We now know that every application has a root application module that is, by convention, called AppModule. The main purpose of the AppModule is to orchestrate the application as a whole. And now that we've removed the feature and shared pieces from this module, its purpose is easier to see. Let's take another look. We've reduced the code in AppModule such that it now fits on one page. The AppModule normally imports BrowserModule. This is the module that every browser application must import. BrowserModule registers critical application service providers. It also imports and exports CommonModule, which declares an exports directive such as ngIf and ngFor. These directives are then available to any of the AppModule's component templates. We also import HttpModule to register the angular HTTP client service provider. We import RouterModule and call forRoot, passing in the configured routes for the root of the application. Here we configure our default route and any wildcard routes. Then we import each feature module. In this example we have only one feature module, ProductModule. The declarations array identifies the list of components that belong to this module. In this example the root component, AppComponent, and the application's WelcomeComponent are declared here. The bootstrap array identifies the root component, AppComponent, as the bootstrap component. When Angular launches the application, it loads this component and displays its template. We could take the refactoring a step further and separate the routing into its own module. We could create one Angular module for our root application routes, and another Angular module for our product feature routes. Let's go back to the slides and see what that code would look like. If we wanted to refactor our root application routes into their own module, this is what it might look like. We export a class, add the NgModule decorator, and import what we need. We add the RouterModule to the imports array, passing in our root application routes, including our default route and our wildcard route. Notice that we call forRoot here to ensure that we register the routing service provider, and we export RouterModule so we can use it from any module that imports this module. We import the AppRoutingModule in the AppModule here. Note that the AppRoutingModule is listed after the ProductModule in the imports array. This is required because Angular registers the routes based on the order of the modules specified here. The ProductModule is listed first, so it registers the product routes first. Then the AppRoutingModule registers the application routes, including the wildcard route. If the AppRoutingModule was before the ProductModule, then the wildcard route would be registered before the product route's, and the product route's would never be accessible. So the AppRoutingModule with the wildcard route should always be last in this list. We can do the same to refactor our product feature routes into their own module. The key difference here is when we import RouterModule and any feature module, we pass the configured routes to the forChild method instead of the forRoot method. This ensures that we don't register the routing service provider a second time. And we import this product routing module into the ProductModule, as shown here. Now let's finish up this course module with some checklists and a summary.
Checklists and Summary
Your application architecture depends on many factors, including the size and scope of the application you are working on, your team size and experience, and your project's goals. But here are some suggestions based on what we covered in this course module. Every application must always have a root application module, by convention called AppModule. This is normally the module that bootstraps the root application component, AppComponent. For smaller applications, this could be the only Angular module for the application, as was the case with our sample application prior to this course module. As the application gets more features, considered defining a separate Angular module for each feature set. For example, a ProductModule, a CustomerModule, and an InvoiceModule. This keeps the code organized, separates the concerns, and prevents the AppModule from getting excessively large and unwieldy. As you add feature modules, you may find components, pipes, and directives that you want to share across feature modules. Define one or more shared modules for these shared pieces. Shared modules primarily use the exports and declarations arrays, with most of the declared pieces exported as well. If you have a set of services that you want to ensure are loaded when the application is loaded, consider defining a core module for those services. Be sure that the core module is imported only once in the root application module. Since the core module is for services, they primarily have providers, none of which are exported. We did not create a core module for our sample application since our service needs are limited, but you may find them useful for your applications. And as we discussed in the last clip, we can also refactor our routes into their own routing modules. When creating an Angular module, we build a class and decorate it with the NgModule decorator. The NgModule metadata includes the bootstrap array for defining the list of startup components. In many cases there is only one, the root component of the application. The declarations array declares which components, directives, and pipes belong to this module. The exports array identifies the list of components, directives, and pipes that an importing module can use. The imports array lists supporting modules. These modules provide components, directives, and pipes needed by the components in this module. The providers array lists the service providers. Angular registers each provider with Angular's root application injector, so these services are available to be injected into any class in the application. This course module was all about Angular modules. We took a second look at the definition and purpose of an Angular module. We then focused in on the Angular module metadata and covered the truths to keep in mind when using that metadata. We leveraged that knowledge to create a feature module for our application, and took it one step further, defining a shared module to reduce duplication in our application. Lastly we re-examined our application root Angular module and saw how it orchestrates the application as a whole. If you are building a small application, such as the sample application we've created in this course, you may only need the one root application module, as shown here. But as your application grows, you'll want to refactor into feature modules and shared modules, like this. into multiple modules. We have our feature module, ProductModule, that encapsulates all of the product features. There will be more feature modules as our application grows. We have our SharedModule that shares commonly used components, directives, and pipes with any module that imports it. Currently we import it into the ProductModule. As we build more feature modules, we'll import it into them as well. And we have our AppModule that orchestrates the application. Each feature module is added to the AppModule's imports array to extend the application with those features. Whew, it's been quite a journey. Now let's circle back to the beginning and spend a little more time with the Angular CLI.
Building, Testing, and Deploying with the CLI
We've mentioned the Angular CLI a few times in this course, but haven't really discussed what it is or what it can do. What seemed like wizardry is instead a full-featured tool for building, testing, and deploying our Angular applications. Welcome back to Angular: Getting Started, from Pluralsight. My name is Deborah Kurata, and in this module, we look closer at the Angular CLI. Let's peek behind the curtain and learn more about this amazing tool. In this module, we start with an overview and discover the purpose and basic features of the Angular CLI. We then build an application from scratch using the CLI and examine the many configuration and startup files it creates. We dig into how it serves up our application, learn more about its code generation features, take a brief look at running tests, and prepare the resulting application for deployment. Let's get started.
Angular CLI Overview
The Angular CLI is a command line interface for Angular. Its primary purpose is to assist Angular developers with building an Angular application, generating Angular files, and executing, testing, and deploying Angular applications. Before you can use the CLI, you need to install it. Just open a terminal window or command prompt, and type npm install -g @angular/cli. This installs the CLI globally so you can use it from any folder. If you have not yet installed the CLI, do it now. Once you have it installed, we'll jump into a demo and try it out. I've already installed the CLI. Now, how can we find out what the CLI can do? One option is to use the ng help feature. We can see that it is listing every command. Let's try out the version command. Type ng for the Angular CLI, and the command. We'll use the v alias. And we see a nice text output, including our Angular CLI version. When you watch this course, you may have a newer version. Use the help command any time you need assistance with the CLI commands or their flags. Now let's build a new application from scratch using the CLI.
Our new little application is cool and all, but it doesn't do much. We need to add components and templates and services and modules. We do that with the generate command. We've already seen this command in action, but it can do so much more. If we type ng g --help, this lists all of the different types of files we can generate with this command. We can create simple classes. We can create components with external template and style sheet files, as we did with the product detail component earlier in this course. We can generate our own custom directives, enum values, and route guards, as we did with our product detail guard. We can create interfaces and modules. We can create pipes and services, all with only typing a few characters. Neat! Let's generate a component. Type ng g c, and our component name, and let's say, welcome. The output indicates that it created a style sheet file, template HTML file, unit testing file, and TypeScript file for our component. By default, it created a welcome folder and added these files to it.
Testing our Angular code is always important, but often neglected due to the additional time and effort required to set up and execute the tests. The CLI makes it a bit easier by generating the testing spec and configuration files for us. We can run these tests using ng test and ng e2e. To run our unit tests, in the terminal window type ng test. This builds the application, launches the browser, and executes the Karma test runner. From here, we can see the results of our unit tests. Angular generated four tests for us, and they've all passed, so we are all green. The test runner runs in watch mode, so we can modify our files and the tests will immediately re-run. I'll close the browser, and notice that it tried to bring it up again. I first need to terminate the test runner with Ctrl+C. Then I'll clear the screen with cls. We can use the e2e command to run our end-to-end tests using Protractor. Let's try that out, ng e2e. Notice that it pops up the browser, executes our application, and closes the browser again. We can see here that it successfully executed one spec. Testing is a big topic that is beyond the scope of this course. See the Pluralsight library for more information about testing. Now that we have our application built and tested, we are ready to deploy.
An application is not very useful unless you can deploy it to your users. The build command helps you prepare your application for deployment. Let's take a look. Here in the terminal window, we'll type ng build. The output is similar to the ng serve command, but now we see a dist folder here in our project. If we open the folder, we see our actual bundles. We can look again at the main.js file and find our code. And here is the resulting index.html file with the script tags to pull in each bundle. We can then deploy these files to a server somewhere. But wait, there's more. When we deploy to production, there are a few more things that we want to achieve. We want our bundles to be as small as possible. This means that we want to minify or uglify the code and perform something called tree shaking. Tree shaking shakes up our code to drop out any dead branches, which basically means that it removes any unused pieces. We also want to pre-compile our templates, so instead of downloading the Angular compiler as part of the vendor bundles, and running the compiler in the browser, we can make the bundles smaller and execute faster if we pre-compile before we deploy. This makes use of the AOT, or Ahead of Time Compiler. The CLI will do all of these things for us if we simply add the --prod flag. Let's try that out. Type ng build --prod. Now if we look at the files in the dist folder, we notice that they have a hash as part of the file name. This is a cache-busting technique. Every time we update something in our code, it needs to be re-deployed to the server. We expect the new version of the files to be downloaded by the browser when a user accesses our application, but for performance reasons, the browser may have cached those files. By changing the file names using a hash, the browser will download the latest version because the file names won't match its cached versions. Notice also that there are no MAP files here. By default, the --prod flag does not generate MAP files, but there is a flag for that if you need them. Peeking into the main bundle, all the code is on one line, and look at those variable names. It's been minified. Glad we don't have to debug using that code. Let's look at some of the key flags available with the build command. Type ng build --help. We'll scroll up a bit. There are quite a few flags here to help you tune exactly what you want the build command to do. One of the other key flags here is the --base-href. Recall that we set the base element in our index.html file to specify the root path for our Angular routing, but when we deploy to production, we may not want to deploy to the root path of our server. We can use the --base-href flag to set the appropriate base URL for the deployed application. Wow, the CLI is powerful. Now let's finish up this module with some checklists we can use as we leverage the Angular CLI.
Checklists and Summary
Here are the key CLI commands for reference. Use ng help, or ng, a command name, --help, to view the CLI documentation. The remaining commands here help you create, execute, build, test, and deploy your Angular application. You may find yourself using ng generate a lot. Refer to this checklist as you generate the code files for your application. In this module, we discovered the purpose and basic features of the CLI. We built an application from scratch and examined its many configuration and startup files. We dug into how it serves up our application, learned about its code generation features, looked at running tests, and prepared the resulting application for deployment. Now that we've peeked behind the curtain, you know the secrets of the CLI. There is no wizardry here. There are more commands and many more flags than what we discussed in this module. To learn more about the Angular CLI, check out John Papa's course, aptly entitled Angular CLI, in the Pluralsight library. It covers many more of its amazing features. Only one module left.
As you have seen throughout this course, Angular provides a consistent set of patterns for building components, templates, and services, helping us to come up to speed quickly. Welcome back to Angular: Getting Started from Pluralsight. My name is Deborah Kurata and the final words in this course include a recap of our journey, a few pointers to additional information, and a look at a broader description of Angular. Let's jump right into this short module.
Recapping Our Journey
The goal of this course was to guide you down the right path, making your own adventures with Angular more pleasant and productive. Let's recap our journey and review the answers to the key questions we identified at the beginning of this course. What is a component? We discovered that a component is a view defined with a template, logic defined with a class, and metadata defined with a decorator. Where do we put the HTML for our user interface? Either in the metadata using the template property or in a separate HTML file using the template URL file in the metadata. When should we use binding? Any time we want to display a component class property value in the view. When we want to control the DOM by setting a DOM element property in code. When we want to respond to user actions, and when we want to display a component class property, and update the property when the user makes a change. Why do we need a service? We uncovered several reasons for building a service. To implement functionality that is independent from any particular component, to share data and logic across components, and to encapsulate external interactions such as with data access. And how? How do we build an Angular application? With code that looks like this. Export a class, attach a decorator, and import what we need. Then put each component, directive, and pipe in its appropriate Angular module. Leverage the Angular CLI for creating, building, testing, and deploying your Angular application. Along the way, each module provided a set of checklists containing steps and tips. Feel free to revisit and reference these checklists as you start building your own Angular applications.
This course covered the basics, but there is so much more. On Pluralsight, the Angular: First Look course provides a comparison of Angular JS to the newer versions of Angular and presents a more full-featured sample application. The Angular CLI course covers much more about the Angular CLI. Angular Reactive Forms covers building and validating data entry forms with Angular and details basic CRUD or create, read, update, and delete operations using HTTP. In the Angular Routing course you'll learn how to define more complex routes, pass data to routes, guard your routes, pre-load data for your views, lazy load routes for better performance, and much more. Angular Component communication provides effective solutions for tracing and sharing state and sending notifications between components. This course teaches you numerous communication techniques. and more importantly which to use when, Angular Fundamentals expands on the basics and covers more intermediate information on every key aspect of Angular. And unit testing in Angular teaches everything you need to know to unit test your Angular projects. Including testing services, component templates and dealing with a synchronize code In addition to courses, the Angular documentation is very comprehensive and is a great reference to everything Angular. And I've set up a GitHub repository for this course so you can use it as a starting point or learning aide. It provides starter files if you want to try building an application yourself or you can review the completed course files here.
What Is Angular? (Revisited)
Congratulations! You've made it! Yay! You've completed the journey through the basics of Angular. But the road goes ever on and on. There are so many more paths for us to take and so much more to learn. Thoughts or comments about this course? Please use the discussion tab on the Pluralsight page for the course to leave your feedback and follow me on Twitter. It would be great to hear about your Angular adventures. Thanks for listening and I hope you enjoyed our adventure through Angular.
Deborah Kurata is a software developer, consultant, Pluralsight author, Google Developer Expert (GDE) and Microsoft Most Valuable Professional (MVP). Follow her on twitter: @deborahkurata
Updated11 Jul 2018