What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
Angular Best Practices
by Jim Cooper
As you learn a new technology, it's easy to miss or forget some of the best practices. This course will teach you some of those best practices within Angular.
Start CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Learning Check
Recommended
Course Overview
Course Overview
Hello everyone. This is Jim Cooper, and welcome to my course on Angular Best Practices. This is the best practices course for Angular 2 and above. I've been a developer for over 20 years, and I've been building Angular applications ever since the 1.0 release of AngularJS in 2012. As we learn a new technology, it's easy to miss or forget some of the best practices, so I've complied the most important Angular best practices into a simple and condensed course where you can learn best practices, pro tips, and how to avoid pitfalls all in about an hour and a half. In this course, you'll learn best practices around Angular project and folder organization, Angular module organization, and how to use core, shared, and featured modules, Angular components, and services best practices, and some really important performance best practices to ensure you're building fast and scalable Angular applications. Plus, we'll cover a few basic coding best practices while we're at it. By the end of this course, you'll know all the key best practices to help you build respectable, high-quality, and scalable Angular applications. Before beginning the course, you should be familiar with the fundamentals of building Angular applications. If you would like to start there first, check out my Angular Fundamentals course before jumping in to these best practices. I look forward to joining you on this journey to learn Angular Best Practices with Pluralsight.
Getting Started with Angular
Introduction
Hi, this is Jim Cooper, and welcome to this Angular Best Practices module on application structure and organization. In this module, we'll take a quick look at some best practices around file and folder structure in Angular. But before we dive into that, let's talk a little bit about some prerequisites for this course. First, you'll definitely need to know some basic JavaScript. We won't be doing anything too complex, so if you feel like you have a decent grasp of basic JavaScript fundamentals, you'll be just fine. If you want to brush up on your JavaScript, choose a course from the beginner JavaScript learning path. You'll also want to have a basic understanding of HTML. As long as you can build a simple web page, that's really all you'll need to know. If you're fairly new to HTML, you may miss some concepts, but it shouldn't distract too much from learning how to create Angular applications. There's also an HTML learning path if you want to learn a little more. And then finally, you'll need a fundamental understanding of Angular. You certainly don't need to be an expert, but you'll need to understand a little about how Angular components and Angular modules work, plus a number of other basic fundamental Angular concepts. If you feel like you're lacking here, check out my Angular Fundamentals course first. Okay, now let's start jumping into best practices for file and folder structure. The Angular style guide has several recommendations to help with this, and most of them are related to LIFT. This is an acronym to remind us of a few key architectural principles. LIFT reminds us to organize our code in a way that allows us to locate code quickly, identify code at a glance, keep the flattest structure possible, and to try to be DRY, meaning try not to repeat yourself or try to avoid duplicate code. We'll touch on each of these in various places throughout the course. This module contains several recommendations that will help us with the first three items. Other modules will also touch on these items, but this module is particularly focused on these. We'll start off by getting our environment set up for those who want to follow along. Then we'll take a look at how to get started with the Angular CLI. Using the Angular CLI will help us get started, right off the bat, with several best practices. Then we'll take a look at recommendations for file naming for different types of files in our Angular app. And then, a really important part of good application structure is the folder structure, so we'll take a pretty deep look at recommendations on how to organize the files in our project. And finally, we'll briefly talk about the importance of not putting too many items in a single file. So let's jump right in.
Setting up Our Environment
We're going to need a few tools installed to get going. First, we'll need Git installed. I'm on Windows, so I'm going to use Git for Windows, which can be downloaded from here. If you're on a Mac, you can install SCM Git from here. And if you're on Linux, you can just run a command like this one from the terminal. I'll go ahead and download and install Git for Windows. And then I'll run that, and I'll just use the defaults throughout the install. And now that that's installed, I have this new Git Bash console that I can run. So we'll use that in a minute. And actually, while I prefer to use this Bash console, sometimes Windows users have issues with it. So if you're getting weird errors, try using the Windows console window instead. The next thing I need is Node. I prefer to install Node using Node Version Manager just because it makes it easier to switch between versions of Node as I develop different applications. On Windows, I can download that from here. For Linux and Mac, you can just run this command from the terminal. So I'm going to go ahead and install nvm for Windows, and I'll just use all the default options here too. Okay, now that that that's installed, I'm going to open by Git Bash console. On Windows, be sure to open it as administrator. Okay, now that I have nvm installed, I can just run nvm install 6.9.5. It's recommended that you use Node 6.9.5 or higher for this course to avoid any compatibility issues. So Node is downloading. And now I can just tell nvm to use this version of Node. Okay, we're good to go with Git and Node now. If you have problems using nvm to install Node, you can try installing it directly from here. Just choose the correct installer for your system. Okay, let's get coding.
Getting Started with the CLI
One of the best things you can do to help get on the right track with best practices is to use the Angular CLI. The Angular team has put a lot of thought into best practices around code organization, and the Angular CLI has been designed to help make it easy. And so there are a number of best practices that become easier if you're using the CLI. So that's our first recommendation. Use the Angular CLI to create new Angular projects. So let's take a look at how to do that. First, we need to install the Angular CLI globally, like this. So let's go ahead and install that. Okay, now that we have that installed, let's use the CLI to create a new project. First I'm going to change to my dev directory. The application that we'll be working with in this course is this one, Whitebeard's College for Witches and Wizards. It is a college registration site for young witches and wizards. This is what the completed site will look like. So to get started, I'm going to use the Angular CLI like this. You call the Angular CLI with ng, and then I'm going to say new, which means I want to create a new application, and I'll call that application whitebeards. And then I'll hit Enter. And you can see it's generating quite a bit of content here, and then it's doing an npm install for us. Okay, now that that's done, let's go take a look at what it generated for us. So here I've opened, in Visual Studio Code, the whitebeards folder that was just generated by the Angular CLI. And notice that we have this src folder here. And everything is pretty much in here except for these configuration files that are at the project level and this end to end testing folder. And then most of the files inside this src folder are going to be inside this app folder with the exception, of course, of a few top level client-side app files, like our index HTML, that will be loaded at app start-up, our main.ts file, which bootstraps our Angular app, our top level styles, and a few other configuration files. And then we have this assets folder that will store some of our images and where we could put any third-party libraries. We're not going to go into all the rest of this stuff that was generated by the CLI, that's a topic for another course, but all of our components and other code will go inside this app folder. However, for this course, we're not going to be building this app from scratch. I've already created one, and we'll start with that. So let's close this. And then back here in my terminal, I'm just going to delete this entire whitebeards directory. Be careful with this command. You don't want to delete the wrong thing with this command, so be sure you type it just like this. Okay, so that's deleting the folder that was generated by our Angular CLI. And like I said, for this course, we're going to start with an app that I already built. So while we're here, let's go ahead and clone this repo. And we'll clone that into a whitebeards folder. And then while we're here, let's go ahead and do an npm install. Oops, we'll have to be in the whitebeards directory. Now let's do that npm install. Okay, let's go ahead and go open up our Visual Studio Code. And here is the app that we just cloned from GitHub. So notice this was generated by the CLI also, so we have the same folder structure. And inside our src folder, we have our app folder. And you can see that I have a bunch of components and services, etcetera inside the app folder. And actually, we can start to see a number of problems with the way this project is organized. In fact, this whole project is a mess of bad practices. This is intentional, and we'll refactor all this as we go along in order to demonstrate best practices. Before we jump into that, let's take a look at what our app does. So let's go ahead and start it. I'm going to do an npm start, and then that'll take a second to compile our project. So this is the Angular CLI that's running all this for us. Angular CLI installed a webpack build for us, and that should be ready to go now on port 4200, so let's go take a look at that. Okay, so here's our functioning app, and everything's working, but there's a lot of poor practices that we need to fix. So let's get started discovering our best practices.
File Naming
Remember we talked about LIFT previously? This clip will focus on the L-I of LIFT, locate code quickly and identify the code at a glance. It's important that we're able to easily find things within our app. A big part of making this possible is naming our files in a way that makes it clear from the file names what the file contains. This project has a lot of organizational problems, and one of them is poor file naming. Let's start with this courses TypeScript file. By the name of this file, I'd kind of expect that it would contain a list of courses or something, but if we take a look, it actually contains a component. There are actually two things wrong with this file name. First of all, this component is what displays our catalog page. So when I click on this Catalog link, it's the courses component that's displayed. So this component would be better named catalog, but even catalog.ts is still a little vague. Other than it's in the components folder, there's nothing that indicates that this file contains a component. This will become even more important later because we're going to move this file out of this folder later in the course. So let's rename it. We'll go ahead and name this catalog.component.ts. Now it's clear that this is our catalog component. This naming style, which consists of a descriptor followed by a period and then followed by its type, is a recommended practice according to the Angular style guide. So we're going to start doing this everywhere in our app. In fact, we need to do this for this components template and CSS too. So this CSS file should be named catalog.component.css. So you can tell by looking at it that this file contains the styles for the catalog component. You may have expected this to be called catalog.styles.css, but it's clear that it contains styles by the css extension. So using catalog.component.css makes it clear that these are the styles for the catalog component. And we'll do the same thing for the template. We'll call this catalog.component.html. Now let's go ahead and rename those files. So over here, the catalog.css will be catalog.component, and in the templates folder, catalog.html needs to be catalog.component.html. All right, now all that's left is to go fix the component name in our routes and modules file. So over here in routes, we are importing CoursesComponent from components/courses, and it's going to be components/catalog.component, and that's going to import the CatalogComponent. And actually, you can see from this that I need to rename that still, so let's go back over there, and let's call this the CatalogComponent. We need to use that component here. And then same thing over in our app.module. So this is going to be CatalogComponent, and then we just need to use that down here. Okay, and our app should still be working if I come over and hit refresh. There we go. And now we just need to do that for all the rest of our components, and also for our services. I'm going to go ahead and do that off camera so you don't have to watch me do all that. Okay there. Now I've renamed all of our components to match the same style. You can see I've renamed all our components and their styles and templates. And also, notice that I renamed our data-repository.service to have the .service in its name. So this type of naming structure is our recommended best practice for this clip. Name your Angular files using this pattern. Next, we'll take a look at our folder structure.
Folder Structure
At first glance, it may look like our app structure is well-organized. Essentially, we have things broken down by their type. We have all the components in a component folder, services in a services folder, styles in a styles folder, etcetera. There are a few problems with this approach, however. First of all, when I'm working on a component, I am typically working on the component, the template, and the styles simultaneously. Having to jump around from folder to folder isn't very efficient. That's a good enough reason alone to change things. But there are even bigger problems with this approach. This approach isn't very scalable. If I want to break up my app into modules later, either for reusability or for lazy-loading purposes, it will be impossible with this structure. It also makes it harder if I'm going to eventually break the project up for multiple teams to work on since typically we do that by feature areas. So let's reorganize this into folders based on feature areas. This is a fairly small app, so we really only have two main feature areas, content related to the course catalog and content related to users. So let's create a catalog folder and a users folder. We'll create those right inside the app folder. Now let's move all our components, styles, and templates into the appropriate folders. So I'll move the catalog.component into the catalog folder, along with its styles and template. And then we'll have to update those paths inside the component, so they're just right next to the component now. All right, and we have this app.component and this nav-bar.component. These don't really belong to a feature area. These are application-like components. So let's just move those up to the app folder, along with the styles for the nav-bar.component. And actually, this account-menu component is also part of the nav-bar, so we'll move that up also with its styles too. Okay, that leaves us with just this loading-spinner.component and this sign-in.component. However, you'll notice something odd here. We have this register.component.css file, but no component to go along with it. Our application has both a sign-in page and a register page, but we can't seem to find the RegisterComponent here. And that's because the RegisterComponent is actually also in this sign-in.component file. So in here, you can see, once we get past these styles and template, that we have a SignInComponent here, and then down further we have a RegisterComponent. You can see how someone might feel like those are related since it's related to the user account, and you create an account, and then you sign in to it. But this isn't really a best practice to put multiple components in one file, so we'll fix that in the next clip. And in fact, this whole file is kind of a mess. It's really hard to locate a piece of code inside this file. That's the L in LIFT. So we'll revisit this file a couple of times over the next few modules as we clean things up. For now, we'll just move this file in to the users folder, along with the template for the register.component. And then we will update that template file URL right here. And we need to grab the styles too and update that URL. And we didn't have to move the sign-in.component template and CSS because that's actually embedded in this file. All right, so the only thing left in our components folder is this loading-spinner.component. This one is a little different because it's used in both our catalog feature area and our users feature area. So we'll leave this here for now and take a look at where to put that in the next module on Angular module organization. But other than this one file, you can see how we have two nice feature areas now with the catalog feature area and the users feature area, especially if we go ahead and delete these two folders that are now empty. Let's go ahead and update the routes and our app.module to point to the right locations for these files now. So CatalogComponent is now in a catalog folder, and the Register and SignInComponents are in the users folder. And then we'll have to do something similar over here in our app.module. NavBar is just right next to the module file. Oops, and the same with AppComponent here, and CatalogComponent is in catalog, and users. And then the AccountMenuComponent is also right next to our module file here. And it looks I failed to update the AccountMenuComponent and NavBarComponent to point to their new location for their styles, so let's update those. And it looks like the services, since the NavBarComponent was moved, its path to the services folder has changed, and its styles are right next to it now. Okay, things are compiling nicely, so let's go take a look at our app. Refresh, and there we go. Things are working just fine. And I can sign in over here, and sign out, and I can register. There we go. Now one thing to notice, when we organize this, if we look inside our catalog folder, you can see we three files related to the catalog.component. We could've put those in their own folders, same in the users folder. We could've created a register.component folder and a sign-in.component folder, but this is trying to help us honor the F in LIFT, which is try to keep the flattest structure you can. So you can probably see that we might eventually have multiple components inside our catalog folder and multiple components inside our users folder, but we'll just leave this flat for now. And if things become unwieldy, we can break things into smaller folders later. A good rule of thumb is that if you have more than seven files in a folder to consider making one or two subfolders. This is just a general guideline, but you wouldn't want to get too many more files in a directory than that or it becomes hard to locate them. Okay, great. Now that that's all organized, let's clean up that sign-in.component file.
One Item per File
Okay, let's take another look at this messy sign-in.component. There's a lot of stuff in here, including all of the styles and template for the sign-in.component, the SignInComponent itself, and this RegisterComponent. This is really making it hard to find things, so let's start cleaning this up. The best practice we'll address in this clip is the recommendation to have each file contain only one item. In this case, we have two components in one file, and to make matters worse, that file is poorly named since it doesn't even suggest that you would find the RegisterComponent in here. So to fix this, we're just going to move the RegisterComponent out of here. So let's create a register.component file. And then we'll cut and paste the RegisterComponent out of this sign-in.component file and paste it into here. And we'll need to add the imports for this, so let's go grab those. So I'll need Component, and we'll need the Angular form stuff. And then we're also going to need the Router and DataRepositoryService, so pretty much we need all of these imports. And our sign-in.component actually isn't going to need these form controls, so we can delete that. Okay, so now we have this register.component. We just need to go update the references to it in our app.module and routes files. So this now is coming from register.component, which makes a lot more sense. And then our routes file needs to make that same update. All right, and everything should still be working in our app. I should still be able to sign in and sign out and also register. There we go. So that's a lot better. Now it's really obvious where to find our RegisterComponent, and we don't have two things in one file. So remember this best practice, only one item per file, meaning don't put multiple components, services, directives, etcetera in a single file.
Summary
In this module, we examined the following best practices: using the Angular CLI, file naming, folder structure, and using one item per file. Following these best practices will help keep your projects well-organized allowing you to locate and identify code quickly. In the next module, we'll take a look at best practices around Angular module organization.
General Coding Best Practices
Introduction
Hi, this is Jim Cooper, and welcome to this Angular Best Practices module on General Coding Best Practices. In this module, we'll cover a few general best practices that are important for Angular applications, and most of them apply even outside of Angular applications. We'll start off by discussing the single responsibility principle and the importance of breaking files and services up into small pieces with just a single responsibility each. Then we'll take a look at some best practices around naming components, services, variables, constants, etcetera. We'll also talk about immutability and the importance of using it in JavaScript in general, and specifically, in Angular applications. And finally, we'll briefly talk about the importance of making sure the functions in your code never get too large and unwieldy. So let's take a look.
The Single Responsibility Principle
The single responsibility principle is a term coined by Bob Martin and is described here. This principle basically says that a single class or module should only have a single responsibility. If you remember from earlier, we had this data repository service that we couldn't move into an appropriate feature folder just yet. The reason why we couldn't do that is because this file is taking on too many responsibilities. This file is basically responsible for all data access. If we look at the methods in here, you can see that there's a method for getting the course catalog mixed with methods for working with users, such as saving the user and rolling and dropping classes and signing in. The fact that this file has taken on the responsibility of data access for both the catalog and for users makes it difficult to put it where it belongs. So let's create two separate files that will split up these two responsibilities. So here in the catalog folder, let's create a new catalog-repository.service. And let's just copy everything from the data-repository into this catalog-repository. Then we'll delete this USERS array, and we'll delete the user-specific functions here. Okay, this just leaves us with catalog-related functions. Now we just need to rename this to CatalogRepositoryService, and then we can rename this data-repository.service to a user-repository.service, and then delete from it the catalog-related functions. So I'll delete that function and the catalog data down here at the bottom. Okay, that should do it. And we would like to move this user-repository.service out of the services folder. And it seems like the right place for that would be in the users feature area, but even though this file only contains user-specific functions, it's actually used in both the users feature and the catalog feature. The catalog uses it for enrolling users into courses. So we will move this later, but we're not quite ready to tackle that until we create a core Angular module in the next section of this course, so we'll leave it here for now. But we do need to rename this to UserRepositoryService. Okay, great. So now we have a user-repository.service and a catalog-repository.service. However, the catalog-repository.service still has this currentUser variable that we don't need, so let's delete that, and it's going to have to get that from the user-repository.service, so let's import that. And then we just need to inject that in the constructor. And then we can just use that down here. Instead of setting this to this.currentUser, I'll set it to this.userRepository.currentUser. Okay, great. So now the catalog-repository is deferring to the user-repository to get the currentUser. Okay, now we just need to go clean up some imports. Let's start with our app.module. So instead of this DataRepositoryService, we actually need two services, a CatalogRepository and our UserRepository. And then we just need to provide those. All right, now let's just go update where these are used. So in our catalog.component, this is going to need both the CatalogRepository and the UserRepository. And then we just need to inject those down here. Okay, now we just need to use the appropriate repositories down here. So this is the catalogRepository, and this comes from the userRepository, and then we need the userRepository here also. Okay, and actually we need to update the template for this component too because you can see down here it's accessing this dataRepository. And actually I don't have a clip dedicated to this, but this doesn't feel like a great practice, accessing our repository from a template. It really might be better to move this into a getter property or a method on the component, but we'll leave it here for now, and we'll just rename this to userRepository. Okay, now over in our register.component we need the UserRepository, and we need to inject that correctly, and then use it down here. Okay, and then the same thing in the sign-in.component and in the nav-bar.component. Okay, cool. That should take care of everything. So let's just go take a look at our console and make sure everything's compiling. And it looks like it is. Okay, and let's make sure our app is still working. So I can refresh, and it looks like everything's still working just fine. Cool. That was a little bit of work, but things are much cleaner, and this is actually going to help us with some other best practices later on. Now let's take a look at some symbol naming best practices.
Symbol Naming Best Practices
If you're not familiar with the term symbol, it just refers to anything that you give a human-readable name to, such as variables, classes, properties, etcetera. The first best practice we want to talk about in this clip is making sure that your file naming and symbol naming are consistent. For example, if we take a look at our loading-spinner.component, the file is named loading-spinner.component, but the class name of the component is just LoadingComponent. To make that consistent, let's rename this to LoadingSpinnerComponent, and we'll have to rename that over in our app.module also. So this will be LoadingSpinnerComponent, and we'll just use that down here. Okay, next let's take a look at the recommended practice for naming constants. If we go take a look at our catalog-repository.service, you can see that we have two constants down here, COURSES and COURSE_CATALOG. This all uppercase naming convention is a pretty common practice in a lot of languages, but the recommendation for Angular apps is to just name them like all your other variables, so just basic CamelCase. So let's just lowercase these. In Visual Studio Code, I'm going to hit F2 to rename the symbol. This will take care of renaming it everywhere where it's used. And then we'll do the same thing up here for COURSES. The reason we can get away with not using the more intrusive uppercase naming is that TypeScript and most IDEs will tell us if we try to reassign our constants. Notice that if after declaring this variable I try to change it, my TypeScript-aware IDE tells me right away that it's a constant. We could also set up our IDE to do different syntax highlighting for constants if we wanted to, but either way, our IDE will tell us if we try to change a constants value. So the recommendation is to use the easier to read and write CamelCase convention for constants. So let's just go over to our user-repository.service and update this USERS constant also. Okay, now I just want to call out a few things that we've been doing all along that are best practices. First of all, classes like this, should be named UpperCamelCase, also called PascalCase, like we've been already doing here. And service names should be suffixed with the word Service like this, and components should be suffixed with the word Component. And then next, properties and methods, like these, should be LowerCamelCased, as you can see here. Okay, so that's a quick overview of symbol naming best practices. There's one more thing I wanted to mention that isn't really related to symbol naming, but doesn't really need an entire clip of its own, and that is the line spacing that we're using on our imports. If we take a look at the catalog.component, for example, notice that we are imparting third party imports first. And then we have a blank line followed by imports from our own app. This is recommended best practice that allows you to easily identify third party imports versus your own. Okay, next we'll take a look at best practices around immutability.
Using Immutability
Generally speaking, immutability is a good practice in JavaScript whether you're using Angular or not, so the recommendation to prefer immutability is a good recommendation in all your JavaScript code. If you're not familiar with immutability, it basically refers to not mutating existing objects in memory, but rather creating new objects. Using immutability can help you avoid certain classes of bugs, such as bugs that occur when a value is unexpectedly changed from somewhere else in the code. Using immutability can also help with certain types of change detection in Angular. So let's fix a few places where we're mutating state in our application currently. This is occurring in a few places in the user-repository.service. First, let's take a look at what we're doing in this enroll method. Notice that when a user enrolls in a class we push the classId onto the classes array on the currentUser object. This is mutating data in two ways. First of all, it's mutating the classes array by adding an object to an existing array, and it's mutating the currentUser object by changing the contents of the classes property of the currentUser object. We can change this to be immutable like this. So now we're using the Object.assign to basically create a new object with all the properties from the currentUser object, and then we're replacing the classes property with a new array that was created using the concat array function. The concat function is like push, except that it accepts an array and adds all the items from the array, and it returns a new array instead of mutating the existing array. So now we're using immutability to update the currentUser. Let's do something similar for our drop function. You can see that right now we're mutating the currentUser property. We'll fix that, like this. Okay, so now that's being updated in an immutable way. And the last thing we want to fix is in the saveUser function. Notice that we're passing in a user and setting currentUser to equal user. While this in not mutating the existing currentUser variable, it's actually replacing it, it does open us up to potential mutation from outside of this function. It's pointing our currentUser variable to the same user object that's passed in. So if the code, which calls saveUser, were to change its local user object after calling saveUser, it would actually change our currentUser object here. And that could lead to a pretty hard to find bug and may end up messing with change detection if you're going to use a more advanced type of change detection, such as OnPush. So let's fix that using Object.assign like this. Okay, so now that will update currentUser to a new object that is populated with all the properties from user. And now all this code is updated to be immutable. This is a great best practice for all of your JavaScript applications, including your Angular apps.
Small Functions
Using small functions is another best practice that's great for all JavaScript apps, and really apps written in any language, and it's a best practice for Angular applications too. If you find yourself writing a function that is more than 5 to 10 lines, ask yourself if it would be more legible if that function was broken down into a couple of smaller functions. This app isn't that complex, so there aren't a lot of places where this has been an issue. But in real world applications, it's really easy to accidentally create a really large function that is hard to read due to its size and complexity. If we take a look at our catalog.component, these functions are all pretty small and pretty easy to read. The only one that might be a little long is this applyFilter function. We could make this a little smaller by extracting this into a showOnlyGeneralCourses method, and actually that would read much better. Right now, if the filter that is passed in is set to GEN, then it only shows courses that meet this filter criteria, but what does that mean? It really means only show general courses, not courses in a specific discipline. So let's create a showOnlyGeneralCourses method. And now let's just copy these lines without the return keyword into here, and now we'll just call that function here. We aren't really using this return here. We're just using that as a short circuit way to exit out of the function after we call this. And now this reads real nice. If filter equals GEN, then only show general courses. So this is a real simple, but useful best practice. Try to keep all your methods nice and short like this. It makes the code really easy to read.
Summary
In this module, we examined a number of general coding best practices including the single responsibility principle, symbol naming, preferring immutability, and using small, legible functions. These are some great JavaScript practices that also have some benefits in Angular. In the next module, we'll take a look at best practices around Angular module organization.
Angular Module Organization
Introduction to Angular Module Organization
Hi, this is Jim Cooper, and welcome to this Angular Best Practices module on Angular Module Organization. In this part of the course, we'll take a brief look at Angular modules and the different types of modules we may want to create in an application. And then we'll take a deeper look into creating a core module, creating a shared module, and creating feature modules. If we take a look at the anatomy of an Angular application, it is basically a group of components. So you have a parent component, or your app component, and it contains one or more components, which contain other components, etc. And so, you can think of it as a tree, and an Angular module wraps all this together, including these components and services and directives and pipes and anything that they use. And this is what a really simple Angular app would look like. However, as applications get bigger, it's useful to break this up into multiple modules. So this might be a module, and this might be another module, and this another. This is helpful in a couple of ways. First of all, it just allows for a high-level of encapsulation, where everything within a module is packaged up together, and you only expose what's necessary to external modules. And then another great reason to create feature modules is that feature modules are easily lazy loaded, which helps with the performance of your application. We'll talk about lazy loading later in the course, but it's a best practice to create feature modules like this, whether you're going to load them lazily or eagerly. So let's take a look at the recommended types of modules to create in Angular. In this section of our course, we'll be talking about three types of recommended Angular modules, besides the required app module. This includes a core module, a shared module, and feature modules. The core module should contain any shared singleton services that are shared across multiple modules in your app. This is because the Angular injector creates a new instance of a service for every lazily loaded module that provides the service. And as we'll learn later, most of our feature modules should be lazily loaded, so this is important. So application wide singleton services need to be declared in only one module, and the core module is the right place. The core module should also contain any app-level components. This does not mean components that are shared throughout the application, but rather, components that are only used by the top-level app component. A good example of this is an application's navigation bar. There is a place, however, to declare application-wide shared components, and that is in the shared module. The shared module should contain any shared components, directives, and pipes. A good example of a shared component would be a loading spinner component, if that component is used throughout the application. And finally, we have feature modules. You will likely have multiple feature modules in your application, and these will typically correspond with the feature-level folders that we talked about previously. These are modules that are used to group together feature-level services, components, directives, and pipes. Okay, so let's go take a look at how we do all this in our demo application.
Creating a Core Module
So as we mentioned, a core module should be created to provide application-wide singleton services and app-level components. Using a core module keeps the main application folder from becoming too cluttered. So we have one application-wide singleton service, and that is the user-respository.service. This service is used throughout our application, and it's important that we only have one instance of the service, because we use it to store the currently logged-in user, and that needs to remain the same throughout the application. So the core module is the right place for that. The other item that we're going to put into the core module is this nav-bar.component. This component is only used in the top-level app component, so that also qualifies it to be in the core module. So let's move our user-repository.service first. We'll start by creating a core folder, and we'll move the user-repository.service into here. And now we'll create a core module, and this we'll need to import NgModule. And then we need to import our user repository. Okay, now we just need to create our basic module, just like we did with our app module, so that will look like this. And now if we go look at our app module, you can see that we import BrowserModule here. This module should be imported in the app module only, but the core module should import a module called CommonModule. So we'll go import that, and then we just need to add that as an import here. Alright, and now we just need to provide our UserRepositoryService, and we can stop providing that over here. Great. Now let's move our nav-bar.component. So we'll grab the component and CSS, and the nav-bar.component uses this account-menu.component, so we'll move that into the core module also with its CSS. Okay, now we just need to go declare those components in our core module. Alright, so let's just declare those here. And since these components are going to be used outside of the core module, in the app component, we'll need to export them. And we can remove those from the app module too. Okay, cool. So now we have a core module; to use it we just need to import it into our app module. So we'll import that up here. And then we just need to add it to our imports. Okay, now there are a number of places that we're using the user-repository.service, and it's moved, so we need to go update those references, first over in our nav-bar.component. Up here we were importing the user-repository.service, and we moved the location of our nav-bar.component, so this path is not quite right. That's just right here next to us, so we can just import it from right here. And then we'll have to do this same sort of fix where the service is imported in the catalog, register, and sign-in components, and in our catalog repository service. I'm going to go ahead and do this off-screen, but if you're following along, go ahead and update it in those places too. Alright, so I've updated it in those four other places, and you can see how this service is used throughout our application in various components across various features, and this is what really requires this service to be in core module, so that we can have one instance of this user that's keeping track of our current user. Okay, so we're almost done. But if you go look at our web page, you can see that it is failing to load, and if we look at the dev tools you can see that we're getting an error about routerLink. So that is a router directive that we're using inside the nav-bar.component. So that directive comes from Angular's router module. Now if we go over and look at our app module, you can see that we were importing RouterModule here, and then we're using it in our imports down here. We're doing a RouterModule.forRoot. We don't need to that exactly, but we do need to go into our new core module and import RouterModule and add it as an import here. And that should fix our application. There we go. And so you just need to remember when you're creating a core module that any directives that are being used inside that module, the module that they're in will also have to be imported inside your core module. Okay, so there was a lot of updating of our paths and things that we needed to do once we created that core module, but creating the core module itself really was not that hard.
Creating Shared Modules
As we discussed, the shared module is a place to provide components, directives, and pipes that will be shared across multiple modules in your project. Currently, we don't have multiple modules in our app, but pretty soon here we'll create a couple of feature modules for the catalog and user feature areas of our site, and those will both need this loading-spinner. So let's create a shared module that will provide that component. So first, we're going to create a shared folder, and we'll move our loading-spinner into that folder. And then we don't need this components directory anymore. And now we just need a shared module, so let's create that. And here's the basic template for our new module, now we just need to import our spinner component. And then we need to both declare it and export it so that it's available to modules that import this module. This module will be imported by our feature modules when we create them. And actually, feature modules need access to this CommonModule, so we can export that here too. Now the feature modules won't have to each import that separately. Alright, that's all we need here, let's just go clean up our app module. So it doesn't need this loading-spinner.component anymore. And then later when we have feature modules, we'll only import the shared module in the feature modules that need them. But for now, we'll just import it in our app module. It looks like I missed a semicolon up here. Alright, now we just need to import SharedModule, and we just need to delete our LoadingSpinnerComponent declaration. Okay, that was really simple. Now we have a shared module and it's providing the loading-spinner.component for us. Let's just go take a look at our site. Okay, it looks like our site is working. So if I register here and click Save, we'll see our loading spinner here. And if I enroll here, we see it here. So it's still working throughout our app. In the next clip we'll create a feature module, and once we've done that we'll have a place to import this shared module.
Creating Feature Modules
Okay, so looking at our app, it's pretty easy to see a couple of feature areas in our app. We have a catalog area and a users area. Let's take a look at how we'd create a catalog feature module. So in our catalog folder, let's create a catalog module. And here's a basic starting point for our module. Now we'll import the CatalogComponent and the CatalogRepositoryService. And then we need to declare and provide them. Alright, now our catalog component uses our loading spinner. And remember that's now in our shared module, so we need to import that. And the catalog component also uses the routerLink directive from Angular's router module, so we need to import that too. So we'll import them and then we'll just add them as imports. And now that we're importing our shared module, we're not actually going to need to import our common module, because that was already imported and re-exported from the shared module, so let's delete that. Alright, now we just need to go add the CatalogModule to our app module, and we'll just import that here. And since the CatalogComponent and CatalogRepositoryService are now being provided by this catalog module, we don't need to provide them anymore, so we can delete them here. And you can already see how our app module is becoming a lot more clean. And now we have this nicely encapsulated catalog module for the catalog feature area of our site. And that should be working. If we come over here and refresh, you can see that our catalog is working normally. And we're going to want to do that same thing for the users feature. But we're going to do that later when we talk about lazy loading modules in the performance module of this course. So that wraps up our Best Practices for Angular Modules.
Summary
In this course module, we studied the different types of Angular modules and we learned how to create a core module, shared modules, and feature modules. In the next course module, we'll take a look at best practices related to Angular components.
Angular Components Best Practices
Introduction
Hi, this is Jim Cooper, and welcome to this Angular Best Practices module on Angular Components. In this module, we'll be taking a look at the following best practices with regard to Angular components, using prefixes in component selectors, when to separate out our CSS and template files from our component and when it's okay to have them all combined, the recommended approach for creating input and output properties on components, delegating complex component logic to services, the recommended ordering sequence of component members such as properties and methods, implementing lifecycle hook interfaces, and finally, when to and not to create components. This seems like a lot, but it's not going to take too long, so let's jump right in.
Prefixing Component Selectors
We have a couple of components that have selectors that we use in our HTML. For example, this nav-bar.component has a nav-bar selector. It's actually a best practice to add a prefix to these selectors that match the feature area that they're in. This one is in our core module, which is kind of an app-level module, so we'll prefix that with wb, for whitebeards. Prefixing your component selectors like this avoids conflicts if you happen to import a module that has a component selector that conflicts with one of your own. So this can be really important. So let's go add prefixes to our other component that has a selector. Our catalog, register, and sign-in components actually don't have selectors because they're loaded as routes, but our loading-spinner.component has a selector, so let's go update it. This is in our shared module, which is also an app-wide module, so we'll prefix this one with wb also. We don't have any components with selectors in our feature areas, like in our catalog or user features. But if we had one in the catalog feature, for example, we'd prefix it with something specific to that feature. So maybe, cat- for catalog, and the same thing for user, maybe user-. Unfortunately, we don't have any components to demonstrate that with right now, but you get the idea. Prefixes are usually two to four characters to keep them short and from distracting too much from the actual component name. But they can really be whatever you want, just be sure to prefix them with something that represents the feature area that they're in. Now let's just go update a couple of places where these are used. So for the nav-bar.component, that's used in our app.component. So right here this just needs to be wb-. And then our loading-spinner.component is used in our register.component, that's down here by the Save button, this is wb-loading-spinner, and then it's also used in the catalog-component template. Okay, and that's all there is to it. Just remember to add these prefixes whenever you're using a selector.
Separating Component, CSS, and Template Files
Alright, let's take a look at this sign-in.component; it's a bit of a mess. The first thing that you'll notice is that finding the actual component code in here isn't totally straightforward. I mean, if you're familiar with the code, you'd know to just scroll to the bottom, but even finding where the template ends and the component begins isn't totally obvious. The li in lift, which stands for being able to locate code quickly and identify code at a glance, suggests that we should fix this. The Angular style guide recommends that if your template or CSS are more than three lines, you should extract them. If we take a look at the loading-spinner.component, this is a great example of a component that really makes sense to have it all contained in just this one file. Identifying all the parts of this component is super quick because there's not much here, so let's go clean up that sign-in.component. So we'll start by creating a sign-in.component.css file, and then let's copy the styles out of here, and put them in here. And notice the nice thing is that we're getting some nice CSS coloring in here that you don't get when it's embedded in your template. And that's fine when you only have a couple of lines, but for something big like this, it's really nice to have it in a separate file for that reason too. Okay, so over in our component now, we just need to update this to be stylesUrls, and that is an array of strings, and it's just going to be sign-in.component.css. And I've got a little typo here. Alright, now let's just do the same thing for this template. So we'll create a new file, this will be sign-in.component.html, and let's grab all of this. We'll cut that out of here, paste it over here, and then we just need to update our component here to have a templateUrl that points to that. Okay, so you can see how that really cleaned up this component. It's much easier now to locate and identify the pieces of code that we want. Let's just go take a look at our nav-bar.component. Here we've already pulled out our CSS into its own file, but let's pull out this template too. So we'll create a new file here, nav-bar.component.html, copy this over, and then change this to a templateUrl. Okay, cool. So this is a little more simple now too. One more thing that I want to demonstrate is in our app.component. You can see here that both our styles and our template are more than three lines. Our rule of moving them out if they're more than three lines suggests that we should move these. However, I kind of like the way this looks and I don't really plan on making changes to my app.component much, so I'm going to go ahead and leave these here. Hopefully I don't regret that later. And generally speaking, it's kind of up to you and your team to make decisions about how exactly you implement these best practices. I'd caution you to really think about it whenever you decide to ignore any of these best practices; a lot of thought and experience has gone into this style guide and these best practices, so whenever you ignore a best practice, there's a good chance you'll pay the price for it at some point. So try to adhere to them as much as possible. For this component though, I'm just going to leave it as it.
Decorating Input and Output Properties
With input and output properties on components, there are two ways to declare them. You can either declare them as inputs in your component metadata, as I've done here, or you can use input and output decorators. It's recommended that you avoid defining them the way I have here. If you look at the loading property here, it really isn't obvious that it's an input property. You have to look up in the metadata and mentally connect the declared inputs with properties. It's a lot more clear if you just use a decorator. The way I have it here also requires me, if I ever want to rename this property, to have to remember to rename it in both places. So we'll remove this input here and add an input decorator down here, don't forget these parenthesis, and then I just need to import that up here. There, now it's a lot more obvious that this loading property is an input property. And then the same syntax exists for output properties, and the same rule applies there. Decorating your input and output properties makes it more obvious which properties are used for input and output, and it makes it easier for renaming. It's also simply less code this way.
Delegating Complex Logic to Services
We talked earlier about the single responsibility principle. This comes into play in a lot of places in an application, and it definitely is a consideration in our components. Like everything else, we want our components to be as simple and focused as possible. That means if our component needs to do something that requires some complex logic, for example in response to some action that the user takes on the component, then we need to consider if that logic belongs in the component or not. If the logic is really simple, say just a line or two, then we can probably just leave it in the component. However, the more complex the logic gets, the more it qualifies to be moved out into a service. Let's take a look at our catalog.component. If we look at this component on the website, we have these buttons at the top that filter the catalog based on the different magical disciplines. Each of these calls this applyFilter function on the component. This method is somewhat borderline as to whether or not it should remain in this component, but it could be argued that this is getting complex enough to move it out. So to demonstrate this, let's create a filter-classes.service. So I'll create a new file, filter-classes.service, and here's the basic template for our service. And now we can just move this code over here. And then we'll just call this filterClasses. And instead of modifying a visible classes property, we're just going to say here, so if they don't provide a filter then we'll just return the classes that they pass in. And this is going to take a filter property and a classes property. And then same thing down here, we can just return classes.filter, and then down here same thing, except this is going to have to get passed in here. Okay, cool. So now we have this service that we can use to filter our classes. And you could argue that this could actually be a directive, and that would be fine too. But let's come over now to our catalog.component, and we are going to still need that applyFilter method. And in here we can basically just set this.visibleClasses, which is what we were setting before, equal to this.filterClassesService, and we need to go create that in here, and we'll pass in the filter and this.classes, which is our classes array in here in this component. So you can see how we are now deferring all of that logic to another service. Let's just go import that up here. Okay, and then we need to import that here too. And actually, now that I think about it, I think I have an error, yeah, okay, so this should be filterClassesService.filterClasses. We need to call the filterClasses function. Alright, now this is a new service, so let's go add that to our catalog module, and we just need to provide that. Okay, cool. So our filter should be working now. If we come over to our catalog we can still filter. And now our component, our catalog component, doesn't need to worry about the responsibility of that filtering logic. This is a design decision that you'll have to make in various cases as you build your own applications.
Component Member Sequence
Okay, there's a best practice that we've been doing all along in this project, and I just want to take a second to point it out. If you take a look at this register.component, or any of our other components really, you can see that the properties, like these, appear right here at the very top of our component, and the methods, like ngOnInit and registerUser, follow after the properties. This makes it really easy to see what properties exist on a component and they don't get lost by intermingling them down within the methods. So always put your properties up here at the top of your component. Another concept to consider with regard to class member ordering is that we should always put our public class members before the private members. This makes it easier to see what the public interfaces of our component are. Members are public by default in TypeScript, so unless we put the word private in front of our properties and methods, they're public. So you'll notice that methods on a component that are called by the template are public. You could try making these private, and you could see that it would actually work in depth, but when you go to compile for production, you'll get errors, so these need to remain public like they are now. However, there are times when you might have one of these methods call another method that is private. As an example of this, let's take a look at this registerUser method. You can see that it saves the user, and then when that finishes, it redirects to the catalog page. To make this more clear, I could extract this out to a method named saveAndRedirect, like this. So I'll cut this out here and call saveAndRedirect, pass in the user, and then here I'll paste this code. And this needs to be this.. Okay now our registerUser method looks really nice and clear. And the saveAndRedirect method can actually be private because it's not called by our template or anything else, only by this registerUser method. So I'm just going to mark this as private. Okay, and the guidance to put private methods after public methods says that I should move this method down here after the cancel method. This is really helpful because we want to easily see what the public interface of our component is, and this cancel method could have easily gotten lost amongst a bunch of other private methods if we didn't follow this advice. So for this reason, always push your private class members down below the public ones.
Implementing Life Cycle Hook Interfaces
There are a number of lifecycle hooks for Angular components. We've been using one of them already, the ngOnInit lifecycle hook that you can see here in our catalog.component. And there are a number of other lifecycle hooks as you can see here in the documentation. Each one of these comes with a corresponding TypeScript interface, and it's a best practice to have your components implement these interfaces. So if we take a look at our catalog component, you can see we're using ngOnInit here, but we haven't implemented the interface, so let's do this now. The interface for each of the lifecycle hooks is named exactly like the method, except without the ng prefix. So I'll just implement the interface for OnInit like this, and then I just need to import OnInit up here. Okay, so now if I remove my ngOnInit method down here, you can see that my editor is already notifying me that I've failed to implement the method. And you can see that over here, we're getting a compiler error. So let's put this back. This becomes even more useful with hooks like OnChanges. So if I implement OnChanges and import that, our editor's complaining, and if I come down here and implement ngOnInit or ngOnChanges, and OnChanges has a parameter named changes. And notice if I incorrectly assume that the type for this property is Boolean, that my editor is telling me here that I have not appropriately implemented OnChanges. The changes parameter for ngOnChanges is of data type SimpleChanges. So I would have to change this to SimpleChanges. And if I import that up here, then you can see my editor's not complaining. And over in our compiler we were seeing similar errors here also. So it's a best practice to implement these on your component like this so that you get the editor and compiler errors if you don't implement them correctly. So let's just clean this up, we really only need OnInit, then I can delete this ngOnChanges. Next let's take a look at when to and when not to create components.
When to (and Not to) Create Components
One of the great things about Angular is the ability to make custom components that encapsulate functionality and that are semantically expressive. A great example is this loading-spinner. Adding this component makes it very clear that this is a loading image that will be displayed when its loading state is set to true. This is more expressive and semantic than just an image tag with an ngIf. It's also reusable. And of course, this whole catalog.component is a great example of encapsulating a lot of HTML and JavaScript functionality into one component. But the question is, when should I make a component? If we look again at this loading-spinner.component, we can see in the template that it's really just an image tag, but I do like the expressiveness of this loading spinner tag. Now let's take a look at another potential candidate for a component. If we take a look at our catalog page when we're signed in, we can click on these Enroll and Drop buttons, and it toggles their state. If we take a look at the code for that over here in our catalog.component template, it's actually two buttons, as you can see here. We could encapsulate that in a component, and really that's not a terrible idea, but there is one thing to be aware of about getting too component happy. Imagine that this was encapsulated into a component. Let's go take a look at some of the attributes of the button element. So on the MDN docs here for button, a button has a number of attributes, such as autofocus, disabled, etc. If we were to encapsulate our buttons into a component, it would prevent the consumer of our component from applying bindings to these attributes on the buttons, because they're hidden behind the component. In order to support that, we'd have to add input properties on our new component, and then map them to the attributes on the buttons inside the component. Maybe that's not so bad in this case, but imagine an input element like a textbox that has a whole bunch of properties and methods, and if you started making components such as, for example, a red input box component, which is just a text input with red text, or say you decided to create wide input box and narrow input box components that have a fixed width for consistency across your site, this can have real consequences because you would end up creating input and output properties on each one of them for every single attribute that you want to allow developers to bind to when using your component. For this reason, it's good to exercise a bit of caution about getting too granular with your components. A loading-spinner isn't a bad abstraction, and you can argue that our enroll and drop buttons could maybe be a component, and that probably wouldn't be so bad, although I kind of like them how they are. But creating a red input box is probably going to cause more pain than it's worth, so consider this when you're creating components. Use them when it makes sense, be careful not to over use them.
Summary
In this module, we looked at the following best practices for Angular components, using prefixes in component selectors, when to separate out our CSS and template files from our component and when it's okay to have them combined, the recommended approach for creating input and output properties on a component, delegating complex component logic to services, the recommended ordering sequence of component members such as properties and methods, implementing lifecycle hook interfaces, and finally, when to and not to create components. In the next module, we'll take a look at a few best practices related to creating Angular services.
Angular Services Best Practices
Introduction
Hi, this is Jim Cooper, and welcome to this Angular Best Practices module on Angular services. In this module, we'll take a real quick look at the following best practices with regard to Angular services. First, we'll take a look at the right way to mark services as injectable. And then, we'll discuss using services for data retrieval. And lastly, we'll take a look at recommendations for working with the Angular injector and how to appropriately provide services in your modules. Let's go take a look.
Marking Services as Injectable
If we take a look at our catalog-repository.service, you can see that we've marked this catalog-repository.service class with this @Injectable decorator. This is necessary whenever a service injects another service. And you can see here that we're injecting the userRepositoryService, so this @Injectable decorator is required. However, you'll also notice that in our user-repository.service, we've also marked it as @Injectable, even though it doesn't inject anything in its constructor. The @Injectable decorator isn't actually required since the user repository doesn't inject anything. However, it's a recommended practice to just add it, because you never know when you're going to inject a dependency in here, and it'll be hard to remember to add the decorator then. So that's my first recommendation. And then if we go back over to our catalog-repository.service, there is actually an alternative to using this @Injectable decorator. Instead of using this here, I could mark this userRepository as an injectable, like this, and then I'd just need to import @Inject. But this is not recommended unless it's needed. You really only need to do this if the service you're injecting is not using the services datatype as the token for the service. For more information on using custom tokens for services, and when you need to do that, you can refer to the Dependency Injection module of the Angular Fundamentals course, but generally you don't need to do that. And the way we're injecting the user service here, its type is the token used by the injector. And typically this is just fine, so you won't need to use this @Inject syntax, and it's a little bit more code because you would have to do this for every parameter. So the recommendation is to not do this and to just use the @Injectable decorator.
Using Services for Data Retrieval
You may remember that we talked earlier about the single responsibility principle. This is the idea that a single class or object should just have a single responsibility. For this reason, all of our data access has already been broken out into separate services, like this catalog-repository.service. This wasn't required, but it's definitely a best practice. If we go take a look at our catalog.component, you can see we call the catalogRepository service right here in order to get the catalog. We could just simply do an API call right here, it really wouldn't be that much more code, but that gives our catalog.component too many responsibilities. The catalog.component shouldn't have to think about how to get the data, it shouldn't have to know that the data is coming from an API, for example, and that the API requires a GET call to a specific endpoint. The component just knows it needs catalog data, it just knows that it needs to get it from the catalog-repository.service, and the catalog-repository.service takes on the responsibility of how to get the data. It may get it from an API, it may get it from local storage, or anywhere else. And in fact, it is currently just getting it from memory for this demo app. But later, if we were to wire up this app to a real API, we could just change it in the catalog-repository.service. And everything that consumes the repository would just continue to work, even though the data is coming from a totally different place behind the scenes. This puts the responsibility in the right place, it makes the retrieval of logic reusable, and it makes testing much easier. So avoid the temptation to access your APIs directly in your components and extract that logic out into a service.
Service Injector Best Practices
It's important to understand how the Angular injector works so that we can use our custom Angular services according to best practices. If we look at our core.module, you can see we're providing the UserRepositoryService here. There's a really good reason for this, and to understand it, you need to understand Angular injectors. To envision how Angular injectors work, imagine you have an app with three modules, the app module, an eagerly-loaded feature module, and a lazily-loaded feature module. For every Angular app, Angular creates a root injector, which is responsible for injecting services wherever they're needed. So imagine we provide a service in this eagerly-loaded module. Angular actually registers that with the root injector. This makes it available to the entire app. But here's the catch that a lot of developers miss, including myself for a while. If you provide a service in a lazily-loaded feature module, Angular creates a new injector for that module and registers it there. This instance of the service is now only available to this module. This is true, even if these two services are the same class. For example, if I provided the user-repository.service in the eagerly- loaded feature module, and then provided it again in the lazily- loaded feature module, Angular would create two different instances of the service. And in the case of our user-repository.service, that would be really bad because it's supposed to keep track of the currently logged-in user. And if we have two instances, they'll be out of sync. This behavior of creating a second instance is unique to lazily-loaded feature modules. If I provided the same service into eagerly-loaded modules, Angular would only actually create one instance, and all of this is why we create a core module. This is place to put services that we want to be shared throughout our app without cluttering up the app module. This is a module that we know will never be lazily loaded, and so when we provide a service in this module, it gets registered in our root injector, so it becomes a singleton service that is available to our entire app. The core module is really just another eagerly loaded module, so it behaves the same. There's actually no technical difference between it and an eagerly- loaded feature module. It just represents the core of our app instead of a feature area. Hopefully this helps you understand how Angular injectors work. Just remember that if you provide a service in a lazily-loaded module, it is only available to that module, and if you need a single instance of a service to be available everywhere, define it in your core module, and only in your core module. Now you might be wondering, why not just provide all your services in the core module? Well if you have services that are really only needed in a particular module, then it makes sense to put them in the same folder and declare them in that module. It just gives a nice sense of organization to your project, even though all these services will technically be registered in the root injector when you provide them. So our best practice for this clip is to be aware of all of these injector behaviors and provide your services in the right places for both organizational and functionality purposes.
Summary
In this module, we discussed how to mark services as injectable and preferring the @Injectable decorator over the @Inject decorator where possible. Then we talked about using services for data retrieval in order to encapsulate all of the data access logic somewhere, so we don't have to worry about it everywhere in our app. And finally, we talked about the Angular injector and the appropriate way to provide services in our modules so that we can control the instantiation of those services appropriately. In the next module, we'll take a look at a handful of best practices to help us get the best performance out of our Angular apps.
Performance Best Practices
Introduction
Hi, this is Jim Cooper, and welcome to this Angular Best Practices module on Performance Best Practices. In this module, we'll take a look at some important performance considerations and best practices to help you get the best performance from your Angular apps. We'll start by looking at the ahead-of-time compiler and how the CLI makes it easy to get some out-of-the-box performance benefits. Then we'll look at lazy loading feature modules so the application only needs to download and process the code needed based on what parts of the site a user visits. Next we'll explore some suggestions and tooling to help make sure our deployable bundle sizes aren't getting too large. Following that, we'll investigate OnPush change detection, the disadvantages of it, how immutability enables it, and how OnPush change detection can help you with performance in some cases. Finally we'll take a look at pure and impure pipes and performance considerations that you should take into account when working with pipes. Okay, let's jump in.
Ahead-of-time Compilation and the CLI
Without ahead-of-time compilation, your entire app gets sent to the browser, along with a bunch of Angular code that is used to compile your app, and then that compilation occurs on the fly in the browser at runtime. And it's a lot of extra stuff to both download and execute that you don't want happening at runtime in production because it'll slow down your app. This is where ahead-of-time compilation comes into play. Ahead-of-time compilation compiles the app and creates a distributable application, which you then deploy to your production servers. That way the compilation happens only once before deployment, and only your complied app gets sent to the browser in production, which saves a lot of time. Ahead-of-time compilation is easy if you use the Angular CLI to build your apps. And so that's our first performance-related best practice, use the Angular CLI. It makes performance tuning a lot easier. Our second performance related best practice is to be sure to ahead-of-time compile your app before deploying it to production. So let's go take a look at how to do this. As we discussed in the beginning of this course, this app that we've been working with was started using the Angular CLI. As a reminder, you can install the Angular CLI globally using npm with a command like this one. Once I had the Angular CLI installed, I just ran a command like this to get started. And this generated the starting point of the project for me. I've been running this app in dev mode using npm start, and you can see here when I run this that right here the npm script is using the Angular CLI to do an ng serve, which serves the app in dev mode. So if we go take a look at the network tab in Chrome, and refresh this, you can see here that it's downloading about 3.5 MB. This is a lot bigger than it needs to be because we're running in dev mode. Let's take a look at what we get if, instead, we compile this using the ahead-of-time compiler. To do that, we just need to run ng serve with a prod flag. We can do that with npm, like this. This prod flag will be passed along to the ng serve command, and when you run ng serve with a prod flag, it will build the app with production settings, which includes ahead-of-time compilation. So now if we do a full refresh over here, you can see that the downloaded application shrunk down to about 280K. That's pretty significant. This is because the prod build complied our app for production in advance, and so it doesn't need to send all those runtime compilation files to the browser. It also did some minification of our JavaScript. Now if we go back to our console here and scroll up a little bit, this is warning me not to use this built-in web server that we get from the CLI in production. So using the CLI, all we have to do to package up our app for production is run npm run build and pass it the prod flag. So if I run this, it will bundle everything up for production. And notice that this created this new dist folder. This is what we would deploy to production, and if we take a look at the files that are in that folder, you can see that it is nicely packaged up for production, including minified bundles and an index.html. Building for production and doing ahead-of-time compilation makes a big difference, and it's made easy with the CLI. It is the number one best practice that you can do to improve performance in your Angular apps. Next we'll take a look at the performance benefits we can get from lazy loading feature modules.
Lazy Loading Feature Modules
Another fairly easy performance win is to create lazy loadable modules where possible. If we take a look at this users feature area in our app, you can see that it contains the register.component and the sign-in.component along with their template and CSS. And these components aren't even used unless the user goes to the register or sign-in portions of our site. So there's no reason to even download them from the server when the app is first loaded. This may seem pretty trivial in this case with this one tiny module, but imagine a much larger site with, say, 50 distinct features. You could save a lot of time on your initial page render by only downloading the one feature that you need when the app first loads. Luckily, Angular makes this easy. In order to lazy load a feature area, it first has to be bundled up in a module. Currently, these two components are still part of the app module. So let's create a lazy loadable feature module out of this users feature. First we'll create a users.module, and here's our basic shell for this module. So notice that we're importing CommonModule, and in addition to this we're importing ReactiveFormsModule, FormsModule, and RouterModule. These are all things that are going to be used inside our users module. And this module's going to use the loading-spinner, which is in our shared module. So let's import our shared module here. And then we'll just declare that down here. And now we just need to import and declare our components. So we'll import our RegisterComponent and our SignInComponent, and then we'll just declare them down here. Okay, cool. Now we can stop declaring them over here in our app.module, so we'll delete the imports and the declarations here. Okay, so now we have a users module. Now the lazy loading part. We take care of lazy loading a module in the routes, which kind of makes sense since you want to load the module when the user navigates to that part of the site. And in here, we have these two user-related routes. The first half of this, the /users, will remain in this file, and then the child routes will get moved into their own routes file in the users module. So while we're in here, let's create our lazy loaded route. That will look like this. So notice this new loadChildren syntax. And what we provide here is the path to and name of our users module, like this, and the #UsersModule. So this says for any URL that starts with /users, load this module from this path and continue resolving the routes using that module's child routes. So let's go create those child routes. We'll start by grabbing these out of here, and then over here in the users.module, right here where we're declaring the RouterModule, we will define these routes using forChild, like this, and then we define our routes here. So I'm going to paste those routes in here, and then we just need to delete this users/ from each of these because that's already defined in our parent route. And then again here, even though modules typically need to import CommonModule, we don't need to do it here because we've imported our SharedModule that is already exporting the CommonModule. And then notice this RouterModule.forChild, is similar to what we did over in our app.module, except notice here we used RouterModule.forRoot. Okay, cool. Now let's go check this out. First let's make sure we're running in prod mode, so I'm going to stop our server and do an npm start and pass in the prod flag. So this will serve it in prod mode. Okay, so if we go over to our browser, and open our dev tools on the Network tab and then refresh, okay, notice down here that I've loaded about 284K. This is a little bit smaller than we were loading previously. And notice if I clear this file list and then click on Catalog that we don't see any new files loaded. But if I click on Sign In, notice that it loaded another chunk for us. That's our users module that's being loaded, and it was not loaded when I first loaded this site. And so you can see down here that that was an additional 4.3K that was saved on the initial site load. Again, this doesn't seem like much, but in a larger app with dozens of modules, it can really make a difference in your app performance, especially in that initial page load time that is so important when the user first comes to your site. So it's recommended that you lazy load your feature modules, and Angular makes that really easy. Next we'll take a look at how we can manage the size of the bundles we're sending to the browser.
Monitoring Bundle Sizes
A big part of application performance is the amount of JavaScript that the browser needs to download and parse, so knowing how large your application is from release to release is important, and things can easily creep in that unexpectedly and dramatically affect your application size. When you build for production, which we can do like this, the CLI build process creates multiple bundles. And if we take a look at our dist folder, after doing a prod build, you can see here, oops, you can see with this command, that our biggest bundle is this vendor bundle that contains third-party libraries. You'll want to pay attention to all these bundle sizes so you're not surprised by some import that dramatically affects the size of one of your bundles. The size of this vendor bundle is a little surprising, so let's see if we can figure out why it's so large. There are a few tools that you can use for this. One that I like is called source-map-explorer. You can install that with npm, like this. I've already installed this, so I'm not going to do it. And then in order for source-map-explorer to work, we need to generate our source maps with our build. By default, when building for a production like we just did, source maps are not included. We can override that, so let's go back to our main app directory and run this command. Okay, so that will do a production build with Source Map. Okay, so now if we go back into our dist folder and take a look, we now have source maps. So now we can use source-map-explorer, which we can do like this, source-map-explorer, and then you provide the name of the bundle you want to look at. We want to look at this vendor bundle. So if I run that, you can see it generated and opened this visual map that allows us to quickly see what is taking up the most space in this bundle. And this whole giant section over here is all RxJS. Wow, that's insightful. This is actually happening because I accidently imported way more than I needed to when importing RxJS packages. If we go back over to our code and take a look at our catalog-repository.service, the problem stems from these imports up here. I'm importing Observable and Subject from rxjs/Rx. I can make this more efficient by changing the app, like this, and I can delete this up here. So now I'm just loading the observable and subject packages instead of the whole RxJS package. And we have the exact same problem over in our user-repository.service. So let's just copy and paste the fix from our catalog-repository.service over to here. And then in this repository I'm using a couple of other RxJS operators, so I need to import those now too. Okay, now let's go rebuild our app. So I'm just going to run this command again. And now if we go take a look at our bundle sizes, you can see that our vendor bundle has shrunk by about 150K; that's almost a 30% improvement. Now if we look at our source map now, you can see that rxjs is taking up a much smaller portion of this bundle. Almost everything in our vendor bundle is Angular stuff now, so that's perfect. Since it's so easy for bundle sizes to grow without realizing it, I think it's important to pay attention to it. And you could manually check the size of your bundles prior to each release, but that's easy to forget to do, so you may want to consider adding something to your continuous integration build that notifies you if your bundle sizes change significantly. That way you'll know immediately if you inadvertently import something surprisingly large. And that's really easy to do unwittingly, and this rxjs case is a good example of the importance of watching those bundle sizes. If I had been watching them when I added RxJS, I would have likely noticed that importing RxJS had a much bigger impact than I expected. So that's our best practice for this clip. Keep an eye on those bundle sizes.
Improving Performance with OnPush Change Detection
I wanted to briefly mention OnPush change detection in this part of the course so that you're aware of it and when it might be useful. We won't be looking very deeply at how to implement it and all of the consequences of doing so since that's outside the scope of this course, but I want to be clear that the recommendation is definitely not to always use OnPush change detection. In most applications, there won't be a need for it, and using it can be really painful. I couldn't imagine working in an application where the entire app was using OnPush change detection, however, you may occasionally have a need for one of your pages to use it, so let's talk briefly about what it is and when it might be useful. To understand OnPush change detection, we first need to understand how Angular's default change detection works. If we take a look at our Catalog page, we have bindings for every one of these fields. We can see those bindings in the template over here. Every one of these bindings is something that Angular needs to be aware of and watch for changes in, and if we go back to the page, if we click on each one of these buttons, clicking on these buttons is causing Angular's change detection to fire and update the view, so how does it do that? Basically, Angular monkey patches all low-level browser events such as click, mouseover, etc., in addition to some functions like set timeout. Whenever Angular detects one of these events, it checks every one of these bindings to see if they've changed. So if we look at the code again, after every event it checks every one of these properties on this class object to see if their current values are different from the prior value, and updates the view whenever it sees a difference. So Angular has to evaluate all 10 or so bindings for every row in this table, and it will have to reevaluate every one of these bindings, even if just one of them changes. Angular can do this very quickly, so this number of bindings with the amount of data that we're working with in this app is totally manageable, and really, Angular can handle tens of thousands of checks pretty quickly, but what if you have some crazy page that has lots of bindings and really needs to reevaluate them all the time, or perhaps even more likely, what if reevaluating some of these bindings is a little costly. Say that you have to do an expensive calculation to generate the value for one of these bindings. In that case, you would really only want to reevaluate your bindings when it's really necessary. This is where OnPush change detection comes in. In order to enable OnPush change detection, all we have to do is over here in the component metadata, just add the changeDetection property, like this, and then we just need to import ChangeDetectionStrategy. With OnPush change detection, Angular will only do comparison for the object or array references, not for every one of the properties in the objects, or every one of the items in the arrays. In addition, instead of change detection firing for every browser event, OnPush detectors only fire when input references change or when an async observable fires an event, and in a few other cases. This is oversimplifying things a little bit, but change detection is a whole topic of its own, and beyond the scope of this course. It's helpful to know about OnPush change detection and to have an idea of the scenarios where it may be helpful to use it, but it's a little painful to use, so definitely don't just go using it everywhere. But if you have a component that is having performance issues, and you determine that change detection is causing expensive expressions to be reevaluated too frequently, you may want to look into OnPush change detection, and when you do, you'll find that using immutability becomes important, because as we mentioned, Angular will look at reference changes, not property changes. So when you want to actually change something in the view, you need to be sure Angular notices it by changing the object or array references, not just by mutating its properties. Another good reason to use immutability in Angular, so be aware of OnPush change detection. You won't typically need it, but it's there if you do, and I recommend looking at other ways to solve your problems before turning on OnPush detection. In the next clip, we'll take a look at performance using pipes.
Pure and Impure Pipe Performance
There are some performance considerations when it comes to creating your own custom pipes. To explore this, imagine we wanted to add sorting to this catalog list. Let's just start by making it so that if you click on this Professor column, it will sort the classes by professor name. So first, let's go create a custom order by pipe. So I'm going to right-click here and create a new order-by.pipe. We'll discuss the wisdom of this approach soon, for now I'm just going to paste in the guts of this pipe here. You can pause this here and type this in if you're following along if you'd like, but I don't want to focus too much on the contents of this pipe. Basically, it's just a pipe that takes in an array and a field name and it sorts the objects in the array by that field. So let's go add this pipe to our catalog.module. First we need to import it and declare it. Alright, now let's go use it. Over here in our catalog template, we'll update the click on the Professor column to set an orderByField name, like this, and then we'll our pipe to this visibleClasses array, and we'll pass in the field name that is set to sort by. Okay, let's go check that out. So over here if we click on our Professor column now, you can see it sorted our classes by professor name. But we have a potential issue here. Let's throw a button on our page here that changes one of these professor names. So just above our table here I'm going to add a button, and when we click on that we're going to call a function called mutateFirstProfessor. Okay, let's go create that function in our component, and that will just look like this. So notice that this function is just taking the first class in our visible classes and setting the professor to Zebraman. Let's add our semicolon here. Okay, so let's go see what happens here. So we have our unsorted list here; I'm going to go ahead and sort it. And now I'm going to click on this Mutate First Professor button. So notice it did change the professor name to Zebraman, but our orderBy pipe did not re-execute and sort things. This class now should be at the bottom of the list because we're sorted by professor name. This is because pipes are pure by default, meaning they don't work with data mutation. They only get reevaluated if the object reference changes that the pipe is being applied too. So there are a few ways that we can handle this. The easiest and dirtiest way is to just make the pipe impure. We can do that over here in our pipe by just setting pure to false. But using impure pipes comes at a real performance cost. Sorting can be a fairly expensive operation. Let's add a console.log into our pipe here so every time transform is called on our pipe, it's going to console.log Sorting. Now let's go open up our console and let's just refresh this and see what we get logged. Oops, let's switch to our console. There. You can see when we ran this that our sort pipe was called five times just to load this page initially. And sorting is not a cheap operation. For this reason, I'd recommend not to ever use impure pipes, for sorting or anything else. There are better ways to solve this problem. Probably the best recommendation is just to sort the data in your component code, and don't use a pipe for things like sorting. Alternatively, we could make this work as a pure pipe using immutability. To demonstrate this, let's go change our pipe back to a pure pipe, we'll set that to true, or we could remove it because that's the default behavior, and then let's add a second button here that will update our professor name using immutability. We'll just call this updateFirstProfessor, and we'll say over here Immutability Update First Professor. Okay, so over in our component we'll create that method called updateFirstProfessor and we will set visibleClasses to a new array. So notice this is already using immutability because we're going to replace the existing visible classes with a new reference, and we'll set that to a new array, and the first object in that array is going to be the first professor with the name replaced. So I'll use Object.assign and we'll grab this.visibleClasses 0, and we'll replace the professor property with Zebraman. And then the remaining items in the list will be everything else in the array. So let's go try that out. So if we refresh, notice that sorting is only being called twice now, not five times, because we are using pure pipes. And if I sort this, notice that if I mutate the first professor, the sorting does not work because we're using pure pipes as we demonstrated earlier. But if I refresh and sort this again, and then immutably update the first professor, notice that it updated the name and the sort fired. This is because the pure pipe noticed the change in the reference to the visible classes, and so it re-executed the sort. This is a much better practice than using impure pipes, because impure pipes can get updated frequently and the logic in pipes can be a little expensive. You may want to consider whether to use pipes for this, or whether you want to do it yourself in your component code so you have more control.
Summary
In this Angular Best Practices module, we discovered best practices to help you get the best performance from your Angular apps. We first looked at the ahead-of-time compiler and the CLI, and we looked at using them to create production builds. Then we discovered how to lazy load feature modules to make things load more quickly. And we explored some suggestions and tooling to help keep our bundle sizes under control. And following that, we scratched the surface on OnPush change detection and how it can help you with performance in some specific use cases. Finally, we looked at performance implications with pure and impure pipes. This concludes this course on Angular Best Practices. I hope you found this course helpful, and I wish you luck in your Angular adventures.
Course author
Jim Cooper
Jim Cooper is a software developer at Pluralsight. With more than 20 years of software development experience, he has gained a passion for Agile software development -- especially Lean.
Course info
LevelIntermediate
Rating
(156)
My rating
Duration1h 41m
Released23 Oct 2017
Share course