About this book

With the help of Server-Side Enterprise Development with Angular, equip yourself with the skills required to create modern, progressive web applications that load quickly and efficiently. This fast-paced book is a great way to learn how to build an effective UX by using the new features of Angular 7 beta, without wasting efforts in searching for referrals.

To start off, you'll install Angular CLI and set up a working environment, followed by learning to distinguish between the container and presentational components. You'll explore advanced concepts such as making requests to a REST API from an Angular application, creating a web server using Node.js and Express, and adding dynamic metadata. You'll also understand how to implement and configure a service worker using Angular PWA and deploy the server-side rendered app to the cloud.

By the end of this book, you'll have developed skills to serve your users views that load instantly, while reaping all the SEO benefits of improved page indexing.

Publication date:
November 2018
Publisher
Packt
Pages
142
ISBN
9781789806267

 

1. Creating the Base Application

Learning Objectives

By the end of the chapter, you will be able to:

  • Build a modular Angular app using Angular CLI
  • Implement a reusable user interface module based on the Bootstrap framework
  • Implement application logic using a clean separation of concerns
  • Use services to retrieve data from a REST API
  • Use resolvers to make sure that the data is loaded before navigating to the page

This chapter introduces us to Angular CLI and how to use it to create a new application. Here, we will create the various components and implement the logic for our app.

 

Introduction

Server-Side Rendering

When we talk about the server-side rendering of websites, we generally refer to an application or website that uses a programming language that runs on a server. On this server, web pages are created (rendered) and the output of that rendering (the HTML) is sent to the browser, where it can be displayed directly. Examples of this include PHP, Java, Python, .NET, and Ruby.

Strengths

The benefits of server-side rendering is that the generation happens on a server, making it ready to consume in the browser once it's downloaded. It works great with indexing by search engines and with sharing on social media. The content is ready to be consumed, and the client (in this case, the search engine) does not need to run any code to analyze the page.

Weaknesses

The downside of server-side rendering is that the pages often have only basic possibilities of interaction with the user, and when the content changes, or the user navigates to another page, they have to re-download the whole page. This results in more bandwidth and gives the user the feeling that the page is loading slower than it actually is.

Client-Side Rendering

When we talk about client-side rendering, we generally refer to an application or website that uses JavaScript running in the browser to display (render) the pages. There is often a single page that is downloaded, with a JavaScript file that builds up the actual page (hence the term single-page application).

Strengths

The benefit of client-side rendering is that the pages are highly interactive. Parts of the page can be reloaded or updated without having to refresh the whole browser. This uses less bandwidth and it generally gives the user the feeling that the website or application is very fast. The server can be mostly stateless as the page is not rendered there; it just serves the HTML, JavaScript, and stylesheets one time and is done. This takes load off the server, and this results in better scalability.

Weaknesses

Client-side rendered websites are difficult for a search engine to index, as they need to execute the JavaScript to display how the page looks. This is also the case when sharing links to the sites on social media, since these are generally static instead of dynamic.

Another weakness is that the initial download is bigger, and this can be an issue on, for instance, mobile devices with slow connections. Users will see a blank page until the whole application is downloaded, and maybe they will just use a small part of it.

In this chapter, we will create an Angular application that is used throughout this book.

The Angular application we will build is going to be a list of posts that you regularly see on a social networking site such as Twitter. From the list of posts, we can click a link that brings us to the post detail page. We will intentionally keep the application simple as this book is meant to focus on the technology rather than the functionality of the app. Although the app is simple, we will develop it using best practices for Angular development. It should be easy for any Angular developer to extend on the logic and structure that is shown in this application.

 

Installing Angular CLI

Angular CLI is the officially supported tool for creating and developing Angular applications. It's an open source project that is maintained by the Angular team and is the recommended way to develop Angular applications.

Angular CLI offers the following functionalities:

  • Create a new application
  • Run the application in development mode
  • Generate code using the best practices from the Angular team
  • Run unit tests and end-to-end tests.
  • Create a production-ready build
  • Easily install and add third-party software (using ng add, since version 6)

One of the main benefits of using Angular CLI is that you don't need to configure any build tools. It's all abstracted away and available through one handy command: ng.

Throughout this book, we will be using the ng command for creating the app, generating the code, running the application in development mode, and creating builds.

Note

For more information about Angular CLI, refer to the project page on GitHub at https://github.com/angular/angular-cli.

Exercise 1: Installing Angular CLI

In this exercise, we will use npm to globally install Angular CLI. This will give us access to the ng command, which we will use throughout this book:

  1. Open your terminal.
  2. Run the following command:
    npm install -g @angular/[email protected]
  3. Once this command has finished running without any errors, we can make sure that the ng command works as expected by running the following command:
    ng --version

    Verify that the output is similar to the output shown here:

Figure 1.1: Installing Angular CLI
Figure 1.1: Installing Angular CLI

We now have Angular CLI installed and we are ready to get started!

 

Creating a New Application

Now that we have installed and configured Angular CLI, we will start by generating a new application.

Running the ng new command will do the following:

  1. Create a folder called angular-social.
  2. Create a new application inside this folder.
  3. Add a routing module (because of the --routing flag).
  4. Run npm install inside this folder to install the dependencies.
  5. Run git init to initialize a new Git repository.

The following is the folder structure of an Angular CLI app:

  • src: This folder contains the source files for the application.
  • src/app/: This folder contains the application files.
  • src/assets/: This folder contains the static assets we can use in the application (such as images).
  • src/environments/: This folder contains the definition of the default environments of the application.
  • e2e: This folder contains the end-to-end tests for the application.

Exercise 2: Creating a New Application

In this exercise, we will create a new application. Follow these steps to complete this exercise:

  1. Open the terminal and navigate to the workspace directory where you want to work on the application:
    cd dev
  2. Inside the workspace directory, invoke the ng command, as follows:
    ng new angular-social
  3. Answer Y to the question about generating a routing module.
  4. For the stylesheet format, we will select CSS.

    The application will be generated using these options in the angular-social directory, as shown in the following screenshot:

Figure 1.2: Creating a new application
Figure 1.2: Creating a new application

Exercise 3: Starting the Development Server

In this exercise, we will start the development server. Follow these steps to complete this exercise:

  1. Open the terminal and enter the working directory:
    cd angular-social
  2. Use ng serve to start the development server:
    cd angular-social
    ng serve
