Chapter 7: Building an SSR Application for a GitHub Portfolio Using Angular

A typical Angular application follows the Single-Page Application (SPA) approach, where each page is created in the DOM of the browser while the user interacts with the application. A web server hosts the application and is responsible for serving only the main page, usually called index.html, at application startup.

Server-Side Rendering (SSR) is a technique that follows an entirely different approach for application rendering than SPA. It uses the server to prerender pages while they are requested at runtime from the user. Rendering content on the server dramatically enhances the performance of a web application and improves its Search Engine Optimization (SEO) capabilities. To perform SSR in an Angular application, we use a library called Angular Universal.

In this chapter, we will learn how to benefit from Angular Universal by building a portfolio application using the GitHub API. We will cover the following topics:

  • Building an Angular application with the GitHub API
  • Integrating Angular Universal
  • Prerendering content during build
  • Enhancing SEO capabilities
  • Replaying events with preboot

Essential background theory and context

An Angular application consists of several pages that are created dynamically in the DOM of the browser by the Angular framework while we use the application. Angular Universal enables the Angular framework to create these pages on the server statically during application runtime. In other words, it can create a fully static version of an Angular application that can run even without needing to have JavaScript enabled. Prerendering an application on the server has the following advantages:

  • It allows web crawlers to index the application and make it discoverable and linkable on social media websites.
  • It makes the application usable to mobile and other low-performant devices that cannot afford to execute JavaScript on their side.
  • It improves the user experience by loading the first page quickly and, at the same time, loading the actual client page in the background (First Contentful Paint (FCP)).

    Important note

    The application does not respond to user events during the FCP other than navigation events initiated from the routerLink directive of the Angular router. However, we can use a special-purpose library called preboot to replay those events after the entire application has been loaded.

The GitHub API is an HTTP REST API for interacting with GitHub data. It can be used either publicly or in private using an authentication mechanism provided out of the box.

Important note

Unauthorized requests to the GitHub API are limited to 60 requests per hour. For an overview of available authentication methods, you can find more details at https://docs.github.com/en/rest/overview/other-authentication-methods.

To communicate over HTTP in Angular, we use the built-in HTTP client that is available in the @angular/common/http npm package. Interacting with HTTP in SSR applications may result in duplicated HTTP requests due to the page prerendering at the FCP. However, Angular Universal can overcome this type of duplication using a mechanism called TransferState.

Project overview

In this project, we will build a portfolio application for our GitHub user profile. We will initially use the Angular CLI to scaffold an Angular application that interacts with the GitHub API. We will learn how to use the GitHub API and fetch user-specific data. We will also use the Bootstrap CSS library to style our application and create a beautiful user interface.

After creating our Angular application, we will turn it into a server-side rendered application using Angular Universal. We will see how to install and configure Angular Universal, and we will learn how to prerender it during build time. Then, we will configure our application to be correctly rendered using SEO in the most popular social platforms. Finally, we will find out how to use the preboot library to play back browser events that are not fully supported in SSR applications.

Build time: 2 hours

Getting started

The following prerequisites and software tools are required for completing this project:

Building an Angular application with the GitHub API

GitHub contains an API that we can use to fetch various information about the profile of a GitHub user. The Angular application that we are building will communicate with the GitHub API and display a brief portfolio for our GitHub profile. Our application will consist of the following features:

  • Dashboard: This will be the landing page of the application, and it will display a summary of our GitHub profile.
  • Info: This will display personal information about us.
  • Repositories: This will display a list of our public repositories.
  • Organizations: This will display a list of GitHub organizations of which we are members.

    Important note

    The resulting output of each feature that is displayed in the screenshots of this chapter will be different according to your GitHub profile.

The dashboard will be the main page of the application, and it will contain all the other features. We will learn how to build the dashboard page in the following section.

Building the dashboard

Before we can start creating the main features of our application, we need to scaffold and configure an Angular application first by running the following command:

ng new gh-portfolio --routing=false --style=scss

The preceding command will use the ng new command of the Angular CLI, passing the following options:

  • gh-portfolio: The name of the Angular application that we want to create
  • --routing=false: Disables routing because our application will consist of a single page
  • --style=scss: Configures the Angular application to use the SCSS stylesheet format when working with CSS styles

We will use the Bootstrap CSS library for styling our portfolio application. Let's see how to install and configure it in the Angular CLI application that we have just created:

  1. Execute the following npm command to install the Bootstrap CSS library:

    npm i bootstrap

  2. Open the srcstyles.scss file and import the Bootstrap SCSS stylesheet:

    @import "~bootstrap/scss/bootstrap";

    The styles.scss file contains CSS styles that are applied globally to the application. The @import CSS rule accepts the absolute path of a stylesheet file that we want to load.

    Important note

    When we import a stylesheet format using the @import rule, we omit the extension of the file.

    The ~ character denotes the node_modules folder in the root folder of our Angular CLI application.

  3. Execute the following command to install Bootstrap Icons, a free and open source icon library:

    npm install bootstrap-icons

    Bootstrap Icons can be used in various formats, such as SVG or font. In this project, we are going to use the latter.

  4. Import the font icons format of the Bootstrap Icons library into the styles.scss file:

    @import "~bootstrap/scss/bootstrap";

    @import "~bootstrap-icons/font/bootstrap-icons";

