Service workers are used to cache data responses. So far, you have seen how to create a new Angular application, configure the application to be installable, and cache the application so that it is accessible even when offline. This chapter introduces how to cache a data response from an HTTP service.
This chapter begins by creating a new component to retrieve and show data from an HTTP service. Next, it discusses how to create an interface that acts as a contract between the service and the Angular application. Next, you will learn how to create a Node.js Express mock service that provides data to the Angular application. It runs in a separate process outside the Angular application. The chapter details how to create an Angular service, which uses an out-of-the-box HttpClient service to invoke the HTTP service.
Now that you have integrated with an HTTP service and accessed the data, the chapter details how to configure Web Arcade to cache data responses. It elaborates on the configuration and showcases a cached data response with a simulated offline browser.
Adding a Component to List Board Games
Use the Board Component
Define a Data Structure for Board Games
Next, define a data structure for the board games page. You create a TypeScript interface for defining the data structure. It defines a shape for the board games data objects. TypeScript uses an interface to define a contract, which is useful within the Angular application and with the external, remote service that serves the board games data.
The TypeScript interface enforces the required list of fields for board games. You will notice an error if a needed field is missing because of a problem in the remote service or a bug in the Angular application. An interface acts as a contract between the Angular application and the external HTTP service.
Interfaces for Board Games
BoardGamesEntity represents a single board game. Considering Web Arcade will have multiple games, GamesEntity includes an array of board games. Later, GamesEntity can extend to other categories of games in the Web Arcade system.
Mock Data Service
A typical service retrieves and updates data from/to a database or a back-end system, which is out of scope for this book. However, to integrate with a RESTful data service, this section details how to develop mock responses and data objects. The mock service returns board games data in JavaScript Object Notation (JSON) format. It can be readily integrated with the Angular component created in the earlier section “Adding a Component to List Board Games.”
You will use Node.js’s Express server to develop the mock service. Follow these instructions to create a new service.
Notice the --save-dev option with the npm command and the --dev option with the yarn command. It installs the package in dev-dependencies in package.json, qualifying it as a developer tool. It will not be included in the production builds, which helps reduce the footprint. See Listing 5-3, line 15.
Package.json dev-dependencies
The npx command first checks the local node_modules for the package. If it is not found, the command downloads the package to the local cache and run the command.
The previous command runs, even without the dev-dependency installation in the previous step (npm install --save-dev express-generator). If you do not intend to run this command often, you may skip the dev-dependency installation.
Next, run npm install (or yarn install) in the mock-services directory.
Board Games Mock Data
New API Endpoint That Returns Mock Board Games Data
Line 3 imports and sets the board games mock data on a variable.
Lines 6 to 9 create the endpoint that returns the board games data.
Notice the get() function in line 6. The endpoint responds to an HTTP GET call, which is typically used to retrieve data (as opposed to create, update, or delete).
Line 7 sets the response content type to application/json ensuring the client browsers interpret the response format accurately.
Line 8 responds to the client with the board games data.
Line 11 exports a router instance that encapsulates the service endpoint.
Integrate the New Board Games Endpoint
Line 9 imports the board games route instance exported in the earlier Listing 5-5.
Line 25 adds the route /api/board-games to the application. The new service is invoked when the client invokes this endpoint.
Notice that you are running the service application on a separate port, which is 3000. Remember, in the earlier examples, the Angular application runs on ports 8080 (with Http-Server) and 4200 (with the ng serve command that uses Webpack internally). The Angular application running on one of these ports is expected to connect to the service instance running on port 3000.
Call the Service in the Angular Application
This section details how to update the Angular application to consume data from the Node.js service. In a typical application, Node.js services are server-side, remote services that access data from a database or another service.
Configure the Service in an Angular Application
environment.ts: This is for the debug build configuration used by the developer on localhost. Typically, the ng serve command uses it.
environment.prod.ts: This is for production deployments. Running ng build (or yarn build or npm run build) uses this configuration file.
Integrate the New Board Games Endpoint
Line 2 adds a relative path to the service endpoint. You will import and use the configuration field boardGameServiceUrl while making a call to the service.
Line 3 set production to false. Remember, the file environment.ts is used with the ng serve command, which runs a debug build with the help of Webpack. It is set to true in the alternate environment file environment.prod.ts.
Create an Angular Service
A service can be used for sharing data among components. Imagine a screen with a list of users. Say the list is shown by a UserList component. Users can select a user. The application navigates to another screen, which loads another component, say UserDetails. The user details component shows additional information about the user in your system. The user details component needs data about the selected user so that it can retrieve and show the additional information.
You may use a service to share the selected user information. The first component updates the selected user details to a common service. The second component retrieves the data from the same service.
A service is an easy and a simple way to share data among components. However, for a large application, it is advisable to adapt the Redux pattern. It helps maintain application state, ensures unidirectional data flows, provides selectors for easy access to the state in the Redux store, and has many more features. For Angular, NgRx is a popular library that implements the Redux pattern and its concepts.
A service can be used to aggregate and transform JSON data. The Angular application might obtain data from various data sources. Create a service with a reusable function to aggregate and return the data. This enables a component to readily use the JSON objects for the presentation.
A service is used to retrieve data from a remote HTTP service. In this chapter, you have already built a service to share the board games data with an Angular application. The Node.js Express server running in a separate process (ideally on a remote server) shares this data over an HTTP GET call.
common/games.services.ts: A TypeScript file for adding Angular service code that makes HTTP calls for games data
common/games.services.spec.ts: A unit test file for the functions in games.service.ts
Angular Service Skeleton Code
Provide a Service
Provide at the module level: The service instance is available and shared within the module. Later sections give more details about Angular modules.
Provide at the component level: The service instance is created and available for the component and all its child components.
Once a service is provided, it needs to be injected. A service can be injected into a component or another service. In the current example, the board games component needs data so that the games are listed for users to view. Notice in the earlier Listing 5-8 that the code creates a new function called getBoardGames() intended to retrieve the list from the remote HTTP service.
Inject Games Service into a Component
The ngOnInit() function on line 7 is an Angular lifecycle hook. It is invoked after the framework completes initializing the component and its properties. This function is a good place in a component for additional initializations, including service calls.
Line 8 in Listing 5-9 calls the service function that retrieves board games data. This data is required as part of component initialization as the primary functionality of the component is to show a list of games.
HttpClient Service
Next, invoke the remote HTTP service . Angular provides the HttpClient service as part of the package @angular/common/http. It provides an API to invoke various HTTP methods including GET, POST, PUT, and DELETE.
Import HttpClientModule
Remember from Listing 5-5 (line 6) that the service returns data to the Angular application with a GET call. Hence, we will use the get() function on the HttpClient instance to invoke the service. Remember, we already created the function getBoardGames() as part of GamesService (see Listing 5-8, line 8).
GamesService Injects and Uses HttpClient
Line 13 injects HttpClient into GamesService. Notice that the name of the field (an instance of HttpClient) is client. It is a private field and hence accessible only within the service class.
The statement in lines 16 to 18 invokes the client.get() API. As a client is a field of the class, it is accessed using the this keyword.
The get() function accepts one parameter, the URL for the service. Notice the import statement for the environment object on line 3. It imports the object exported from the environment configuration file. See Listing 5-7. It is one of the environment configuration files. Use the boardGameServiceUrl field from the configuration (Listing 5-11, line 18). You may have more than one URL configured in an environment file.
Notice that the get() function is expected to retrieve GamesEntity. It was created in Listing 5-2.
The getBoardGames() function returns an Observable<GamesEntity>. Observable is useful with asynchronous function calls. A remote service might take some time, such as a few milliseconds or sometimes a few seconds, to return the data. Hence, the service function returns an observable. The subscriber provides a function callback. The observable executes the function callback once data is available.
Notice that line 16 returns the output of the get() function call. It returns an Observable of the type specified. You specified the type GamesEntity on line 18. Hence, it returns an Observable of type GamesEntity. It matches with the return type of getBoardGames() on line 15.
Board Games Component Template Shows List of Games
The template renders the list as an HTML table.
Notice, in line 7, that the *ngFor directive iterates through boardGames. See Listing 5-2. Notice that boardGames is an array on the interface GamesEntity.
The template shows fields on each game in the entity. See lines 11, 13, 15, and 18. They show the fields title, alternateNames, origin, and description.
Remember, the class field games is set with the values returned from the service. This field is used in the template. See line 7.
Notice the pipe with async (| async) on line 7. It is applied on Observable. Remember, the service returns an Observable. As mentioned earlier, an Observable is useful with asynchronous function calls. A remote service might take time, a few milliseconds or sometimes a few seconds, to return the data. The template uses the field boardGames on games Observable, when the data is available, in other words, when it is obtained from the service.
Cache the Board Games Data
So far, we have created an HTTP service to provide board games data, created an Angular service to use the HTTP service to obtain the data, and added a new component to show the list. Now, configure the service worker to cache the board games data (and even other HTTP service responses).
Data Groups Configuration for a Service Worker in an Angular Application
Line 4 configures the service URLs to cache data. It is an array, and we can configure multiple URLs here.
The URLs support matching patterns. For example, you may use api/* to configure all the URLs.
As part of the cache configuration (cacheConfig), see line 10. Set strategy to performance. This instructs the service worker to use cached responses first for better performance. Alternatively, you may use freshness, which goes to the network first and uses the cache only when the application is offline.
Notice that maxAge is set to 36 hours, after which the service worker clears the cached responses (of board games). Caching data for too long could cause the application to use obsolete fields and records. The service worker configuration provides a mechanism to automatically clear the data at periodic intervals, ensuring the application does not use stale data.
The timeout is set to 10 seconds. This is dependent on strategy. Assuming strategy is set to freshness, after 10 seconds, the service worker uses cached responses.
maxSize is set to 100 records. It is a good practice to limit the size by design. Browsers (like any other platform) manage and allocate memory for each application. If the application exceeds the upper limit, the entire dataset and the cache could be evicted.
Listing 5-13 has a single data groups configuration object. As we further develop the application, the additional services might have slightly different cache requirements. For example, the list of gamers might need to be the latest ones. If your friend joins the arcade, you prefer to see her listed instead of showing the old list. Hence, you might change the strategy to freshness. Add this URL configuration as another object in the dataGroups array. On the other hand, for a service that fits the current configuration, add the URL to the urls field on line 4.
Angular Modules
All Angular applications use at least one root module. Typically, the module is named AppModule and defined in src/app/app.module.ts. A module may export one or more functionalities. The other modules in the application can import the exported components and services.
Angular modules are separate from JavaScript (ES6) modules. They complement each other. An Angular application uses both JavaScript modules and Angular modules.
Summary
This chapter provided instructions for creating a new component for listing board games. With this code sample, it demonstrated how service workers cache data responses from an HTTP service. It provided instructions to create a board games component with Angular CLI. You also updated the application to use this new component instead of dice.
It also defined data contracts between the Angular application and the external HTTP service, detailed how to create a Node.js Express service for providing data to the Angular application, and introduced Angular services.
Create a new route in the Node.js Express application for exposing a list of jigsaw puzzles.
Create an Angular service to use the new jigsaw puzzles service endpoint and retrieve data.
Ensure the latest jigsaw puzzles data is available to the user. Cache only when the user is offline or lost connectivity.
For the new service, configure to use data from the cache if the service does not respond after one minute.