Figure 1.3: Serving the application
Figure 1.3: Serving the application

Exercise 4: Browsing to the Application

In this exercise, we will navigate to the default page of our application. Follow these steps to complete this exercise:

  1. Open your browser and navigate to http://localhost:4200/.
  2. You should be greeted with a default page that says Welcome to angular-social!:
Figure 1.4: Browsing to the application
Figure 1.4: Browsing to the application
 

Configuring Global Styles

The default generated Angular application does not have any styling. Angular does not dictate anything in terms of style. This means that in your own projects you can use any CSS framework like Bootstrap, Angular Material, Foundation, Semantic UI, or one of the many others.

Alternatively, it's possible to create a custom style from scratch to get a unique look and feel. For this book, though, we will stick to Bootstrap 4 and Font Awesome, as they are widely used and provide a decent style with a minimal amount of code.

Font Awesome is a so-called icon font. You can include it in your page and then use it to show icons by applying some classes to an empty <i class=""></i> tag.

Linking to the Stylesheets in the Global styles.css File

As mentioned in the previous section, the application has a global stylesheet named src/styles.css.

In this stylesheet, we will use the @import command to link to Bootstrap and Font Awesome. This will instruct Angular to download those files and apply the style to the application globally.

Note

For a list of all available icons, you can refer to the Font Awesome icon list at https://fontawesome.com/v4.7.0/icons/. For an overview of all available Bootstrap styles, you can refer to the Bootstrap 4 documentation at https://getbootstrap.com/docs/4.1/getting-started/introduction/. To easily apply a different theme to the app, you can switch out Bootstrap with one of the BootSwatch themes at https://www.bootstrapcdn.com/bootswatch/.

Exercise 5: Installing Bootstrap and Font Awesome

In this exercise, we will add Bootstrap and Font Awesome to the global stylesheet. Follow these steps to complete this exercise:

  1. Navigate to https://www.bootstrapcdn.com/.
  2. From the main page, find the Quick Start block and copy the link that says Complete CSS.
  3. Open the src/styles.css file in the editor.
  4. Add the following line at the end of the file:
    @import url('');
  5. Paste the link you copied in step 2 inside the quotes of the url( ) function.
  6. Navigate to the Font Awesome page on BootstrapCDN.
  7. Copy the link to the CSS file.
  8. Add the following line at the end of the file:
    @import url('');
  9. Paste the link to Font Awesome CSS inside the quotes of the url( ) function:
    Figure 1.5: Import URLs
    Figure 1.5: Import URLs
  10. Refresh the app in the browser:
Figure 1.6: Applying a different font to the application
Figure 1.6: Applying a different font to the application

As you can see, the font of the application got updated to a sans serif font, as that's the Bootstrap default.

Exercise 6: Using Bootstrap CSS and Font Awesome

In this exercise, we will update the template of AppComponent to show that Font Awesome works. Follow these steps to complete this exercise:

  1. Open the src/app.component.html file and replace its content with the following:
    <h1 class="text-center mt-5">
      <i class="fa fa-thumbs-up"></i>
    </h1>
  2. When the app refreshes, you should see the thumbs up icon in the center of the page:
Figure 1.7: The thumbs up icon
Figure 1.7: The thumbs up icon

Activity 1: Using a BootSwatch Theme

We can change the default Bootstrap theme with a different one. The BootSwatch Themes project (https://www.bootstrapcdn.com/bootswatch/) provides a lot of colorful themes that are a drop-in replacement for Bootstrap. This means that all of the Bootstrap CSS selectors will work — they just look different! In this activity, we will use a different theme for our app.

The steps are as follows:

  1. Navigate to BootSwatch Themes (https://www.bootstrapcdn.com/bootswatch/) on BootstrapCDN.
  2. Select one of the themes and copy the link to the CSS.
  3. Update the link to Bootstrap CSS in src/styles.css.
  4. Refresh the app in the browser and verify that the theme has been updated.

    Note

    The solution for this activity can be found on page 108.

Activity 2: Using Different Font Awesome Icons

Font Awesome comes with a large amount of icons that you can use once you've included the file. In this activity, we will use a different icon than the thumbs-up icon we have used already.

The steps are as follows:

  1. Open the src/app/app.component.html file.
  2. Navigate to the Font Awesome icon list at https://fontawesome.com/v4.7.0/icons/.
  3. Replace the value of fa-thumbs-up with another icon. Note that you always need the fa class.
  4. Refresh the app in the browser and verify that the browser now shows your new icon.

    Note

    The solution for this activity can be found on page 109.

 

Creating the UI for the Application

One of the great things about working with Angular is that it promotes building applications in a modular and componentized way. In Angular, NgModule (or simply Module) is a way to group an application into logical blocks of functionality. A Module is a TypeScript class with the @NgModule decorator. In the decorator, we define how Angular compiles and runs the code inside the module.

In this chapter, we are going to build a module that groups together the components we want to use in the application's global user interface. We will create a LayoutComponent that consists of a HeaderComponent and a FooterComponent, and in between those we will define the space where the actual application logic will be displayed:

Figure 1.8: Structure of our module
Figure 1.8: Structure of our module

Creating the UiModule

In this section, we will generate the UiModule using the ng command, import the UiModule in the AppModule, and add Router Outlet to the AppComponent.

Exercise 7: Creating the UiModule

Using the ng generate command, we can generate or scaffold out all sorts of code that can be used in an Angular application. In this exercise, we will use the ng generate module command to generate the UiModule. This command has one required parameter, which is the name. In this case, we use ui. Follow these steps to complete this exercise:

  1. Open the terminal and navigate to the project directory.
  2. Run the ng generate module command from inside the project directory.
    ng generate module ui
    CREATE src/app/ui/ui.module.ts (186 bytes)

    As you can see by the output of the preceding command, the UiModule is generated in the new folder called src/app/ui.

    When we take a look at this file, we can see what an empty Angular module looks like:

    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    @NgModule({
      imports: [
        CommonModule
      ],
      declarations: []
    })
    export class UiModule { }
Figure 1.9: Generating the UI module
Figure 1.9: Generating the UI module

Exercise 8: Importing the UiModule

Now that the UiModule has been created, we need to import it from the AppModule. This way, we can use the code inside the UiModule from other code that lives inside the AppModule. Follow these steps to complete this exercise:

  1. In the editor, open the src/app/app.module.ts file.
  2. Add the import statement at the top of the file:
    import { UiModule } from './ui/ui.module';
  3. Add a reference to UiModule in the imports array inside the NgModule decorator:
    @NgModule({
      ...
      imports: [
        // other imports
        UiModule
      ],
      ...
    })

    The UiModule has now been created and imported in the AppModule, which makes it ready to use:

Figure 1.10: Importing the UI module
Figure 1.10: Importing the UI module

Let's go ahead and create the first component inside the UiModule to make it display in the app!

Exercise 9: Updating the AppComponent Template

When building an Angular app, you generally lean on Angular's router to tie all of the modules and components together. We will build all the application logic in modules and use the AppComponent to display the current route.

For this to work, we need to update the AppComponent template and define the router-outlet component. Follow these steps to complete this exercise:

  1. In the editor, open the src/app/app.component.html file.
  2. Remove all of its content and add the following tag:
    <router-outlet></router-outlet>
Figure 1.11: Updating the template
Figure 1.11: Updating the template

After refreshing the app, we should see a blank page. This is because we don't have any routes set up, and thus there is no way that the Angular app knows what to display.

Let's move to the next topic so that we can create the basic layout.

Creating the Layout Component

In this section, you will use ng generate to create the LayoutComponent inside the UiModule, add the LayoutComponent to the AppRoutingModule so that it gets displayed, and implement the template of the LayoutComponent.

The LayoutComponent is the main template of the application. The function of this component is to glue together the HeaderComponent and the FooterComponent and show the actual application pages in between those two.

Exercise 10: Generating the LayoutComponent

In this exercise, we will use the ng generate command to create the LayoutComponent. Follow these steps to complete this exercise:

  1. Open the terminal in the project directory.
  2. Run the following command from inside the project directory:
    ng generate component ui/components/layout
    CREATE src/app/ui/components/layout/layout.component.css (0 bytes)
    CREATE src/app/ui/components/layout/layout.component.html (25 bytes)
    CREATE src/app/ui/components/layout/layout.component.spec.ts (628 bytes)
    CREATE src/app/ui/components/layout/layout.component.ts (269 bytes)
    UPDATE src/app/ui/ui.module.ts (273 bytes)

    We can see that the component was created in the new src/app/ui/components directory:

Figure 1.12: Generating the layout component
Figure 1.12: Generating the layout component

The last line of the output shows us that the UiModule got updated.

When we open the UiModule in the editor, we can see that it added an import for the LayoutModule and added it to the declarations array in the NgModule decorator.

Using declarations, we declare the existence of components in a module so that Angular knows that they exist and can be used:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LayoutComponent } from './components/layout/layout.component';
@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [LayoutComponent]
})
export class UiModule { }
Figure 1.13: Declaring the component
Figure 1.13: Declaring the component