We have already created the Angular application and added the necessary artifacts for styling it. We are now ready to start creating the main page of our Angular application:

  1. Download an Angular logo of your choice from the press kit of the official Angular documentation at https://angular.io/presskit.
  2. Copy the downloaded logo file into the srcassets folder of the Angular CLI workspace. The assets folder is used for static files such as images, fonts, and JSON files.
  3. Open the environment.ts file in the srcenvironments folder and add a new property to the environment object:

    export const environment = {

      production: false,

      username: '<Your GitHub login>'

    };

    Replace the value of the username property with your GitHub login. The environment object is used to define application-wide properties such as configuration settings or the URL of a backend API.

  4. Open the environment.prod.ts file that exists in the same folder and add the same property as in step 3. The environment.prod.ts file is used when we run an Angular CLI application in production mode. The environment.ts file is the default one, and it is used in development mode.

    Important note

    The environment object must have the same structure and properties in all environment files.

  5. Open the app.component.ts file and create a property in the AppComponent class to get the username property from the environment object:

    import { Component } from '@angular/core';

    import { environment } from

      '../environments/environment';

    @Component({

      selector: 'app-root',

      templateUrl: './app.component.html',

      styleUrls: ['./app.component.scss']

    })

    export class AppComponent {

      username = environment.username;

    }

    Important note

    We import the environment object from the default environment file. The Angular CLI replaces it with the respective environment file according to the mode that we are currently running, either development or production.

  6. Open the app.component.html file and replace its content with the following HTML template:

    <div class="toolbar d-flex align-items-center">

      <img width="40" alt="Angular Logo"

        src="assets/angular.png" />

      <span>Welcome to my GitHub portfolio</span>

      <a class="ms-auto p-2" target="_blank"

      rel="noopener" href="https://github.com/{{username}}

       " title="GitHub">

        <i class="bi-github"></i>

      </a>

    </div>

    In the preceding template, we define the header of our application. It contains an anchor element that links to our GitHub profile. We have also added the GitHub icon using the bi-github class from the Bootstrap Icon set.

  7. Insert the following HTML snippet after the header of the application:

    <div class="content d-flex flex-column">

      <div class="row">

        <div class="col-sm-3"></div>

        <div class="col-sm-9">

          <div class="row">

            <div class="col-12 col-sm-12"></div>

          </div>

          <div class="row">

            <div class="col-12 col-sm-12"></div>

          </div>

        </div>

      </div>

    </div>

    In the preceding snippet, we create the container element for the basic features of our application. The element with the col-sm-3 class selector will display the personal information feature. The element with the col-sm-9 class selector will be split into two rows, each one for the repositories and organizations feature. The resulting layout of the content will look like the following:

    Figure 7.1 – Main content

    Figure 7.1 – Main content

  8. Open the app.component.scss file and add the following CSS styles for the header and the content of our application:

    .toolbar {

      height: 60px;

      background-color: #1976d2;

      color: white;

      font-weight: 600;

    }

    .toolbar img {

      margin: 0 16px;

    }

    .toolbar i {

      font-size: 1.5rem;

      color: white;

      margin: 0 16px;

    }

    .toolbar a {

      margin-bottom: 5px;

    }

    .toolbar i:hover {

      opacity: 0.8;

    }

    .content {

      margin: 52px auto 32px;

      padding: 0 16px;

    }

  9. Run ng serve to start the application and navigate to http://localhost:4200. The header of the application should look like the following:
Figure 7.2 – Application header

Figure 7.2 – Application header

The main page of our portfolio application is now ready. It contains a header and an empty container element for adding the main features. In the following section, we will start building the personal information feature of our application.

Displaying personal information

