In Chapter 2, I built a quick and simple Angular application. Small and focused examples allow me to demonstrate specific Angular features, but they can lack context. To help overcome this problem, I am going to create a simple but realistic e-commerce application.
My application, called SportsStore, will follow the classic approach taken by online stores everywhere. I will create an online product catalog that customers can browse by category and page, a shopping cart where users can add and remove products, and a checkout where customers can enter their shipping details and place their orders. I will also create an administration area that includes create, read, update, and delete (CRUD) facilities for managing the catalog—and I will protect it so that only logged-in administrators can make changes. Finally, I show you how to prepare and deploy an Angular application.
My goal in this chapter and those that follow is to give you a sense of what real Angular development is like by creating as realistic an example as possible. I want to focus on Angular, of course, and so I have simplified the integration with external systems, such as the data store, and omitted others entirely, such as payment processing.
The SportsStore example is one that I use in a few of my books, not least because it demonstrates the ways in which different frameworks, languages, and development styles can be used to achieve the same result. You don’t need to have read any of my other books to follow this chapter, but you will find the contrasts interesting if you already own my Pro ASP.NET Core 3 book, for example.
The Angular features that I use in the SportsStore application are covered in-depth in later chapters. Rather than duplicate everything here, I tell you just enough to make sense of the example application and refer you to other chapters for in-depth information. You can either read the SportsStore chapters from end to end to get a sense of how Angular works or jump to and from the detail chapters to get into the depth. Either way, don’t expect to understand everything right away—Angular has a lot of moving parts, and the SportsStore application is intended to show you how they fit together without diving too deeply into the details that I spend the rest of the book describing.
Preparing the Project
The angular-cli package will create a new project for Angular development, with configuration files, placeholder content, and development tools. The project setup process can take some time since there are many NPM packages to download and install.
You can download the example project for this chapter—and for all the other chapters in this book—from https://github.com/Apress/pro-angular-9. See Chapter 1 for how to get help if you have problems running the examples.
Installing the Additional NPM Packages
It is important to use the version numbers shown in the listing. You may see warnings about unmet peer dependencies as you add the packages, but you can ignore them. Some of the packages are installed using the --save-dev argument, which indicates they are used during development and will not be part of the SportsStore application.
Adding the CSS Style Sheets to the Application
Adding CSS to the angular.json File in the SportsStore Folder
Preparing the RESTful Web Service
The SportsStore application will use asynchronous HTTP requests to get model data provided by a RESTful web service. As I describe in Chapter 24, REST is an approach to designing web services that use the HTTP method or verb to specify an operation and the URL to select the data objects that the operation applies to.
Adding a Script in the package.json File in the SportsStore Folder
To provide the json-server package with data to work with, I added a file called data.js in the SportsStore folder and added the code shown Listing 7-3, which will ensure that the same data is available whenever the json-server package is started so that I have a fixed point of reference during development.
It is important to pay attention to the file names when creating the configuration files. Some have the .json extension, which means they contain static data formatted as JSON. Other files have the .js extension, which means they contain JavaScript code. Each tool required for Angular development has expectations about its configuration file.
The Contents of the data.js File in the SportsStore Folder
This code defines two data collections that will be presented by the RESTful web service. The products collection contains the products for sale to the customer, while the orders collection will contain the orders that customers have placed (but which is currently empty).
The Contents of the authMiddleware.js File in the SportsStore Folder
This code inspects HTTP requests sent to the RESTful web service and implements some basic security features. This is server-side code that is not directly related to Angular development, so don’t worry if its purpose isn’t immediately obvious. I explain the authentication and authorization process in Chapter 9, including how to authenticate users with Angular.
Don’t use the code in Listing 7-4 other than for the SportsStore application. It contains weak passwords that are hardwired into the code. This is fine for the SportsStore project because the emphasis is on client-side development with Angular, but this is not suitable for real projects.
Preparing the HTML File
Preparing the index.html File in the src Folder
The HTML document includes an app element, which is the placeholder for the SportsStore functionality. There is also a base element, which is required by the Angular URL routing features, which I add to the SportsStore project in Chapter 8.
Creating the Folder Structure
The Additional Folders Required for the SportsStore Project
Folder | Description |
---|---|
SportsStore/src/app/model | This folder will contain the code for the data model. |
SportsStore/src/app/store | This folder will contain the functionality for basic shopping. |
SportsStore/src/app/admin | This folder will contain the functionality for administration. |
Running the Example Application
The development web server will start on port 4200, so the URL for the application will be http://localhost:4200. You don’t have to include the name of the HTML document because index.html is the default file that the server responds with. (You will see errors in the browser’s JavaScript console, which can be ignored for the moment.)
Starting the RESTful Web Service
Preparing the Angular Project Features
Every Angular project requires some basic preparation. In the sections that follow, I replace the placeholder content to build the foundation for the SportsStore application.
Updating the Root Component
The Contents of the app.component.ts File in the src/app Folder
The @Component decorator tells Angular that the AppComponent class is a component, and its properties configure how the component is applied. All the component properties are described in Chapter 17, but the properties shown in the listing are the most basic and most frequently used. The selector property tells Angular how to apply the component in the HTML document, and the template property defines the HTML content the component will display. Components can define inline templates, like this one, or they use external HTML files, which can make managing complex content easier.
There is no code in the AppComponent class because the root component in an Angular project exists just to manage the content shown to the user. Initially, I’ll manage the content displayed by the root component manually, but in Chapter 8, I use a feature called URL routing to adapt the content automatically based on user actions.
Updating the Root Module
There are two types of Angular modules: feature modules and the root module. Feature modules are used to group related application functionality to make the application easier to manage. I create feature modules for each major functional area of the application, including the data model, the store interface presented to users, and the administration interface.
The Contents of the app.module.ts File in the src/app Folder
Similar to the root component, there is no code in the root module’s class. That’s because the root module only really exists to provide information through the @NgModule decorator. The imports property tells Angular that it should load the BrowserModule feature module, which contains the core Angular features required for a web application.
The declarations property tells Angular that it should load the root component, the providers property tells Angular about the shared objects used by the application, and the bootstrap property tells Angular that the root component is the AppComponent class. I’ll add information to this decorator’s properties as I add features to the SportsStore application, but this basic configuration is enough to start the application.
Inspecting the Bootstrap File
The Contents of the main.ts File in the src Folder
Starting the Data Model
The best place to start any new project is the data model. I want to get to the point where you can see some Angular features at work, so rather than define the data model from end to end, I am going to put some basic functionality in place using dummy data. I’ll use this data to create user-facing features and then return to the data model to wire it up to the RESTful web service in Chapter 8.
Creating the Model Classes
Every data model needs classes that describe the types of data that will be contained in the data model. For the SportsStore application, this means classes that describe the products sold in the store and the orders that are received from customers.
The Contents of the product.model.ts File in the src/app/model Folder
The Product class defines a constructor that accepts id, name, category, description, and price properties, which correspond to the structure of the data used to populate the RESTful web service in Listing 7-3. The question marks (the ? characters) that follow the parameter names indicate that these are optional parameters that can be omitted when creating new objects using the Product class, which can be useful when writing applications where model object properties will be populated using HTML forms.
Creating the Dummy Data Source
To prepare for the transition from dummy to real data, I am going to feed the application data using a data source. The rest of the application won’t know where the data is coming from, which will make the switch to getting data using HTTP requests seamless.
The Contents of the static.datasource.ts File in the src/app/model Folder
The StaticDataSource class defines a method called getProducts, which returns the dummy data. The result of calling the getProducts method is an Observable<Product[]>, which is an Observable that produces arrays of Product objects.
The Observable class is provided by the Reactive Extensions package, which is used by Angular to handle state changes in applications. I describe the Observable class in Chapter 23, but for this chapter, it is enough to know that an Observable object represents an asynchronous task that will produce a result at some point in the future. Angular exposes its use of Observable objects for some features, including making HTTP requests, and this is why the getProducts method returns an Observable<Product[]> rather than simply returning the data synchronously.
The @Injectable decorator has been applied to the StaticDataSource class. This decorator is used to tell Angular that this class will be used as a service, which allows other classes to access its functionality through a feature called dependency injection, which is described in Chapters 19 and 20. You’ll see how services work as the application takes shape.
Notice that I have to import Injectable from the @angular/core JavaScript module so that I can apply the @Injectable decorator. I won’t highlight all the different Angular classes that I import for the SportsStore example, but you can get full details in the chapters that describe the features they relate to.
Creating the Model Repository
The Contents of the product.repository.ts File in the src/app/model Folder
When Angular needs to create a new instance of the repository, it will inspect the class and see that it needs a StaticDataSource object to invoke the ProductRepository constructor and create a new object.
The repository constructor calls the data source’s getProducts method and then uses the subscribe method on the Observable object that is returned to receive the product data. See Chapter 23 for details of how Observable objects work.
Creating the Feature Module
I am going to define an Angular feature model that will allow the data model functionality to be easily used elsewhere in the application. I added a file called model.module.ts in the SportsStore/src/app/model folder and defined the class shown in Listing 7-12.
Don’t worry if all the file names seem similar and confusing. You will get used to the way that Angular applications are structured as you work through the other chapters in the book, and you will soon be able to look at the files in an Angular project and know what they are all intended to do.
The Contents of the model.module.ts File in the src/app/model Folder
The @NgModule decorator is used to create feature modules, and its properties tell Angular how the module should be used. There is only one property in this module, providers, and it tells Angular which classes should be used as services for the dependency injection feature, which is described in Chapters 19 and 20. Feature modules—and the @NgModule decorator—are described in Chapter 21.
Starting the Store
In the sections that follow, I’ll use Angular features and the data in the model to create the layout shown in the figure.
Creating the Store Component and Template
As you become familiar with Angular, you will learn that features can be combined to solve the same problem in different ways. I try to introduce some variety into the SportsStore project to showcase some important Angular features, but I am going to keep things simple for the moment in the interest of being able to get the project started quickly.
The Contents of the store.component.ts File in the src/app/store Folder
The @Component decorator has been applied to the StoreComponent class, which tells Angular that it is a component. The decorator’s properties tell Angular how to apply the component to HTML content (using an element called store) and how to find the component’s template (in a file called store.component.html).
The Contents of the store.component.html File in the src/app/store Folder
The template is simple, just to get started. Most of the elements provide the structure for the store layout and apply some Bootstrap CSS classes. There are only two Angular data bindings at the moment, which are denoted by the {{ and }} characters. These are string interpolation bindings, and they tell Angular to evaluate the binding expression and insert the result into the element. The expressions in these bindings display the number of products and categories provided by the store component.
Creating the Store Feature Module
The Contents of the store.module.ts File in the src/app/store Folder
The @NgModule decorator configures the module, using the imports property to tell Angular that the store module depends on the model module as well as BrowserModule and FormsModule, which contain the standard Angular features for web applications and for working with HTML form elements. The decorator uses the declarations property to tell Angular about the StoreComponent class, and the exports property tells Angular the class can be also used in other parts of the application, which is important because it will be used by the root module.
Updating the Root Component and Root Module
Adding an Element in the app.component.ts File in the src/app Folder
Importing Feature Modules in the app.module.ts File in the src/app Folder
When you save the changes to the root module, Angular will have all the details it needs to load the application and display the content from the store module, as shown in Figure 7-4.
If you don’t see the expected result, then stop the Angular development tools and use the ng serve command to start them again. This will repeat the build process for the project and should reflect the changes you have made.
Adding Store Features the Product Details
The nature of Angular development begins with a slow start as the foundation of the project is put in place and the basic building blocks are created. But once that’s done, new features can be created relatively easily. In the sections that follow, I add features to the store so that the user can see the products on offer.
Displaying the Product Details
Adding Elements in the store.component.html File in the src/app/store Folder
This is an example of a directive, which transforms the HTML element it is applied to. This specific directive is called ngFor, and it transforms the div element by duplicating it for each object returned by the component’s products property. Angular includes a range of built-in directives that perform the most commonly required tasks, as described in Chapter 13.
Adding Category Selection
Adding Category Filtering in the store.component.ts File in the src/app/store Folder
The changes are simple because they build on the foundation that took so long to create at the start of the chapter. The selectedCategory property is assigned the user’s choice of category (where null means all categories) and is used in the updateData method as an argument to the getProducts method, delegating the filtering to the data source. The changeCategory method brings these two members together in a method that can be invoked when the user makes a category selection.
Adding Category Buttons in the store.component.html File in the src/app/store Folder
There are two new button elements in the template. The first is a Home button, and it has an event binding that invokes the component’s changeCategory method when the button is clicked. No argument is provided to the method, which has the effect of setting the category to null and selecting all the products.
Adding Product Pagination
Adding Pagination Support in the store.component.ts File in the src/app/store Folder
There are two new features in this listing. The first is the ability to get a page of products, and the second is to change the size of the pages, allowing the number of products that each page contains to be altered.
Adding Pagination in the store.component.html File in the src/app/store Folder
The new elements add a select element that allows the size of the page to be changed and a set of buttons that navigate through the product pages. The new elements have data bindings to wire them up to the properties and methods provided by the component. The result is a more manageable set of products, as shown in Figure 7-7.
The select element in Listing 7-22 is populated with option elements that are statically defined, rather than created using data from the component. One impact of this is that when the selected value is passed to the changePageSize method, it will be a string value, which is why the argument is parsed to a number before being used to set the page size in Listing 7-21. Care must be taken when receiving data values from HTML elements to ensure they are of the expected type. TypeScript type annotations don’t help in this situation because the data binding expression is evaluated at runtime, long after the TypeScript compiler has generated JavaScript code that doesn’t contain the extra type information.
Creating a Custom Directive
The Contents of the counter.directive.ts File in the src/app/store Folder
This is an example of a structural directive, which is described in detail in Chapter 16. This directive is applied to elements through a counter property and relies on special features that Angular provides for creating content repeatedly, just like the built-in ngFor directive. In this case, rather than yield each object in a collection, the custom directive yields a series of numbers that can be used to create the page navigation buttons.
This directive deletes all the content it has created and starts again when the number of pages changes. This can be an expensive process in more complex directives, and I explain how to improve performance in Chapter 16.
Registering the Custom Directive in the store.module.ts File in the src/app/store Folder
Replacing the Built-in Directive in the store.component.html File in the src/app/store Folder
Supporting the Custom Directive in the store.component.ts File in the src/app/store Folder
There is no visual change to the SportsStore application, but this section has demonstrated that it is possible to supplement the built-in Angular functionality with custom code that is tailored to the needs of a specific project.
Summary
In this chapter, I started the SportsStore project. The early part of the chapter was spent creating the foundation for the project, including creating the root building blocks for the application and starting work on the feature modules. Once the foundation was in place, I was able to rapidly add features to display the dummy model data to the user, add pagination, and filter the products by category. I finished the chapter by creating a custom directive to demonstrate how the built-in features provided by Angular can be supplemented by custom code. In the next chapter, I continue to build the SportsStore application.