Exercise 11: Adding the LayoutComponent to the AppRoutingModule

As described in the introduction of this section, we will use the LayoutComponent as the base for the whole application. It will display the header, footer, and a router outlet to show the actual application screens. We will leverage Angular's built-in routing mechanism to do this. We will add a new route to the routing array and reference the LayoutComponent in this route's component.

Follow these steps to complete this exercise:

  1. Open the src/app/app-routing.module.ts file.
  2. Add an import statement to the list of imports at the top of the file:
    import { LayoutComponent } from './ui/components/layout/layout.component';
  3. Inside the empty array that is assigned to the routes property, we will add a new object literal.
  4. Add the path property and set its value to an empty string, ''.
  5. Add the component property and set its value to reference the LayoutComponent that we just imported.

    The line of code that we must add to the routes array is as follows:

    {
      path: '',
      component: LayoutComponent,
      children: [] ,
    }

    For reference, the complete file should look like this:

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    import { LayoutComponent } from './ui/components/layout/layout.component';
    const routes: Routes = [
      {
        path: '',
        component: LayoutComponent,
        children: [],
      }
    ] ;
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
Figure 1.14: Adding the LayoutComponent to the AppRoutingModule
Figure 1.14: Adding the LayoutComponent to the AppRoutingModule

When the application refreshes, we should see the text layout works!:

Figure 1.15: Layout component
Figure 1.15: Layout component

Exercise 12: Implementing the LayoutComponent Template

In this exercise, we'll get rid of this default text and start implementing the template. Follow these steps to complete this exercise:

  1. Open the src/app/ui/layout/layout.component.html file.
  2. Replace the contents of the file with the following code:
    <h1>header placeholder</h1>
    <div class="container my-5">
      <router-outlet></router-outlet>
    </div>
    <h1>footer placeholder</h1>

    When we save the file, we will see that the browser outputs a blank page.

    Looking in the Console tab from Chrome Developer Tools, we can see that we have an error stating Template parse errors: 'router-outlet' is not a known element:.

    Figure 1.16: The router-outlet error
    Figure 1.16: The router-outlet error

    To make the router-outlet available to be used in the LayoutComponent, we need to import the RouterModule in UiModule.

  3. Open src/app/ui/ui.module.ts.
  4. Add an import statement to the list of imports at the top of the file:
    import { RouterModule } from '@angular/router';
  5. Add a reference to the RouterModule inside the imports array in the NgModule decorator.

When we now save the file, we should see the placeholders for the header and footer, with some whitespace in-between and the router error gone from the console:

Figure 1.17: The header and footer placeholders
Figure 1.17: The header and footer placeholders

Now that that's done, let's add some content to the placeholders.

Creating the Header and Footer

In this section, you will download a logo to use in the application header, implement the header with a dynamic title and navigation items, and implement the footer with dynamic text.

Downloading the Angular Logo

We will now download the Angular logo and place it in the assets folder:

  1. Download the https://angular.io/assets/images/logos/angular/angular.svg file.
  2. Save the file as src/assets/logo.svg in the project directory.

Exercise 13: Adding the Header to the LayoutComponent

In this exercise, we will add the header to LayoutComponent. We will define three class properties: a string for the application logo, a title, and an array of objects that represent the navigation items we want to display in the header.