The first feature of our application will be to display personal information from our GitHub profile, such as the full name, the profile photo, and some social media links. Before creating the feature, we first need to configure our application so that it can communicate with the GitHub API:

  1. Open the main module of the application, the app.module.ts file, and add the HttpClientModule class to the imports array of the @NgModule decorator:

    import { NgModule } from '@angular/core';

    import { BrowserModule } from '@angular/platform-

      browser';

    import { HttpClientModule } from

      '@angular/common/http';

    import { AppComponent } from './app.component';

    @NgModule({

      declarations: [

        AppComponent

      ],

      imports: [

        BrowserModule,

        HttpClientModule

      ],

      providers: [],

      bootstrap: [AppComponent]

    })

    export class AppModule { }

    The HttpClientModule class is the main Angular module of the built-in HTTP library that exports all necessary services for interacting with an HTTP resource.

  2. Create a new Angular service using the following Angular CLI command:

    ng generate service github

  3. Open the github.service.ts file and inject the HttpClient service into the constructor of the GithubService class:

    import { HttpClient } from '@angular/common/http';

    import { Injectable } from '@angular/core';

    @Injectable({

      providedIn: 'root'

    })

    export class GithubService {

      constructor(private http: HttpClient) { }

    }

    The HttpClient class is an Angular service of the built-in HTTP client that provides all the primary methods for interacting with an HTTP, such as GET, POST, and PUT.

  4. Open the environment.ts file and add a new property for the URL of the GitHub API:

    export const environment = {

      production: false,

      username: '<Your GitHub login>',

      apiUrl: 'https://api.github.com'

    };

    Make sure that you add the same property to the environment file for production.

All interaction between our application and the GitHub API will be delegated to GithubService. Now, let's focus on building our feature:

  1. Execute the following command of the Angular CLI to create a new Angular component for our feature:

    ng generate component personal-info

  2. Create a user interface to define the data model of our component using the following Angular CLI command:

    ng generate interface user

  3. Open the user.ts file and add the following properties to the User interface:

    export interface User {

      avatar_url: string;

      name: string;

      blog: string;

      location: string;

      bio: string;

      twitter_username: string;

      followers: number;

    }

  4. Open the github.service.ts file and add the following import statements:

    import { Observable } from 'rxjs';

    import { environment } from

      '../environments/environment';

    import { User } from './user';

  5. Create a new method for getting the details of our profile from the GitHub API:

    getUser(): Observable<User> {

      return this.http.get<User>(`${environment.apiUrl}/

        users/${environment.username}`);

    }

  6. Open the personal-info.component.ts file and add the following import statements:

    import { Observable } from 'rxjs';

    import { GithubService } from '../github.service';

    import { User } from '../user';

  7. Inject GithubService into the constructor of the PersonalInfoComponent class and create a component property to get the result of the getUser method:

    export class PersonalInfoComponent implements OnInit {

      user$: Observable<User> | undefined;

      constructor(private githubService: GithubService) { }

      ngOnInit(): void {

        this.user$ = this.githubService.getUser();

      }

    }

  8. Open the personal-info.component.html file and replace its content with the following HTML template:

    <div class="card" *ngIf="user$ | async as user">

      <img [src]="user.avatar_url" class="card-img-top"

        alt="{{user.name}} photo">

      <div class="card-body">

        <h5 class="card-title">{{user.name}}</h5>

        <p class="card-text">{{user.bio}}</p>

      </div>

      <ul class="list-group list-group-flush">

        <li class="list-group-item" title="Location">

          <i class="bi-geo me-2"></i>{{user.location}}

        </li>

        <li class="list-group-item" title="Followers">

          <i class="bi-people me-2"></i>{{user.followers}}

        </li>

      </ul>

      <div class="card-body">

        <a href="https://www.twitter.com/{{user.twitter_

          username}}" class="card-link">Twitter</a>

        <a [href]="user.blog" class="card-link">

          Personal blog</a>

      </div>

    </div>

    In the preceding template, we use the async pipe because the user$ property is an observable, and we need to subscribe to it so that we can get its values. The main advantage of the async pipe is that it unsubscribes from the observable automatically when a component is destroyed, avoiding potential memory leaks.

    We also create the user alias for the observable to reference it easily in various locations around the template of the component.

  9. Open the app.component.html file and add the selector of PersonalInfoComponent to the element with the col-sm-3 class selector:

    <div class="col-sm-3">

      <app-personal-info></app-personal-info>

    </div>

If we run ng serve to preview the application, we should see the personal information panel on the left side of the page:

Figure 7.3 – Personal information

Figure 7.3 – Personal information

The first feature of our portfolio application is now complete. It displays the personal information of our GitHub profile along with a short bio and some social network links. In the next section, we will build the repositories feature of our application.

Listing user repositories

The GitHub user profile contains a list of repositories that the user owns, called sources, or contributes, called forks. The repositories feature of our application will only display the sources repositories.

The repositories and organizations features will have a similar user interface. Thus, we need to create a component for use in both features:

  1. Execute the following command of the Angular CLI to create a new component:

    ng generate component panel

  2. Open the panel.component.ts file and define two input properties using the @Input decorator:

    import { Component, Input, OnInit } from '@angular/core';

    @Component({

      selector: 'app-panel',

      templateUrl: './panel.component.html',

      styleUrls: ['./panel.component.scss']

    })

    export class PanelComponent implements OnInit {

      @Input() caption: string = '';

      @Input() icon: string = '';

      constructor() { }

      ngOnInit(): void {

      }

    }

  3. Open the panel.component.html file and replace its content with the following HTML template:

    <div class="card mb-4">

      <div class="card-header">

        <i class="bi bi-{{icon}} me-1"></i>

        {{caption}}

      </div>

      <div class="card-body">

        <ng-content></ng-content>

      </div>

    </div>

    The panel component is a Bootstrap card element that consists of a header and a body. The header uses the caption and icon input properties to display text with an icon. The body uses the ng-content Angular component to define a placeholder where the content from our features will be displayed.

