In this chapter, I continue adding features to the SportsStore application that I created in Chapter 7. I add support for a shopping cart and a checkout process and replace the dummy data with the data from the RESTful web service.
Preparing the Example Application
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.
Creating the Cart
The user needs a cart into which products can be placed and used to start the checkout process. In the sections that follow, I’ll add a cart to the application and integrate it into the store so that the user can select the products they want.
Creating the Cart Model
The Contents of the cart.model.ts File in the src/app/model Folder
Individual product selections are represented as an array of CartLine objects, each of which contains a Product object and a quantity. The Cart class keeps track of the total number of items that have been selected and their total cost.
There should be a single Cart object used throughout the entire application, ensuring that any part of the application can access the user’s product selections. To achieve this, I am going to make the Cart a service, which means that Angular will take responsibility for creating an instance of the Cart class and will use it when it needs to create a component that has a Cart constructor argument. This is another use of the Angular dependency injection feature, which can be used to share objects throughout an application and which is described in detail in Chapters 19 and 20. The @Injectable decorator, which has been applied to the Cart class in the listing, indicates that this class will be used as a service.
Strictly speaking, the @Injectable decorator is required only when a class has its own constructor arguments to resolve, but it is a good idea to apply it anyway because it serves as a signal that the class is intended for use as a service.
Registering the Cart as a Service in the model.module.ts File in the src/app/model Folder
Creating the Cart Summary Components
The Contents of the cartSummary.component.ts File in the src/app/store Folder
When Angular needs to create an instance of this component, it will have to provide a Cart object as a constructor argument, using the service that I configured in the previous section by adding the Cart class to the feature module’s providers property. The default behavior for services means that a single Cart object will be created and shared throughout the application, although there are different service behaviors available (as described in Chapter 20).
The Contents of the cartSummary.component.html File in the src/app/store Folder
This template uses the Cart object provided by its component to display the number of items in the cart and the total cost. There is also a button that will start the checkout process when I add it to the application later in the chapter.
The button element in Listing 8-4 is styled using classes defined by Font Awesome, which is one of the packages in the package.json file from Chapter 7. This open source package provides excellent support for icons in web applications, including the shopping cart I need for the SportsStore application. See http://fontawesome.io for details.
Registering the Component in the store.module.ts File in the src/app/store Folder
Integrating the Cart into the Store
Adding Cart Support in the store.component.ts File in the src/app/store Folder
Applying the Component in the store.component.html File in the src/app/store Folder
Notice how clicking one of the Add To Cart buttons updates the summary component’s content automatically. This happens because there is a single Cart object being shared between two components and changes made by one component are reflected when Angular evaluates the data binding expressions in the other component.
Adding URL Routing
Most applications need to show different content to the user at different times. In the case of the SportsStore application, when the user clicks one of the Add To Cart buttons, they should be shown a detailed view of their selected products and given the chance to start the checkout process.
Angular supports a feature called URL routing, which uses the current URL displayed by the browser to select the components that are displayed to the user. This is an approach that makes it easy to create applications whose components are loosely coupled and easy to change without needing corresponding modifications elsewhere in the applications. URL routing also makes it easy to change the path that a user follows through an application.
The URLs Supported by the SportsStore Application
URL | Description |
---|---|
/store | This URL will display the list of products. |
/cart | This URL will display the user’s cart in detail. |
/checkout | This URL will display the checkout process. |
In the sections that follow, I create placeholder components for the SportsStore cart and order checkout stages and then integrate them into the application using URL routing. Once the URLs are implemented, I will return to the components and add more useful features.
Creating the Cart Detail and Checkout Components
The Contents of the cartDetail.component.ts File in the src/app/store Folder
The Contents of the checkout.component.ts File in the src/app/store Folder
Registering Components in the store.module.ts File in the src/app/store Folder
Creating and Applying the Routing Configuration
Now that I have a range of components to display, the next step is to create the routing configuration that tells Angular how to map URLs into components. Each mapping of a URL to a component is known as a URL route or just a route. In Part 3, where I create more complex routing configurations, I define the routes in a separate file, but for this project, I am going to follow a simpler approach and define the routes within the @NgModule decorator of the application’s root module, as shown in Listing 8-11.
The Angular routing feature requires a base element in the HTML document, which provides the base URL against which routes are applied. This element was added to the index.html file by the ng new command when I created the SportsStore project in Chapter 7. If you omit the element, Angular will report an error and be unable to apply the routes.
Creating the Routing Configuration in the app.module.ts File in the src/app Folder
The RouterModule.forRoot method is passed a set of routes, each of which maps a URL to a component. The first three routes in the listing match the URLs from Table 8-1. The final route is a wildcard that redirects any other URL to /store, which will display StoreComponent.
Defining the Routing Target in the app.component.ts File in the src/app Folder
Navigating Through the Application
With the routing configuration in place, it is time to add support for navigating between components by changing the browser’s URL. The URL routing feature relies on a JavaScript API provided by the browser, which means the user can’t simply type the target URL into the browser’s URL bar. Instead, the navigation has to be performed by the application, either by using JavaScript code in a component or other building block or by adding attributes to HTML elements in the template.
Navigating Using JavaScript in the store.component.ts File in the app/src/store Folder
The constructor has a Router parameter, which is provided by Angular through the dependency injection feature when a new instance of the component is created. In the addProductToCart method, the Router.navigateByUrl method is used to navigate to the /cart URL.
Adding Navigation in the cartSummary.component.html File in the src/app/store Folder
The value specified by the routerLink attribute is the URL that the application will navigate to when the button is clicked. This particular button is disabled when the cart is empty, so it will perform the navigation only when the user has added a product to the cart.
Importing the Router Module in the store.module.ts File in the src/app/store Folder
Guarding the Routes
Remember that navigation can be performed only by the application. If you change the URL directly in the browser’s URL bar, the browser will request the URL you enter from the web server. The Angular development server that is responding to HTTP requests will respond to any URL that doesn’t correspond to a file by returning the contents of index.html. This is generally a useful behavior because it means you won’t receive an HTTP error when the browser’s reload button is clicked. But it can cause problems if the application expects the user to navigate through the application following a specific path.
As an example, if you click one of the Add To Cart buttons and then click the browser’s reload button, the HTTP server will return the contents of the index.html file, and Angular will immediately jump to the cart detail component, skipping over the part of the application that allows the user to select products.
For some applications, being able to start using different URLs makes sense, but if that’s not the case, then Angular supports route guards, which are used to govern the routing system.
The Contents of the storeFirst.guard.ts File in the src/app Folder
There are different ways to guard routes, as described in Chapter 27, and this is an example of a guard that prevents a route from being activated, which is implemented as a class that defines a canActivate method. The implementation of this method uses the context objects that Angular provides that describe the route that is about to be navigated to and checks to see whether the target component is a StoreComponent. If this is the first time that the canActivate method has been called and a different component is about to be used, then the Router.navigateByUrl method is used to navigate to the root URL.
Guarding Routes in the app.module.ts File in the src/app Folder
Completing the Cart Detail Feature
Changing the Template in the cartDetail.component.ts File in the src/app/store Folder
The Contents of the cartDetail.component.html File in the src/app/store Folder
Processing Orders
Being able to receive orders from customers is the most important aspect of an online store. In the sections that follow, I build on the application to add support for receiving the final details from the user and checking them out. To keep the process simple, I am going to avoid dealing with payment and fulfillment platforms, which are generally back-end services that are not specific to Angular applications.
Extending the Model
The Contents of the order.model.ts File in the src/app/model Folder
The Order class will be another service, which means there will be one instance shared throughout the application. When Angular creates the Order object, it will detect the Cart constructor parameter and provide the same Cart object that is used elsewhere in the application.
Updating the Repository and Data Source
Handling Orders in the static.datasource.ts File in the src/app/model Folder
To manage orders, I added a file called order.repository.ts to the src/app/model folder and used it to define the class shown in Listing 8-22. There is only one method in the order repository at the moment, but I will add more functionality in Chapter 9 when I create the administration features.
You don’t have to use different repositories for each model type in the application, but I often do so because a single class responsible for multiple model types can become complex and difficult to maintain.
The Contents of the order.repository.ts File in the src/app/model Folder
Updating the Feature Module
Registering Services in the model.module.ts File in the src/app/model Folder
Collecting the Order Details
Preparing for a Form in the checkout.component.ts File in the src/app/store Folder
The submitOrder method will be invoked when the user submits a form, which is represented by an NgForm object. If the data that the form contains is valid, then the Order object will be passed to the repository’s saveOrder method, and the data in the cart and the order will be reset.
The Contents of the checkout.component.css File in the src/app/store Folder
Angular adds elements to the ng-dirty, ng-valid, and ng-valid classes to indicate their validation status. The full set of validation classes is described in Chapter 14, but the effect of the styles in Listing 8-25 is to add a green border around input elements that are valid and a red border around those that are invalid.
The Contents of the checkout.component.html File in the src/app/store Folder
The form and input elements in this template use Angular features to ensure that the user provides values for each field, and they provide visual feedback if the user clicks the Complete Order button without completing the form. Part of this feedback comes from applying the styles that were defined in Listing 8-25, and part comes from span elements that remain hidden until the user tries to submit an invalid form.
Requiring values is only one of the ways that Angular can validate form fields, and as I explained in Chapter 14, you can easily add your own custom validation as well.
Using the RESTful Web Service
Now that the basic SportsStore functionality is in place, it is time to replace the dummy data source with one that gets its data from the RESTful web service that was created during the project setup in Chapter 7.
The Contents of the rest.datasource.ts File in the src/app/model Folder
Angular provides a built-in service called HttpClient that is used to make HTTP requests. The RestDataSource constructor receives the HttpClient service and uses the global location object provided by the browser to determine the URL that the requests will be sent to, which is port 3500 on the same host that the application has been loaded from.
The methods defined by the RestDataSource class correspond to the ones defined by the static data source but are implemented using the HttpClient service, described in Chapter 24.
When obtaining data via HTTP, it is possible that network congestion or server load will delay the request and leave the user looking at an application that has no data. In Chapter 27, I explain how to configure the routing system to prevent this problem.
Applying the Data Source
Changing the Service Configuration in the model.module.ts File in the src/app/model Folder
This will display the full contents of the database, including the collection of orders. You won’t be able to request the /orders URL because it requires authentication, which I set up in the next chapter.
Remember that the data provided by the RESTful web service is reset when you stop the server and start it again using the npm run json command.
Summary
In this chapter, I continued adding features to the SportsStore application, adding support for a shopping cart into which the user can place products and a checkout process that completes the shopping process. To complete the chapter, I replaced the dummy data source with one that sends HTTP requests to the RESTful web service. In the next chapter, I create administration features that allow the SportsStore data to be managed.