In the template, we will create a Bootstrap navbar consisting of a nav element with some styles, a link with the logo, the title, and the navigation items. Follow these steps to complete this exercise:

  1. In the editor, open the src/app/ui/components/layout/layout.component.ts file.
  2. Inside the component class, we will add some new properties:
    public logo = 'assets/logo.svg';
    public title = 'Angular Social';
    public items = [{ label: 'Posts', url: '/posts'}];
    Figure 1.18: Adding the header
    Figure 1.18: Adding the header
  3. In the editor, open the src/app/ui/components/layout/layout.component.html file.
  4. Replace the contents of the header placeholder with the following markup:
    <nav class="navbar navbar-expand navbar-dark bg-dark">
      <a class="navbar-brand" routerLink="/">
        <img [src]="logo" width="30" height="30" alt="">
        {{title}}
      </a>
      <div class="collapse navbar-collapse">
        <ul class="navbar-nav">
          <li class="nav-item" *ngFor="let item of items" routerLinkActive="active">
            <a class="nav-link" [routerLink]="item.url">
              {{item.label}}
            </a>
          </li>
        </ul>
      </div>
    </nav>
Figure 1.19: The header markup
Figure 1.19: The header markup

When we save this file and check in the browser, we will finally see the first part of the application being displayed:

Figure 1.20: The header
Figure 1.20: The header

Exercise 14: Adding the Footer to the LayoutComponent

In this exercise, we will add the footer to the LayoutComponent.

We will define two class properties, a string property for the name of the developer and the year.

In the template, we will create another Bootstrap navbar consisting of a nav element with some styles and the copyright message that uses both string properties we defined in our component class. Follow these steps to complete this exercise:

  1. In the editor, open the src/app/ui/components/layout/layout.component.ts file.
  2. Inside the component class, we will add the following property. Don't forget to update the two placeholders with the right data:
    public developer = 'YOUR_NAME_PLACEHOLDER';
    public year = 'YEAR_PLACEHOLDER';
    Figure 1.21: Adding the footer
    Figure 1.21: Adding the footer
  3. In the editor, open the src/app/ui/components/layout/layout.component.html file.
  4. Replace the footer placeholder with the following markup:
    <nav class="navbar fixed-bottom navbar-expand navbar-dark bg-dark">
      <div class="navbar-text m-auto">
      {{developer}} <i class="fa fa-copyright"></i> {{year}}
      </div>
    </nav>
Figure 1.22: The footer markup
Figure 1.22: The footer markup

When we save this file and check it in the browser, we will see that both the header and footer are being displayed:

Figure 1.23: The header and footer
Figure 1.23: The header and footer

We are done with the layout. Let's start building the actual application logic.

We can refactor the UiModule by creating separate components for the header and the footer. The following activities should be completed using the knowledge that you have learned in this section.

Activity 3: Moving the Header to a Separate Component

In this activity, you will create a HeaderComponent in src/app/ui/components/. Reference the HeaderComponent in the LayoutComponent so that it says header works!.

After you have done this, you can copy the header markup and class properties from the LayoutComponent to the HeaderComponent.

The steps are as follows:

  1. Create a component called HeaderComponent.
  2. Use the selector to reference the HeaderComponent from the LayoutComponent.
  3. Move the header markup from layout.component.html to header.component.html.
  4. Move the header class properties from layout.component.ts to header.component.ts.

    Note

    The solution for this activity can be found on page 110.

Activity 4: Moving the Footer to a Separate Component

In this activity, we will create a FooterComponent, similar to and based on the instructions from the previous activity.

The steps are as follows:

  1. Create a component called FooterComponent.
  2. Use the selector to reference the FooterComponent from the LayoutComponent.
  3. Move the footer markup from layout.component.html to footer.component.html.
  4. Move the footer class properties from layout.component.ts to footer.component.ts.

    Note

    The solution for this activity can be found on page 111.

 

Creating the App Logic

In this topic, we will build the actual logic of the application.

We will create the PostModule, which contains all of the code related to displaying the posts that come from our API. Inside this module, we will add various components: a service and two resolvers. The components are used to display the data in the browser. We will go over their use in the next section. The service will be used to retrieve the data from the API. Lastly, we will add resolvers to the app that make sure the data from the service is available at the moment we navigate from one route to another.

Types of Components

In this topic, we will take a look at how we can differentiate our components by making a distinction between the container and presentational components. Sometimes, they are also called smart and dumb components when referred to how much knowledge of the world outside of the components each of them has.

The main difference we can make is this:

  • A presentational component is responsible for how things look
  • A container component is responsible for how things work

We will dive into more details of why this distinction is important when we create them, but we can give away a few things already.

Presentational components do the following:

  • Get their data passed in using the @Input() decorator.
  • Any operations are passed up using the @Output() decorator.
  • Handle the markup and the styling of the application.
  • Mostly just contain other presentational components.
  • They have no knowledge (or dependencies) of any routes or services from the app.

Container components do the following:

  • Retrieve their data from a service or a resolver
  • Handle the operations that they receive from the presentational components
  • Have very little markup and styling
  • Will often contain both presentational and container components

The Folder Structure

To make this distinction clear in our project, we will use different folders for both types of components:

  • The src/<module>/components folder is where the presentational components live.
  • The src/<module>/containers folder is where the containers components live.

Creating the Module

In this section, we will create a module called Post. The Post module is responsible for retrieving the posts from an API and showing them in the app.

In this chapter, you will generate the PostModule using the ng command and lazy load the PostModule in the AppRoutingModule.

Tip

You can use shortcuts for most ng commands, for example, ng generate module can be shortened to ng g m.

Exercise 15: Generating the PostModule

We will use the ng generate module command to generate the PostModule.

This command has one required parameter name, and we will call this module post. A second optional parameter, --routing, is passed to create a file to hold the routes for this module, that is, the PostRoutingModule. Follow these steps to complete this exercise:

  1. Open your terminal and navigate to the project directory.
  2. Run the following command from inside the project directory:
    ng g m post --routing
    CREATE src/app/post/post-routing.module.ts (247 bytes)
    CREATE src/app/post/post.module.spec.ts (259 bytes)
    CREATE src/app/post/post.module.ts (271 bytes)

As you can see by the output of the preceding command, the PostModule is generated in the new folder src/app/posts.

Exercise 16: Lazy Loading the PostModule

In contrast to how we load the UiModule by importing it into the AppModule, we will lazy load this module using the AppRoutingModule.

This is an optimization on how the application gets built, and it makes sure that the application has a smaller initial file to download by using a technology called code splitting. This basically bundles each lazy loaded module into its own file, and the browser is instructed to download this file when needed, but not before.