We can now start using the panel component to create our feature:

  1. Create an interface for representing the data model of a GitHub repository:

    ng generate interface repository

  2. Open the repository.ts file and add the following properties:

    export interface Repository {

      name: string;

      html_url: string;

      description: string;

      fork: boolean;

      stargazers_count: number;

      language: string;

      forks_count: number;

    }

  3. Open the github.service.ts file and import the Repository interface:

    import { Repository } from './repository';

  4. Now it is time for some refactoring in our service. The URL that we will use for getting repositories has some similarities with that of the getUser method. Extract the URL of that method in a property of the GithubService class:

    export class GithubService {

      private userUrl: string = '';

      constructor(private http: HttpClient) {

        this.userUrl = `${environment.apiUrl}/users/${

          environment.username}`;

      }

      getUser(): Observable<User> {

        return this.http.get<User>(this.userUrl);

      }

    }

  5. Create a new method to fetch repositories of the current GitHub user:

    getRepos(): Observable<Repository[]> {

      return this.http.get<Repository[]>(this.userUrl +

       '/repos');

    }

Now that we have created the prerequisites for fetching the user repositories from the GitHub API, we can start building the component that will display those repositories:

  1. Execute the following command to create a new Angular component using the Angular CLI:

    ng generate component repositories

  2. Open the repositories.component.ts file and add the following import statements:

    import { Observable } from 'rxjs';

    import { map } from 'rxjs/operators';

    import { GithubService } from '../github.service';

    import { Repository } from '../repository';

  3. Inject GithubService into the constructor of the RepositoriesComponent class and create a component property to get the result of the getRepos method:

    export class RepositoriesComponent implements OnInit {

      repos$: Observable<Repository[]> | undefined;

      constructor(private githubService: GithubService) { }

      ngOnInit(): void {

        this.repos$ = this.githubService.getRepos().pipe(

          map(repos => repos.filter(repo => !repo.fork))

        );

      }

    }

    We use the pipe RxJS operator to combine the observable returned from the getRepos method with the map operator to filter out fork repositories and get only sources. Filtering is accomplished using the standard filter method for arrays.

  4. Open the repositories.component.html file and replace its content with the following HTML template:

    <app-panel caption="Repositories" icon="archive">

      <div class="row row-cols-1 row-cols-md-3 g-4">

        <div class="col p-2" *ngFor="let repo of repos$ |

           async">

          <div class="card h-100">

            <div class="card-body">

              <h5 class="card-title">

                <a [href]="repo.html_url">{{repo.name}}

                 </a>

              </h5>

              <p class="card-text">{{repo.description}}

               </p>

            </div>

          </div>

        </div>

      </div>

    </app-panel>

    In the preceding template, we wrap the main content of the component inside the app-panel component and we set the caption and icon properties for the header.

    Our component iterates over the repos$ observable and displays the name and the description of each repository. The name is an anchor element that points to the actual GitHub URL of the repository.

  5. Add the following list immediately after the element with the card-body class selector:

    <ul class="list-group list-group-flush list-group-

      horizontal">

      <li class="list-group-item border-0">

        <i class="bi-code me-2"></i>{{repo.language}}

      </li>

      <li class="list-group-item border-0">

        <i class="bi-star me-2">

         </i>{{repo.stargazers_count}}

      </li>

      <li class="list-group-item border-0">

        <i class="bi-diagram-2 me-2">

          </i>{{repo.forks_count}}

      </li>

    </ul>

    In the preceding snippet, we display the language of each repository, how many have starred it, and how many have forked it.

  6. Open the app.component.html file and add the selector of RepositoriesComponent in the first HTML element with the col-12 col-sm-12 class selector:

    <div class="col-sm-9">

      <div class="row">

        <div class="col-12 col-sm-12">

          <app-repositories></app-repositories>

        </div>

      </div>

      <div class="row">

        <div class="col-12 col-sm-12"></div>

      </div>

    </div>

  7. Run ng serve to preview the application, and you should see the new panel next to the personal information feature:
Figure 7.4 – Repositories

Figure 7.4 – Repositories

The second feature of our application has been completed. It displays a list of public repositories that exist in our GitHub profile. Our application now also features a panel component that we can use to build the organizations feature of our application in the following section.

Visualizing the organization membership

A GitHub user can be a member of a GitHub organization. Our application will display a list of user organizations along with some additional information about each one. Let's start building our organization list:

  1. Create an interface to define the properties of an organization:

    ng generate interface organization

  2. Open the organization.ts file and add the following properties:

    export interface Organization {

      login: string;

      description: string;

      avatar_url: string;

    }

  3. Open the github.service.ts file and import the Organization interface:

    import { Organization } from './organization';

  4. Create a new method to get organizations of the current GitHub user:

    getOrganizations(): Observable<Organization[]> {

      return this.http.get<Organization[]>(this.userUrl +

        '/orgs');

    }

  5. Execute the following command to create an Angular component for our feature:

    ng generate component organizations

  6. Open the organizations.component.ts file and add the following import statements:

    import { Observable } from 'rxjs';

    import { GithubService } from '../github.service';

    import { Organization } from '../organization';

  7. Inject GithubService into the constructor of the OrganizationsComponent class and set the result of its getOrganizations method to an observable component property:

    export class OrganizationsComponent implements OnInit {

      orgs$: Observable<Organization[]> | undefined;

      constructor(private githubService: GithubService) { }

      ngOnInit(): void {

        this.orgs$ =

         this.githubService.getOrganizations();

      }

    }

  8. Open the organizations.component.html file and replace its content with the following HTML template:

    <app-panel caption="Organizations" icon="diagram-3">

      <div class="list-group">

        <a href="https://www.github.com/{{org.login}}"

          class="list-group-item list-group-item-action"

           *ngFor="let org of orgs$ | async">

          <div class="row">

            <img [src]="org.avatar_url">

            <div class="col-sm-9">

              <div class="d-flex w-100 justify-content-

                between">

                <h5 class="mb-1">{{org.login}}</h5>

              </div>

              <p class="mb-1">{{org.description}}</p>

            </div>

          </div>

        </a>

      </div>

    </app-panel>

    In the preceding HTML template, we place the main content of our component inside the app-panel component, passing an appropriate caption and icon. We display the name and description of each organization. Each organization is wrapped to an anchor element that points to the GitHub page of the organization.

  9. Open the organizations.component.scss file and add the following CSS styles for the organization logos:

    img {

      width: 60px;

      height: 40px;

    }

  10. Open the app.component.html file and add the selector of OrganizationsComponent in the second element with the col-12 col-sm-12 class selector:

    <div class="col-sm-9">

      <div class="row">

        <div class="col-12 col-sm-12">

          <app-repositories></app-repositories>

        </div>

      </div>

      <div class="row">

        <div class="col-12 col-sm-12">

          <app-organizations></app-organizations>

        </div>

      </div>

    </div>

  11. Run ng serve to start the application, and you should see the organization list under the repositories feature:
Figure 7.5 – Organizations

Figure 7.5 – Organizations

Our application now features a complete portfolio for the profile of a GitHub user. It displays the following:

  • Personal information, a short biography, and social media links
  • A list of public user repositories that contains links to each one for more information
  • A list of organizations where the user is a member with links to each one for further details

In the next section, we will learn how to integrate Angular Universal and render our application in the server.

Integrating Angular Universal

Angular Universal is an Angular library that enables an Angular CLI application to be rendered on the server. An SSR application increases the loading speed of an Angular application and improves the loading of the first page.

To install Angular Universal in an existing Angular CLI application, we will use the following command of the Angular CLI:

ng add @nguniversal/express-engine

The previous command uses the ng add command of the Angular CLI to install the @nguniversal/express-engine npm package. The @nguniversal/express-engine package is the heart of the Angular Universal library and consists of a Node.js Express web server at its core.