We will add two routes to the main application file. The first route is a route with a blank path property (the default route), and its function is to redirect to the /posts route.

The second route is the /posts route, and it lazy loads the PostModule. If the user navigates to the app, the first route that will be found is the blank redirect route. This will tell the router to navigate to /posts. The router finds the /posts route and navigates the user to that module. Follow these steps to complete this exercise:

  1. In the editor, open the src/app/app-routing.module.ts file.
  2. Locate the existing route object that is defined in the routes property.
  3. Inside the children array, we will create two routes that look like this:
    {
      path: '',
      redirectTo: '/posts',
      pathMatch: 'full',
    },
    {
      path: 'posts',
      loadChildren: './post/post.module#PostModule',
    },

    Make sure that the complete routes property looks like this:

    const routes: Routes = [
    {
      path: '',
      component: LayoutComponent,
      children: [
      {
        path: '',
        redirectTo: '/posts',
        pathMatch: 'full',
      },
      {
        path: 'posts',
        loadChildren: './post/post.module#PostModule',
      },
      ],
    }
    ];

We'll now see how this works:

  • First, we define that we want to have children to the main route. This makes sure that all of the children get rendered in the <router-outlet> that is defined in the LayoutComponent in the previous section.
  • We define the first route to respond to all paths (that's what the empty string does), and we make it redirect to the /posts route.
  • Lastly, we create a posts route and we tell it to load its children from the new module. The loadChildren property is what enables the lazy loading.

When we refresh the page, we can see that nothing changes in the app itself, but we can see that the URL has changed: it has redirected to /posts:

Figure 1.24: The redirected URL
Figure 1.24: The redirected URL

Let's move on to the next topic, where we will create the container components so that we can start seeing data.

Creating the Container Components

In this section, you will use ng generate to create PostListComponent and PostDetailComponent inside the PostModule, add routes to both components, and create TypeScript models for our data objects.

Exercise 17: Generating the PostListComponent

In this exercise, we will be using the ng generate command to create our PostListComponent. This is the component that will eventually list an overview for all our posts. The application route to this component will be /posts. Follow these steps to complete this exercise:

  1. Open your terminal and navigate to the project directory.
  2. Run the following command from inside the project directory:
    ng g c post/containers/post-list
    CREATE src/app/post/containers/post-list/post-list.component.css (0 bytes)
    CREATE src/app/post/containers/post-list/post-list.component.html (28 bytes)
    CREATE src/app/post/containers/post-list/post-list.component.spec.ts (643 bytes)
    CREATE src/app/post/containers/post-list/post-list.component.ts (280 bytes)
    UPDATE src/app/post/post.module.ts (368 bytes)
  3. Open the src/app/post/post-routing.module.ts file.
  4. Import the PostListComponent:
    import { PostListComponent } from './containers/post-list/post-list.component';
  5. Add the following route to the routes array:
    {
      path: '',
      component: PostListComponent,
    },

When we now refresh the page in the app, we should see the text post-list works! between our header and footer:

Figure 1.25: The PostListComponent
Figure 1.25: The PostListComponent

Exercise 18: Generating the PostDetailComponent

Very similar to the previous exercise, we will create the PostDetailComponent. This is the component that will be responsible for displaying an individual post.

The application route to this component will be /posts/<id>, where <id> is the identifier of the post we want it to display. Follow these steps to complete this exercise:

  1. Open your terminal and navigate to the project directory.
  2. Run the following command from inside the project directory:
    ng g c post/containers/post-detail
    CREATE src/app/post/containers/post-detail/post-detail.component.css (0 bytes)
    CREATE src/app/post/containers/post-detail/post-detail.component.html (30 bytes)
    CREATE src/app/post/containers/post-detail/post-detail.component.spec.ts (657 bytes)
    CREATE src/app/post/containers/post-detail/post-detail.component.ts (288 bytes)
    UPDATE src/app/post/post.module.ts (475 bytes)
  3. Open the src/app/post/posts-routing.module.ts file.
  4. Import the PostDetailComponent:
    import { PostDetailComponent } from './containers/post-detail/post-detail.component';
  5. Add the following route to the routes array:
    {
      path: ':id',
      component: PostDetailComponent,
    },

When the application refreshes and we navigate to http://localhost:4200/posts/42, we should see the text post-detail works!:

Figure 1.26: The PostDetailComponent
Figure 1.26: The PostDetailComponent

Exercise 19: Defining our Data Model Types

To get the most out of working with TypeScript, we will create some custom types to describe the data we get back from the API. We can use these types throughout the app, and they will help us during development by providing type information. This will, for example, prevent us from trying to access properties that do not exist, and can help with auto completion in the editor.

In this application, we will use a post and profile. Follow these steps to complete this exercise:

  1. Open your terminal and navigate to the project directory.
  2. Run the following command from inside the project directory:
    ng g class post/model/post
    CREATE src/app/post/model/post.ts
  3. Open the src/app/post/model/post.ts file and add the following content:
    export class Post {
      id: string;
      profileId: string;
      profile: Profile;
      type: 'text' | 'image';
      text: string;
      date: Date;
    }
  4. Run the following command from inside the project directory:
    ng g class post/model/profile
    CREATE src/app/post/model/profile.ts
  5. Open the src/app/post/model/profile.ts file and add the following content:
    export class Profile {
      id: string;
      avatar: string;
      fullName: string;
      posts?: Post[];
    }

    We have now defined the two models. The last thing we need to do is import the Profile model from our Post model, and vice versa.

  6. Add import { Post } from './post'; to profile.ts.
  7. Add import { Profile } from './profile'; to post.ts:
Figure 1.27: Importing our model types
Figure 1.27: Importing our model types

Creating a Service for API Interaction

While you could make a request to an API from a component, the best practice in Angular is to use services for retrieving and sending data from and to the API. The benefits of doing this are that services can be reused throughout multiple components; this keeps the component to its responsibility of displaying an interface.

An additional benefit is that it makes your code easier to test when writing unit tests. You can mock the behavior of a service to make sure that the unit tests are not dependent on the API being online at the moment the tests are run.

Once we have created the service, we can inject it into a component and use it.

Exercise 20: Using the Environment to Store the API Endpoints

In this exercise, we will use the environment of Angular CLI to store the API URL. Using the environment, we can define a different URL for development and production environments.

By default, the application generated with Angular CLI comes with two pre-defined environments. These environments are defined in angular-cli.json in the default project.

Follow these steps to complete this exercise:

  1. Open the src/environments/environment.ts file.
  2. Inside the environment variable, add a key called apiUrl and assign the value to the 'http://localhost:3000/api' string, which is the URL to the development API.
  3. Open the src/environments/environment.prod.ts file.
  4. Inside the environment variable, add a key called apiUrl and assign the value to the 'https://packt-angular-social-api.now.sh/api' string, which is the URL to the production API.

Exercise 21: Generating and Implementing the PostService

In this exercise, we will use the ng generate service command to generate a service that will handle the interaction with the API. Follow these steps to complete this exercise:

  1. Open your terminal and navigate to the project directory.
  2. Run the following command from inside the project directory:
    ng g s post/services/post
    CREATE src/app/post/services/post.service.spec.ts (362 bytes)
    CREATE src/app/post/services/post.service.ts (133 bytes)

    The next step is to define two public methods in the PostService and make sure that these retrieve the data we need from the API. We will add two methods in the PostService.

    The first method is the getPosts method, which does not take any arguments and returns an Observable of all the posts from the API. The second method is the getPost method, which takes the ID of type string as an argument. It returns an Observable of the post with the ID that is passed in as an argument, and includes all the posts that are made by that profile.

  3. Open the src/app/post/services/post.service.ts file.
  4. Add an import statement to import the HttpClient from @angular/common/http, a reference to the environment where we have the API URL defined, and the Post model:
    import { HttpClient } from '@angular/common/http';
    import { environment } from '../../../environments/environment';
    import { Post } from '../model/post';
  5. Define the baseUrl and defaultParams constants:
    const baseUrl = `${environment.apiUrl}/posts/`;
    const defaultParams = 'filter[include]=profile';
  6. Update the constructor to inject the HttpClient and make it available under the private http variable:
    constructor(private http: HttpClient) { }
  7. Create a new method called getPosts() {} and add the following to it:
    public getPosts(): Observable<Post[]> {
      const params = `?${defaultParams}&filter[where][type]=text&filter[limit]=20`;
      return this.http.get<Post[]>(baseUrl + params);
    }
  8. Create a new method called getPost(id) and add the following to it:
    public getPost(id: string): Observable<Post> {
      const params = `?${defaultParams}`;
      return this.http.get<Post>(baseUrl + id + params);
    }

Exercise 22: Using the PostService in the Container Components

In this exercise, we will reference the PostService in both the container components to fetch the data.

We will use the OnInit component lifecycle hook provided by Angular to call into the injected service and invoke the methods from that service. Note that we do the same thing for both the PostListComponent and the PostDetailComponent. Follow these steps to complete this exercise:

  1. Open the src/app/post/containers/post-list/post-list.component.ts file.
  2. Add an import statement for the new PostService:
    import { PostService } from '../../services/post.service';
    import { Post } from '../../model/post';
  3. Add a public class property called posts of type Post[]:
    public posts: Post[];
  4. Update the constructor to inject the PostService and make it available under the private service variable:
    constructor(private service: PostService) {}
  5. Add the following to the ngOnInit method:
    ngOnInit() {
      this.service.getPosts()
        .subscribe(
          res => this.posts = res,
          err => console.log('error', err),
        )
    }
  6. Open the post-list.component.html file and update the content to the following:
    <pre> {{posts | json}} </pre>

    Let's do the same for the PostDetailComponent now.

  7. Open the src/app/post/containers/post-detail/post-detail.component.ts file.
  8. Add an import statement for the new PostService:
    import { ActivatedRoute } from '@angular/router';
    import { Observable } from 'rxjs';
    import { PostService } from '../../services/post.service';
    import { Post } from '../../model/post';
  9. Add a public class property post of type Post:
    public post: Post;
  10. Update the constructor to inject the PostService and private route: ActivatedRoute dependency:
    constructor(private route: ActivatedRoute, private service: PostService) {}
  11. Set the contents of the ngOnInit method to the following:
    this.service.getPost(this.route.snapshot.paramMap.get('id'))
      .subscribe(
        res => this.post = res,
        err => console.log('error' ,err)
      )
  12. Open the post-detail.component.html file and update the content to the following:
    <pre> {{post | json}} </pre>

Exercise 23: Importing the HttpClientModule to Enable the HttpClient

We are almost done creating the PostService, but there is still one thing we need to fix. When we refresh the application, we can see that we have an error message in the console:

ERROR Error: StaticInjectorError[HttpClient] :
StaticInjectorError[HttpClient] :
NullInjectorError: No provider for HttpClient!
Figure 1.28: The import error
Figure 1.28: The import error

The reason that we get this error is because we have used the HttpClient in the service, but Angular does not know where this module comes from. To fix this, we need to import HttpClientModule in the AppModule. Follow these steps to complete this exercise:

  1. Open the src/app/app.module.ts file.
  2. Add an import statement to import the HttpClientModule from @angular/common/http:
    import { HttpClientModule } from '@angular/common/http';
  3. Update the imports array in the NgModule decorator to import HttpClientModule:
    @NgModule({
    ...
      imports: [
        ...
        HttpClientModule,
        ...
      ],
    ...
    })

When we refresh the application, we should see a list of posts retrieved from the API:

Figure 1.29: List of posts retrieved from the API
Figure 1.29: List of posts retrieved from the API

Let's continue and add some presentational components to give the posts some style.

Creating the Presentational Components

In this section, you will use ng generate component to create the PostItemComponent and PostProfileComponent inside the PostModule, implement the logic for these components, and use these components in our container components.

The PostItemComponent accepts a single post as its input and displays that post. For displaying the profile that belongs to the post, we use the PostProfileComponent. It takes the profile as input and uses the ng-content component to project the content on top.

Exercise 24: Generating the PostItemComponent

In this exercise, we will use the ng generate command to create our PostItemComponent. Follow these steps to complete this exercise:

  1. Open your terminal in the project directory and run the following command:
    ng g c post/components/post-item
    CREATE src/app/post/components/post-item/post-item.component.css (0 bytes)
    CREATE src/app/post/components/post-item/post-item.component.html (28 bytes)
    CREATE src/app/post/components/post-item/post-item.component.spec.ts (643 bytes)
    CREATE src/app/post/components/post-item/post-item.component.ts (280 bytes)
    UPDATE src/app/post/post.module.ts (574 bytes)
  2. Open the src/app/post/components/post-item/post-item.component.ts file.
  3. Import Input from @angular/core by adding it to the existing import statement and add the following:
    import { Post } from '../../model/post';
  4. Add the following property in the component class:
    @Input() post: Post;
  5. Update the template to the following:
    <div class="card">
      <div class="card-body" *ngIf="post">
        <app-post-profile [profile] ="post.profile">
          <div class="my-2">
            <a [routerLink] ="['/posts', post.id]" class="text-muted">
              {{ post.date | date: 'medium'}}
            </a>
          </div>
          <p>{{post.text}}</p>
        </app-post-profile>
      </div>
    </div>

Exercise 25: Generating the PostProfileComponent

In this exercise, we will use the ng generate command to create our PostProfileComponent.

This component will display the avatar and full name of the profile that created the post, and it will use the ng-content component to show the markup that exists inside the <app-post-profile> tags from the previous exercise. Follow these steps to complete this exercise:

  1. Open the terminal and run the following command from inside the project directory:
    ng g c post/components/post-profile
    CREATE src/app/post/components/post-profile/post-profile.component.css (0 bytes)
    CREATE src/app/post/components/post-profile/post-profile.component.html (31 bytes)
    CREATE src/app/post/components/post-profile/post-profile.component.spec.ts (664 bytes)
    CREATE src/app/post/components/post-profile/post-profile.component.ts (292 bytes)
    UPDATE src/app/post/post.module.ts (685 bytes)
  2. Open the src/app/post/components/post-profile/post-profile.component.ts file.
  3. Import Input from @angular/core by adding it to the existing import statement and add the following:
    import { Profile } from '../../model/profile';
  4. Add the following property to the component class:
    @Input() profile: Profile;
  5. Update the template to the following:
    <div class="media" *ngIf="profile">
      <img class="avatar mr-3 rounded" [attr.src]="profile.avatar" [attr.alt]="profile.fullName">
      <div class="media-body">
        <h5>
          {{profile.fullName}}
        </h5>
        <ng-content></ng-content>
      </div>
    </div>
  6. Open src/app/post/components/post-profile/post-profile.component.css and add the following styles:
    img.avatar {
      height: 80px;
      width: 80px;
    }

Exercise 26: Using the PostItemComponent

In this exercise, we will use the PostItemComponent. Follow these steps to complete this exercise:

  1. Open the src/app/post/containers/post-list/post-list.component.html file.
  2. Update the template to the following:
    <div class="row">
      <div class="col-md-8 offset-md-2 mb-3" *ngFor="let post of posts">
        <app-post-item [post]="post"></app-post-item>
      </div>
    </div>
  3. Open the src/app/post/containers/post-detail/post-detail.component.html file.
  4. Update the template to the following:
    <app-post-item [post]="post"></app-post-item>

When we now refresh the application in our browser, we can see that the content is styled and that the navigation still works as expected:

Figure 1.30: List of styled posts
Figure 1.30: List of styled posts

We have successfully separated the concerns of retrieving the data and displaying it.

Resolving Data Using the Router

In this section, you will manually create two injectable classes that act as resolvers, configure the router to use these resolvers, and update the container components to use this resolved data.

A resolver is a class that we can use to fetch the data that we use in the component before the component is displayed. We call the resolvers in the routes where we need the data. In the implementation, the resolvers retrieve the data from the API and return it so that it can be displayed in the components.

Note

More information about resolvers can be found at https://angular.io/guide/router#resolve-pre-fetching-component-data.

Our application is quite neatly structured already, but there is one thing that we can optimize.

To see what the problem is, open Chrome Developer Tools and open the Performance tab. Hit the Cog icon and set Network to Slow 3G. If we now click around in the application, we will see that that the page navigation still works, but we are presented with empty pages.

The reason for this is that while the components are loaded correctly, they still need to retrieve the data after they are loaded. This is because the components call into the PostService from the ngOnInit method.

It would be better if the router could make sure that the component has all the necessary data loaded before entering the page. Fortunately, the Angular router provides a way to handle this by using resolvers. They will resolve the data before entering the route, and in the component, we can just take this resolved data and display it.

The resolvers that we create need the @Injectable() decorator to make sure that they are part of the dependency injection in Angular.

Exercise 27: Creating a Resolver for the getPosts Method

In this exercise, we will create a resolver that invokes the getPosts() method defined in the PostService. Follow these steps to complete this exercise:

  1. Open a terminal and run the following command:
    ng g class post/resolvers/posts-resolver
  2. Open the src/app/post/resolvers/posts-resolver.ts file.
  3. Start the file by defining the necessary imports:
    import { Injectable } from '@angular/core';
    import { Resolve } from '@angular/router';
    import { Post } from '../model/post';
    import { PostService } from '../services/post.service';
  4. Decorate the PostsResolver class with the @Injectable operator:
    @Injectable({ providedIn: 'root' })
    export class PostsResolver {}
  5. Make the class implement Resolve<Post[]>:
    @Injectable({ providedIn: 'root' })
    export class PostsResolver implements Resolve<Post[]> {
    }
  6. Inside the class, create a constructor and inject the PostService:
    constructor(private service: PostService) {}
  7. Below the constructor, create a class method called resolve and make it return the getPosts() method from the PostService:
    resolve() {
      return this.service.getPosts();
    }

This is the resolver that will be used to retrieve all the posts, just like how we do this currently in the PostListComponent.

Exercise 28: Creating a Resolver for the getPost Method

In this exercise, we will create a resolver that invokes the getPost() method defined in the PostService. We will pass in the ID that we get from the ActivatedRouteSnapshot. Follow these steps to complete this exercise:

  1. Open a terminal and run the following command:
    ng g class post/resolvers/post-resolver
  2. Open the src/app/post/resolvers/post-resolver.ts file.
  3. Start the file by defining the necessary imports:
    import { Injectable } from '@angular/core';
    import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
    import { Post } from '../model/post';
    import { PostService } from '../services/post.service';
  4. Decorate the PostResolver class with the @Injectable operator and pass in an object with the providedIn key set to root:
    @Injectable({ providedIn: 'root' })
    export class PostResolver {}
  5. Make the class implement Resolve<Post>:
    @Injectable({ providedIn: 'root' })
    export class PostResolver implements Resolve<Post> {
    }
  6. Inside the class, create a constructor and inject the PostService:
    constructor(private service: PostService) {}
  7. Below the constructor, create a class method called resolve, and pass the route: ActivatedRouteSnapshot class into it:
    resolve(route: ActivatedRouteSnapshot) {
    }
  8. Inside the resolve method, we return the getPost() method from the PostService while getting the id parameter from the ActivatedRouteSnapshot:
    resolve(route: ActivatedRouteSnapshot) {
      return this.service.getPost(route.paramMap.get('id'));
    }

This is the resolver that will be used to retrieve the posts that we have navigated to in the route.

Exercise 29: Adding the Resolvers to the PostRoutingModule

In this exercise, we will add the two new resolvers to the PostsRoutingModule. We will do this by importing the resolvers and then adding a resolve property to both of the routes. The resolve property takes an object where the key is how the data will be available in the router after it is resolved, and the value is a reference to the imported resolver. Follow these steps to complete this exercise:

  1. Open the src/app/post/post-routing.module.ts file.
  2. Import the two freshly created resolvers:
    import { PostsResolver } from './resolvers/posts-resolver';
    import { PostResolver } from './resolvers/post-resolver';
  3. Update both the routes to add a resolve property and call into the resolvers:
    const routes: Routes = [
      {
        path: '',
        component: PostListComponent,
        resolve: {
          posts: PostsResolver,
        }
      },
      {
        path: ':id',
        component: PostDetailComponent,
        resolve: {
          post: PostResolver,
        }
      },
    ];

If we check the Network tab in Chrome Developer Tools, we can see that we make two requests to the same endpoint. This is because we retrieve the data twice: once in the resolver and once in the component. Let's update the container components and let them use the data resolved by the router.

Exercise 30: Using Resolved Data in the PostListComponent

In this exercise, we will update the PostListComponent to read the data that has been resolved by the router. We will subscribe to the data of the active route and we will map over that data twice. In the first map command, the posts value relates to the object key we used in the resolver object for this route. Follow these steps to complete this exercise:

  1. Open the src/app/post/containers/post-list/post-list.component.ts file.
  2. Import ActivatedRoute from @angular/router:
    import { ActivatedRoute } from '@angular/router';
    import { map } from 'rxjs/operators';
  3. Remove the PostService import as we are no longer going to use it here.
  4. Update the constructor to inject only private route: ActivatedRoute:
    constructor(private route: ActivatedRoute) { }
  5. Update the ngOnInit() method and replace the content with the following code:
    ngOnInit() {
      this.route.data
        .pipe(
          map(data => data['posts']),
        )
        .subscribe(
          res => this.posts = res,
          err => console.log('error', err),
        );
    }

Refresh the page and make sure that the data is still loaded.

Exercise 31: Using Resolved Data in the PostDetailComponent

In this exercise, we will update the PostDetailComponent to read the data that has been resolved by the router. We subscribe to the data of the active route and we will map over that data. In the map command, the profile value relates to the object key we used in the resolver object for this route. Follow these steps to complete this exercise:

  1. Open the src/app/post/containers/post-detail/post-detail.component.ts file.
  2. Add the following import statement:
    import {map} from 'rxjs/operators';
  3. Remove the PostService import as we are no longer going to use it here.
  4. Update the constructor to only inject private route: ActivatedRoute:
    constructor(private route: ActivatedRoute) { }
  5. Update the ngOnInit() method and replace the content as follows:
    ngOnInit() {
      this.route.data
        .pipe(
          map(data => data['post'])
        )
        .subscribe(
          res => this.post = res,
          err => console.log('error', err),
        );
    }

Refresh the page and make sure that the data is still loaded.

In the following activities, we will introduce the ProfileModule, which is responsible for listing the profiles. The following activities should be performed using the knowledge that you have learned in this chapter.

Activity 5: Creating a ProfileModule

In this activity, you will create a ProfileModule in src/app/profile. Add a menu item in the LayoutModule to link to the new ProfileModule.

The steps are as follows:

  1. Create a module called ProfileModule.
  2. Define the /profiles/ route to lazy load this new module in app-routing.module.ts.
  3. Add a menu item to link to the /profiles/ URL in the header.component.ts file created in Activity 3.

    Note

    The solution for this activity can be found on page 113.

Activity 6: Creating Container Components

In this activity, we will create the container components ProfileListComponent and ProfileDetailsComponents. The routes are similar to those in the PostModule.

The steps are as follows:

  1. Add the ProfileListComponent and ProfileDetailComponent containers.
  2. Add routes to the container components in ProfileRoutingModule.

    Note

    The solution for this activity can be found on page 114.

Activity 7: Creating Service and Resolvers

In this activity, we will create the service and resolvers called ProfileService, ProfilesResolver, and ProfileResolver. The functionality of those services and resolvers is identical to those in the PostModule.

The steps are as follows:

  1. Add a ProfileService to retrieve the data.
  2. Add a ProfilesResolver and ProfileResolver and use them in the ProfileRoutingModule.

    Note

    The solution for this activity can be found on page 115.

Activity 8: Creating Presentational Components

In this activity, we will create the presentational component to display the profile data.

The steps are as follows:

  1. Use the resolved data in the container components.
  2. Create the presentational components to display the profile data from the API.

    Note

    The solution for this activity can be found on page 117.

 

Summary

We started this chapter by installing Angular CLI and creating a new application. We configured styles using Bootstrap and Font Awesome. We then created the UI for our application, and we created the layout module and then the header and footer components. We finished this chapter by creating the application logic, including creating the container and presentational components, a service for API interaction, and routers.

Our basic application has been built, and even though there are enough things to add and optimize, it's well-structured and works using Angular's best practices. In the next chapter, we will add support for Server-Side Rendering by adding Angular Universal.

About the Author

  • Bram Borggreve

    Bram Borggreve is a software engineer from the Netherlands, who currently works as an instructor at egghead.io, and is the founder of Colmena Consultancy. With almost 20 years of experience in all fields of the software lifecycle, Bram has a complete overview of the high-value challenges that clients are keen to resolve.

    Browse publications by this author

Latest Reviews

(1 reviews total)
Хорошая книга!рекомендую!

Recommended For You

Book Title
Unlock this full book FREE 10 day trial
Start Free Trial