When we execute the preceding command to install Angular Universal, we are not only installing the library but also modifying our Angular CLI workspace with the following files:

  • angular.json: This creates new entries in the architect section to build and enable our Angular Universal application. One of these entries is the server property, which is responsible for building our application with SSR. It outputs the generated bundle into a separate server folder, inside the standard output folder of the Angular CLI application:

    "options": {

      "outputPath": "dist/gh-portfolio/server",

      "main": "server.ts",

      "tsConfig": "tsconfig.server.json",

      "inlineStyleLanguage": "scss"

    }

    The original application bundle is now generated into a browser folder, inside the standard output folder:

    "options": {

      "outputPath": "dist/gh-portfolio/browser",

      "index": "src/index.html",

      "main": "src/main.ts",

      "polyfills": "src/polyfills.ts",

      "tsConfig": "tsconfig.app.json",

      "inlineStyleLanguage": "scss",

      "assets": [

        "src/favicon.ico",

        "src/assets"

      ],

      "styles": [

        "src/styles.scss"

      ],

      "scripts": []

    }

    Thus, an Angular Universal application generates two versions of the same Angular application, one version for the server and another one for the browser.

  • package.json: This adds all the necessary npm dependencies and creates a handful set of npm scripts to start building with Angular Universal:

    "scripts": {

      "ng": "ng",

      "start": "ng serve",

      "build": "ng build",

      "watch": "ng build --watch --configuration

        development",

      "test": "ng test",

      "dev:ssr": "ng run gh-portfolio:serve-ssr",

      "serve:ssr": "node dist/gh-portfolio/server/

         main.js",

      "build:ssr": "ng build && ng run gh-

         portfolio:server",

      "prerender": "ng run gh-portfolio:prerender"

    }

    Scripts that contain the :ssr suffix are related to building and serving the Angular Universal application. The prerender script will create a prerendered version of an Angular application during build time. We will learn about the prerender script in the Prerendering content during build section.

  • server.ts: This contains the Node.js Express application that will host the server-side rendered version of our portfolio application.
  • main.server.ts: This is the main entry point of our Angular Universal application.
  • app.server.module.ts: This is the main application module of the server-side rendered application.
  • tsconfig.server.json: This is the TypeScript configuration for our Angular Universal application.
  • main.ts: The main entry point of our Angular application loads the main application module only after the DOM has been loaded completely:

    document.addEventListener('DOMContentLoaded', () => {

      platformBrowserDynamic().bootstrapModule(AppModule)

      .catch(err => console.error(err));

    });

  • app.module.ts: This calls the withServerTransition method of BrowserModule to load our application as a server-side rendered one:

    @NgModule({

      declarations: [

        AppComponent,

        PersonalInfoComponent,

        PanelComponent

        RepositoriesComponent,

        OrganizationsComponent

      ],

      imports: [

        BrowserModule.withServerTransition({ appId:

         'serverApp' }),

        HttpClientModule

      ],

      providers: [],

      bootstrap: [AppComponent]

    })

    export class AppModule { }

    Important note

    Global JavaScript objects such as window and document are not available when rendering an Angular application in the server because there is no browser. Angular provides abstraction APIs for some of these objects, such as the DOCUMENT injection token. If you need to enable them conditionally, you can inject the PLATFORM_ID token and use the isPlatformServer or isPlatformBrowser methods from the @angular/common npm package to check on which platform your application is currently running:

import { Inject, PLATFORM_ID } from '@angular/core';

import { isPlatformBrowser } from '@angular/common';

export class CheckPlatformComponent {

  isBrowser: boolean;

  

  constructor( @Inject(PLATFORM_ID) platformId: any) {

    this.isBrowser = isPlatformBrowser(platformId);

  }

}  

We can now run our GitHub portfolio application on the server using the following npm command:

npm run dev:ssr

To preview your GitHub portfolio application on the server, open your browser at http://localhost:4200.

You should typically see the application as it was before. So, what have we gained here? Angular Universal applications do not reveal their full potential when running in a development machine with a powerful processor and a lot of memory. Instead, we need to run and preview them in real-world cases, such as a slow network. We can use Google Chrome Developer Tools to emulate a slow network in a development environment:

  1. Open the Google Chrome browser.
  2. Toggle the developer tools and select the Network tab.
  3. Select the Slow 3G option from the Throttling dropdown.
  4. Enter http://localhost:4200 in the address bar of your browser.

If you click on any link while the page is loading, you will notice that nothing happens. Why is that? The server first loads a static version of your application to display to the user until waiting for the actual Angular application to load in the background. During this FCP, all links except routerLink directives are not responding. Angular Universal will switch to the complete application when it has been fully loaded in the background.

Later, in the Replaying events with preboot section, we will see how to handle these events. In the meantime, we will investigate how to improve the loading speed of our application even more using prerendering in the following section.

Prerendering content during build

The package.json file of our Angular CLI workspace contains the prerender npm script that we can use to improve the first loading of our application. The script runs the prerender command from the architect section of the angular.json configuration file and prerenders the content of our application during build time. Let's see the effect that prerendering will have on our GitHub portfolio application:

  1. Execute the following npm command to generate a prerendered version of the application:

    npm run prerender

    Important note

    The username and apiUrl properties in the environment production file should be set correctly. Otherwise, the command will output errors in the terminal window of VSCode.

    The preceding command will output a production bundle of the application into the distgh-portfoliorowser folder.

  2. Navigate to the distgh-portfoliorowser folder and you should see two HTML files, index.html and the index.original.html file.
  3. Open the index.original.html file and find the app-root HTML element. This is the main component of our Angular application where Angular will render the content of our application in the browser.
  4. Open the index.html file and have a look again at the app-root element.

    The main component is not empty this time. Angular Universal has made all HTTP requests to the GitHub API and prefetched the content of our application during runtime. All component templates and styles have been prerendered in the main HTML file, which essentially means that we can view our application on a browser even without JavaScript enabled!

  5. Execute the following command to start the prerendered version of our GitHub portfolio application:

    npm run serve:ssr

    The preceding command will start the application at http://localhost:4000.

  6. Disable JavaScript from the settings of your browser and navigate to http://localhost:4000.

The GitHub portfolio application that we created remains fully operational without having JavaScript enabled. The main page of the application is also rendered instantly without having the user wait for the application to load.

The previous scenario is a perfect fit for users who cannot afford to have JavaScript enabled on their devices. But what happens when the same prerendered version of the application is used by a user with JavaScript enabled? Let's learn more about that:

  1. Enable JavaScript in your browser and toggle the developer tools.
  2. Navigate to http://localhost:4000. Nothing different seems to happen at first sight. Nevertheless, the application loads instantly due to the prerendered content.
  3. Inspect the Network tab and you will notice the following:
    Figure 7.6 – Network tab (Google Chrome)

Figure 7.6 – Network tab (Google Chrome)

Our application initiates all HTTP requests to the GitHub API as if it was rendered from a browser. It essentially duplicates all HTTP requests needed from the application even if data has already been prerendered on the HTML page. Why is that?

The application makes one HTTP request for the browser rendered version and another for the SSR application because both versions have a different state. We can prevent the previous behavior by sharing the state between the server and the browser. More specifically, we can transfer the state of the server to the browser using a special-purpose Angular module of the Angular Universal library called TransferHttpCacheModule.

If we use TransferHttpCacheModule, the server will cache responses from the GitHub API and the browser will use the cache instead of initiating a new request. TransferHttpCacheModule solves the problem by installing an HTTP interceptor in the Angular application that ignores HTTP requests that have been handled by the server initially.

Important note

An HTTP interceptor is an Angular service that intercepts HTTP requests and responses that originate from the built-in HTTP client of the Angular framework.

To install TransferHttpCacheModule in our GitHub portfolio application, follow these steps:

  1. Open the main module file of the Angular application, app.module.ts, and import TransferHttpCacheModule from the @nguniversal/common npm package:

    import { TransferHttpCacheModule } from '@nguniversal/common';

  2. Add the TransferHttpCacheModule class to the imports array of the @NgModule decorator:

    @NgModule({

      declarations: [

        AppComponent,

        PersonalInfoComponent,

        PanelComponent,

        RepositoriesComponent,

        OrganizationsComponent

      ],

      imports: [

        BrowserModule.withServerTransition({ appId:

         'serverApp' }),

        HttpClientModule,

        TransferHttpCacheModule

      ],

      providers: [],

      bootstrap: [AppComponent]

    })

  3. Open the main module file of the SSR application, app.server.module.ts, and import ServerTransferStateModule from the @angular/platform-server npm package:

    import { ServerModule, ServerTransferStateModule }

      from '@angular/platform-server';

  4. Add the ServerTransferStateModule class to the imports array of the @NgModule decorator:

    @NgModule({

      imports: [

        AppModule,

        ServerModule,

        ServerTransferStateModule

      ],

      bootstrap: [AppComponent],

    })

  5. Execute the following command to prerender your application:

    npm run prerender

  6. Run the following command to start your prerendered application:

    npm run serve:ssr

If you preview the portfolio application and inspect the Network tab of your browser, you will notice that it does not make additional HTTP requests. TransferHttpCacheModule intercepted all HTTP requests and stored them in the TransferState store of our application. TransferState is a key-value store that can be transferred from the server to the browser. The browser version of the application later can read the HTTP responses directly from the store without making an extra call.

We now have a fully prerendered version of our GitHub portfolio. But how can we optimize it further so that we can share it on a social media platform? We will learn more about SEO optimization techniques in the following section.

Enhancing SEO capabilities

SEO is the process of optimizing a website to be correctly indexed from a web crawler. A web crawler is a special-purpose software that is present on most search engines and can identify and index websites so that they are easily discoverable and linkable through their platforms.

Angular Universal does a great job of SEO by prerendering content during build time. Some web crawlers cannot execute JavaScript and build the dynamic content of an Angular application. Prerendering with Angular Universal eliminates the need for JavaScript, thus allowing web crawlers to do their best to identify the web application.

We can also help SEO by defining several tags in the head element of the main index.html file of an Angular application, such as title, viewport, and charset:

index.html

<!doctype html>

<html lang="en">

<head>

  <meta charset="utf-8">

  <title>GhPortfolio</title>

  <base href="/">

  <meta name="viewport" content="width=device-width,

    initial-scale=1">

  <link rel="icon" type="image/x-icon" href="favicon.ico">

</head>

<body>

  <app-root></app-root>

</body>

</html>

You can find a list of available tags at https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name.

However, setting a tag in the index.html file is not adequate, especially when an Angular application has routing enabled and contains several routes. The Angular framework provides a couple of handy services that we can use to set tags programmatically. First, let's see how to set the title tag in our application:

  1. Open the app.component.ts file and import the Title and OnInit Angular artifacts:

    import { Component, OnInit } from '@angular/core';

    import { environment } from

      '../environments/environment';

    import { Title } from '@angular/platform-browser';

  2. Inject the Title service into the constructor of the AppComponent class:

    export class AppComponent {

      username = environment.username;

      constructor(private title: Title) {}

    }

  3. Add the OnInit interface to the implemented interface list of the component and call the setTitle method of the title variable in the ngOnInit method:

    export class AppComponent implements OnInit {

      username = environment.username;

      constructor(private title: Title) {}

      ngOnInit() {

        this.title.setTitle('GitHub portfolio app');

      }

    }

  4. Run npm run dev:ssr to preview the application, and you should see the title in the browser tab:
Figure 7.7 – Browser tab title

Figure 7.7 – Browser tab title

Similar to the Title service, we can use the Meta service to set meta tags for our application:

  1. Open the app.component.ts file and import Meta from the @angular/platform-browser npm package:

    import { Meta } from '@angular/platform-browser';

  2. Inject the Meta service into the constructor of the AppComponent class:

    constructor(private title: Title, private meta: Meta) {}

  3. Use the addTags method of the meta variable to add some meta tags to the ngOnInit method:

    ngOnInit(): void {

      this.title.setTitle('GitHub portfolio app');

      this.meta.addTags([

        {

          name: 'description',

          content: `${this.username}'s GitHub portfolio`

        },

        {

          name: 'author',

          content: this.username

        }

      ]);

    }

    In the preceding code, we add two meta tags. The first one sets the description that contains the username of the current GitHub profile. The second one sets the author tag to be the same as the username of the GitHub profile.

  4. Run npm run dev:ssr to start the application and navigate to http://localhost:4200.
  5. Use your browser to inspect the page, and you should see the following meta tags in the head element of the page:
Figure 7.8 – Application head element

Figure 7.8 – Application head element

Each of the popular social platforms, such as Twitter, Facebook, and LinkedIn, requires its own meta tags so that the URL of an SSR application can be correctly displayed on their platforms. If you do not want to add them manually using the Meta service of the Angular framework, you can use the ngx-seo library, which provides built-in methods for each platform. You can get the library from the npm registry at https://www.npmjs.com/package/ngx-seo.

In the following section, we will conclude our project by learning how to replay events not supported by an SSR application.

Replaying events with preboot

In the Integrating Angular Universal section, we saw that an SSR application does not respond to user events other than navigations with the routerLink directive until it is entirely bootstrapped. We will now learn how to queue those events and replay them when the application has been fully loaded using the preboot library. Let's see how to use it:

  1. Execute the following npm command to install the library:

    npm install preboot

  2. Open the app.module.ts file and import PrebootModule from the preboot npm package:

    import { PrebootModule } from 'preboot';

  3. Add the PrebootModule class to the imports array of the @NgModule decorator:

    @NgModule({

      declarations: [

        AppComponent,

        PersonalInfoComponent,

        PanelComponent,

        RepositoriesComponent,

        OrganizationsComponent

      ],

      imports: [

        BrowserModule.withServerTransition({ appId:

         'serverApp' }),

        HttpClientModule,

        TransferHttpCacheModule,

        PrebootModule.withConfig({ appRoot: 'app-root' })

      ],

      providers: [],

      bootstrap: [AppComponent]

    })

    We use the withConfig method of PrebootModule to define the main component of our Angular application. It accepts an options object of the PrebootOptions type that contains various properties, which you can find at https://github.com/angular/preboot#PrebootOptions. One of the options is the appRoot property, which defines the selector of the main component.

The purpose of the preboot library is to maintain a consistent user experience while the state of the application is transferred from the server to the browser.

To verify its functionality, run npm run dev:ssr to start the SSR application and use the steps described in the Integrating Angular Universal section to simulate a slow network. If you try to click on any link while the application is loading, you will notice that your browser will navigate to the selected link after the application has been fully loaded in the browser.

Summary

In this project, we build a portfolio application for our GitHub profile. Initially, we learned how to interact with the GitHub API in a new Angular application. We also used Bootstrap CSS and Bootstrap Icons to provide a beautiful user interface for our portfolio application.

We then saw how to convert our Angular application into an SSR application using Angular Universal. We learned how to benefit from prerendering content when users have low-end and slow-performant devices and some of the potential pitfalls of this technique.

We used some of the available SEO techniques that the Angular framework offers to improve the discoverability of our application. Finally, we installed the preboot library to improve the user experience of our application during loading.

In the next chapter, we will learn about the monorepo architecture and how we can manage the state of an Angular application.

Practice questions

Let's take a look at a few practice questions:

  1. What is the purpose of environment files in an Angular application?
  2. How do we subscribe to an observable in the template of a component?
  3. Which command do we use for installing Angular Universal?
  4. How can we differentiate programmatically between browser and server platforms?
  5. Which command generates a prerendered version of an SSR application?
  6. Which Angular module do we use to transfer the state from the server to the browser?
  7. Which Angular service do we use to set the title of an Angular application?
  8. Which Angular service do we use to set meta tags in an Angular application?
  9. What is the purpose of the preboot library?
  10. How do we enable preboot in an SSR application?

Further reading

Here are some links to build upon what we learned in the chapter:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset