DotNetCurry Logo

Server Side Rendering using Angular Universal and Node.js to create a Pokemon Explorer App

Posted by: Ravi Kiran , on 9/13/2017, in Category AngularJS
Views: 2481
Abstract: Using Angular Universal and Node.js to build an application using the Pokémon API to show a list of pokémons and their details. The application would be rendered from the server.

It is needless to say that front-end JavaScript frameworks have taken a leading position in modern web development. The richness these frameworks bring in to the web application is illustrated in most modern websites.

One of the reasons visitors experience this richness is because these sites are highly responsive. A visitor doesn’t have to wait for long to see a response after sending a request.

But this richness comes at a cost!

To create this richness, a lot of logic has to be run on the browser using JavaScript. The cost is, the dynamic content rendered on the page using JavaScript cannot be consistently read by all search engines.

Are you keeping up with new developer technologies? Advance your IT career with our Free Developer magazines covering Angular, React, .NET Core, MVC, Azure and more. Subscribe to this magazine for FREE and download all previous, current and upcoming editions.

While all Search engines (SE’s) can scan the content that travels from the server to the client, not all SE’s have the capability to scan it when the content is built dynamically at the client’s end. So your site cannot be reached through all SE’s if you are rendering the content on the client side.

In addition to this, we don’t see readable previews when links of JavaScript applications are shared on social media. Again, this is because the social media sites do not run JavaScript while rendering previews, thus making the pages hard to share.

The solution to this problem is Server Side Rendering.

Some of you might think that this approach is taking us back to the days when we had everything rendered from a server. But no, this approach instead enables us to render the initial view of the application from the server and rest of the application, runs on the client side.

It has the following advantages over the traditional SPAs:

  • As the initial page is rendered from the server, it can be parsed by the search engines and the content of the page can be reached using search engines
  • The user doesn’t have to keep looking at the half-baked page, as the complete page with data, is rendered on the client’s system
  • Social media platforms like Facebook and Twitter can show the preview of the site when the URL of a server rendered application is posted on these platforms.

Editorial Note: Web crawlers are becoming smarter now-a-days. Google made a statement in October 2015 implying that it can crawl and index dynamic content. Bing can too. However for a consistent SEO experience across all search engines, server side rendering still remains the preferred option.

Server Side Rendering in Angular

To support server side rendering in Angular, the Angular team created a project named Angular Universal.

Angular Universal is a package to add server rendering support to Angular applications. As Angular is a client framework and can’t be understood by server platforms, the Angular Universal package provides the required knowledge to the server engine.

As we will see shortly, the universal package can be used to pass the Angular module to the Node.js server­. Before Angular 4, it was maintained as a separate repository, but it is now moved to the npm submodule @angular/platform-server.

The support for universal apps was added to Angular CLI in the version 1.3.0.

In this article, we will build an application using the pokémon API to show a list of pokémons and their details. The application would be rendered from the server.

Setting up the environment and application

To follow along the steps shown in this article, the following tools need to be installed on your system:

- Node.js (version 6 or later): Can be downloaded from the official site for Node.js and installed

- npm: A package manager for Node, it is installed along with Node.js

- Angular CLI: It is an npm package created by the Angular team to make the process of creating, testing and building an Angular application easier. It can be installed using the following command:

> npm install @angular/cli –g

Once these tools are installed, a new Angular application can be generated using the following command:

> ng new app-name

We will be building an application that consumes the Pokéapi and displays some information about the pokémons. As this is a universal application, let’s name it as pokemon-universal. Run the following command to create this application:

> ng new pokemon-universal

This command will take a few minutes to complete.

Once it completes, the command creates a new folder named after the project, creates a structure inside the folder and installs the npm packages. The following screenshot shows the structure in Visual Studio Code:

angular-project-structure

Figure 1 – Folder structure of the project

The following listing explains the vital parts of the project created:

  • The src folder will contain the source files of the Angular application. The generated project comes with a few default files
  • The file .angular-cli.json is the configuration to be used by Angular CLI
  • The files karma.conf.js and protractor.conf.js contain the settings for the Karma test runner to run unit tests and the settings for protractor to run e2e tests
  • The e2e folder would contain the end to end tests
  • The file tsconfig.json contains the configuration to be used to compile TypeScript
  • The linting rules to be used during TypeScript compilation are stored in the file tslint.json

You can run this application using the following command:

> ng serve

This command starts a server on the port 4200. Open a browser and change the URL to http://localhost:4200 to browse the application. Now the application is in the default SPA mode.

We need to make a set of changes to be able to run it from the server. Let’s do this in the next section.

Installing Packages

Let’s make the default application render from server before building the pokémon app.

For this, a couple of packages need to be installed to enable the application for server rendering. The following list describes them:

1. @angular/platform-server: This package is created by the Angular team to support server side rendering of Angular applications. This module will be used to render the Angular module from the server side Node.js code

2. express: Express is a Node.js framework for building web applications and APIs. This package will be used to serve the page

The following commands will install these packages. Open a command prompt, move to the folder where the sample app is created and run the following commands there:

> npm install @angular/platform-server --save-dev
> npm install express --save

Adding Required Files

We need to make a few changes to the Angular application to enable it for server rendering.

The BrowserModule imported in the AppModule located in the file app.module.ts has to be made server compatible by adding withServerTransition. Change it as shown in the following snippet:

imports: [
  BrowserModule.withServerTransition({ appId: 'my-app' })
]

A new module has to be created specifically to run the code from the server. This module would import the main AppModule that starts the application and the ServerModule exported by the @angular/platform-server package. The new module won’t have components and other code blocks of its own.

Add a new file to the app folder and name it app-server.module.ts. Place the following code inside this file:

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';

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

@NgModule({
  imports: [
    AppModule,
    ServerModule
  ],
  declarations: [],
  bootstrap: [AppComponent]
})
export class AppServerModule { }

We need a file to serve as the entry point for the application from the server. This file does nothing more than importing the AppServerModule created above. Add a new file to the src folder and name it main.server.ts.

The following snippet shows the code to be added to this file:

export { AppServerModule } from './app/app-server.module';

The TypeScript files have to be transpiled differently for server side rendering. For this, a different configuration file is needed for transpilation. Add a new file to the src folder and name it tsconfig.server.json.

Paste the following code in this file:

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    "module": "commonjs",
    "types": []
  },
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ],
  "angularCompilerOptions": {
    "entryModule": "app/app-server.module#AppServerModule"
  }
}

You will notice the following differences between this file and the tsconfig.app.json file located in the src folder:

  • The module system to be transpiled is set to commonjs
  • It has a new section named angularCompilerOptions and it specifies the path of the Angular module to be loaded from the server code

Adding Configuration for Server Rendering

Information about server rendering has to be fed to the Angular CLI.

For this, we need to add a new application entry in the file .angular-cli.json. If you open this file, you will see a property named apps assigned with an array that has a single entry. The existing entry is the configuration used for development and deployment of the application in the client-oriented mode. The new entry will be for server-oriented mode.

The following is the configuration entry to be added to the apps array:

{
  "platform": "server",
  "root": "src",
  "outDir": "dist-server",
  "assets": [
    "assets",
    "favicon.ico"
  ],
  "index": "index.html",
  "main": "main.server.ts",
  "test": "test.ts",
  "tsconfig": "tsconfig.server.json",
  "testTsconfig": "tsconfig.spec.json",
  "prefix": "app",
  "styles": [
    "styles.scss"
  ],
  "scripts": [],
  "environmentSource": "environments/environment.ts",
  "environments": {
    "dev": "environments/environment.ts",
    "prod": "environments/environment.prod.ts"
  }
}

As you can see, most of the configuration in this entry is similar to the first entry, albeit some differences which are as follows:

  • The first property platform set to server says that this application is going to be executed from the server. The ng build command uses this value to generate server friendly code when the build is executed
  • The output path of this application is set to dist-server, which means the files created by this build would be placed in the dist-server folder
  • It uses the new tsconfig.server.json file to transpile TypeScript

Setting up the Node.js Server

The last file to be added to the application is the file that starts the node.js server. This file will use the JavaScript file produced by running production build using the server application configured in the .angular-cli.json file. It is then applied on the index.html page. The resultant index.html file would be served by the express engine.

Add a new file to the root of the application and name it server.js. Add the following content to this file:

let express = require('express');
let path = require('path');
let ngCore = require('@angular/core');
let fs = require('fs');

let app = express();
const PORT = 3000;

// Load zone.js for the server
require('zone.js/dist/zone-node');

// Enabling prod mode
ngCore.enableProdMode();

// Import renderModuleFactory from @angular/platform-server
let renderModuleFactory = require('@angular/platform-server').renderModuleFactory;

// Load the index.html file from the dist folder
let index = fs.readFileSync('./dist/index.html', 'utf8');

//-------------------------------------------------------------------------------
// Import the AOT compiled factory for your AppServerModule
// This import will change with the hash of your built server bundle
let AppServerModuleNgFactory = require('./dist-server/main.bundle').AppServerModuleNgFactory;
app.engine('html', (_, options, callback) => {
  const opts = { document: index, url: options.req.url };
  // Render to HTML and send it to the callback
  renderModuleFactory(AppServerModuleNgFactory, opts).then(html => callback(null, html));
});
//-------------------------------------------------------------------------------

app.set('view engine', 'html');
app.set('views', 'dist')
app.get('*.*', express.static(path.join(__dirname, '.', 'dist')));

// Respond with the content read from index for all requests
app.get('*', (req, res) => {
  res.render('index', { req });
});

app.listen(PORT, () => {
  console.log(`listening on http://localhost:${PORT}!`);
});

The most important parts of the code in the snippet have inline comments explaining them. As you see, the server code uses the static files from the dist folder to serve the client application. The most vital part of this file is the snippet shown in the following screenshot:

angular-module

Figure 2 – Code snippet loading the Angular module in the server

Statement 23 gets object of the Angular module built for server rendering. This module is passed to the renderModuleFactory method along with the options. The options contain the HTML content to be rendered and the URL to be served by the Angular application. The URL is useful in applications with routes.

The HTML obtained as a result of this operation is passed to the callback of the HTML engine, so that it can be rendered on the page.

Running the Default Application in Server Mode

Now we are good to build and run the application.

Run the following commands in the given sequence. The first command builds the client application, second command builds the code to be rendered from the server and the third command starts the Node.js server.

> ng build
> ng build --app 1 --prod --output-hashing none
> node server.js

Open your favorite browser and change the URL to http://localhost:3000. You will see the same application that we saw earlier. But the difference is, now the components are compiled on the server and the result is sent to the browser. The following screenshot taken on Chrome dev tools shows the content served in response to the index file:

index-html

Figure 3 – Content served for index.html from server

Note: Some of these instructions may change in the future versions of angular-cli or platform-server. You can refer to the instructions on the official wiki of angular-cli if these steps don’t produce the desired result.

Building the Pokémon Explorer App

Now that we saw how the default application works when rendered from the server, let’s modify the sample to add two routes and see how they work when rendered from server.

The final application will have two routes.

One to show a list of pokémons and the second one to show the details of a pokémon selected on the first page. First, we need to add a service to fetch the data and create a model to represent the structure of a pokémon.

We need bootstrap in the application we are going to build. We need to install bootstrap from npm and add it to .angular-cli.json file to include it in the bundle. Run the following command to install the package:

> npm install bootstrap --save

Modify the styles property in both the applications configured in .angular-cli.json as following:

"styles": [
  "styles.css",
  "../node_modules/bootstrap/dist/css/bootstrap.css"
]

Fetching Pokémon Data

The following snippet shows the model classes required for the pokémon explorer. Add a new file to the app folder, name it pokemon.ts and add the following code to it:

export class Pokemon {
  name: string;
  id: number;
  types = [];
  stats = [];
  sprites: Sprite[] = [];

  get imageUrl() {
    return `https://rawgit.com/PokeAPI/sprites/master/sprites/pokemon/${this.id}.png`;
  }
}

export class Sprite {
  name: string;
  imagePath: string;
}

The service will make use of the above models to serve the data to the components. To add the service, you can run the following Angular CLI command:

> ng g s pokemon.service

Once the file is generated, add the following code to it:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/toPromise'

import { Pokemon } from './pokemon';

@Injectable()
export class PokemonService {

  private baseUrl: string = 'https://pokeapi.co/api/v2';

  constructor(private http: Http) { }

  listPokemons() {
    return this.http.get(`${this.baseUrl}/pokedex/1/`)
      .toPromise()
      .then((res: any) => {
        let pokemons: Pokemon[] = [];
        let reducedPokemonEntries = JSON.parse(res._body).pokemon_entries.splice(0, 50);


        reducedPokemonEntries.forEach((entry) => {
          let pokemon = new Pokemon();
          pokemon.name = entry.pokemon_species.name;
          pokemon.id = entry.entry_number;

          pokemons.push(pokemon);
        });
        return pokemons;
      });
  }

  getDetails(id: number) {
    return this.http.get(`${this.baseUrl}/pokemon/${id}/`)
      .toPromise()
      .then((res: any) => {
        let response = JSON.parse(res._body);
        let pokemon = new Pokemon();
        pokemon.name = response.name;
        pokemon.id = response.id;

        response.types.forEach((type) => {
          pokemon.types.push(type.type.name);
        });

        response.stats.forEach((stat) => {
          pokemon.stats.push({
            name: stat.stat.name,
            value: stat.base_stat
          });
        });

        for (let sprite in response.sprites) {
          if (response.sprites[sprite]) {
            pokemon.sprites.push({
              name: sprite,
              imagePath: response.sprites[sprite]
            });
          }
        }

        return pokemon;

      });
  }
}

The service has two methods. The first method listPokemons gets a list of pokémons by querying the pokedex API and takes the first fifty pokémons. This is done to show lesser amount of data on the page and keep the demo simple. If you want to see more number of pokémons, you can modify the logic.

The second method getDetails receives an id of the pokémon and gets the details of it by querying the pokémon API. To call the pokémon REST APIs, the service uses HttpClient. It is a new service added to the @angular/common package and it has a simplified API to invoke and intercept the HTTP based endpoints.

The service has to be registered as a provider in the module. The following snippet shows the modified AppModule:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';


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

import { PokemonService } from './pokemon.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'my-app' }), 
HttpModule

  ],
  providers: [PokemonService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Building the Components

Let’s add the components required for the application.

We need two components to show the list of pokémons and to show the details of a pokémon. These components would be configured to render on different routes. Let’s get the components generated, then we will modify them to display the data we need to show.

> ng g c pokemon-list -m app.module
> ng g c pokemon-details -m app.module

Note: Notice the –m option added to the above commands. This option tells Angular CLI to register the generated component in the specified module. We don’t need to specify this option in most of the cases. But here we need it because we have multiple modules created at the root of the application.

As the name says, the pokemon-list component simply lists the pokémons. It calls the getList method of the PokemonService and displays the results in the form of boxes. Open the file pokemon-list.component.ts and replace the code of this file with the following:

import { Component, OnInit } from '@angular/core';
import { PokemonService } from '../pokemon.service';
import { Pokemon } from '../pokemon';

@Component({
  selector: 'app-pokemon-list',
  templateUrl: './pokemon-list.component.html',
  styleUrls: ['./pokemon-list.component.css']
})
export class PokemonListComponent implements OnInit {
  pokemonList: Pokemon[];

  constructor(private pokemonService: PokemonService) { }

  ngOnInit() {
    this.pokemonService.listPokemons()
      .then(pokemons => {
        this.pokemonList = pokemons;
      });
  }
}

The template of this component has to be modified to show the list. The widget showing each pokémon will have a link to navigate to the details page. Replace the content in the file pokemon-list.component.html with the following:

<div *ngFor="let pokemon of pokemonList" class="col-md-3 col-md-offset-1 text-center box">
  <div class="row">
    <div class="col-md-12">
      <img [src]="pokemon.imageUrl" height="150" width="150" />
    </div>
    <div class="row">
      <div class="col-md-12 link">
        <a [routerLink]="['/details',pokemon.id]">{{ pokemon.name | titlecase }}</a>
      </div>
    </div>
  </div>
</div>

The pokemon-details component receives id of the pokémon whose details have to be displayed and it calls the getDetails method of the PokemonService using received id to fetch the details.

The following snippet shows this code. Replace the code in the file pokemon-details.component.ts with the following:

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { PokemonService } from '../pokemon.service';
import { Pokemon } from '../pokemon';

@Component({
  selector: 'app-pokemon-details',
  templateUrl: './pokemon-details.component.html',
  styleUrls: ['./pokemon-details.component.css']
})
export class PokemonDetailsComponent implements OnInit {
  id: number;
  pokemon: Pokemon;

  constructor(private route: ActivatedRoute,
    private router: Router,
    private pokemonService: PokemonService) { }

  ngOnInit() {
    this.route.paramMap.subscribe((params) => {
      this.id = parseInt(params.get('id'));
      this.pokemonService.getDetails(this.id)
        .then((details) => {
          this.pokemon = details;
        });
    });
  }
}

This component has to display the details of the pokémon like name, types, statistics and images of the sprites. The following snippet shows the template, place this code in pokemon-details.template.html:

<div *ngIf="pokemon" class="details-container">
  <img [src]="pokemon.imageUrl">
  <div>{{pokemon.name | titlecase}}</div>
  <h4>Types:</h4>
  <ul>
    <li *ngFor="let type of pokemon.types">
      {{ type }}
    </li>
  </ul>

  <h4>Stats:</h4>
  <ul>
    <li *ngFor="let stat of pokemon.stats">
      {{ stat.name }}: {{ stat.value }}
    </li>
  </ul>

  <h4>Sprites:</h4>
  <div *ngFor="let sprite of pokemon.sprites" class="col-md-3">
    <img [src]="sprite.imagePath" />
    <br>
    <span>{{sprite.name}}</span>
  </div>
</div>

Adding Routes

Now that the components are ready, let’s add the routes and complete the application.

Add a new file in the src folder and name it app.routes.ts. As we have been discussing till now, we need to add two routes in the application. One to show the list of pokémons and the other to show details of a pokémon.

The following snippet shows the code. Add the following code to this file:

import { Routes, RouterModule } from '@angular/router';
import { PokemonListComponent } from './pokemon-list/pokemon-list.component';
import { PokemonDetailsComponent } from './pokemon-details/pokemon-details.component';

let routes: Routes = [
  {
    path: '',
    component: PokemonListComponent
  },
  {
    path: 'details/:id',
    component: PokemonDetailsComponent
  }
];

const routesModule = RouterModule.forRoot(routes);
export { routesModule };

This has to be added to the application module to make the routing work. We need to import the routesModule exported from the above file and add it to the imports array of the module.

The following snippet shows the modified module file:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';

import { routesModule } from './app.routes';

import { AppComponent } from './app.component';
import { PokemonListComponent } from './pokemon-list/pokemon-list.component';

import { PokemonService } from './pokemon.service';
import { PokemonDetailsComponent } from './pokemon-details/pokemon-details.component';

@NgModule({
  declarations: [
    AppComponent,
    PokemonListComponent,
    PokemonDetailsComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'my-app' }), HttpModule,routesModule
  ],
  providers: [
    PokemonService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

The last change ­we need to make is, modify the template of AppComponent to load the routes. Open the file app.component.html and replace the content of this file with the following code:

<div class="container">
  <div style="text-align:center">
    <h1>
      Explore the Pokemons!
    </h1>
  </div>
  <router-outlet></router-outlet>
</div>

Now run the following commands to build and run the application:

> ng build
> ng build --app 1 --prod --output-hashing none
> node server.js

You will see the pokemon images displayed on the page as shown in Figure 4:

nodejs-pokemon-app

Figure – 4 Pokemon list

Move to the details page by clicking on one of the links and refresh the details page. You will see that the details page is rendered from the server. The following screenshot shows the HTML received from the server in response to the request made:

pokemon-list-server

Figure – 5 HTML of pokemon list from server

Switch between the list and details pages and randomly refresh any of the pages. You will see that the content of the first load comes from the server. This model enables server rendering on all the pages in the application and hence the entire application would have a consistent SEO experience.

Note: You will notice a slight flicker in the page when it is rendered from the server. The flicker is because the app gets bootstrapped from the client side as well after the rendering completes from the server.

Conclusion

The wide usage of JavaScript frameworks has made it necessary that we provide consistent SEO experience even for those search engines that cannot scan and index dynamically generated content. Server side rendering is a big value add to business driven sites as they will enjoy the benefits of both being rich and searchable.

As we saw in this article, Angular has a very good support for server side rendering and it is now integrated with Angular-CLI as well. We should definitely use this feature to make our sites SEO consistent!

Download the entire source code of this article (Github)

This article was reviewed by Mahesh Sabnis and Suprotim Agarwal.

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+
Further Reading - Articles You May Like!
Author
Rabi Kiran (a.k.a. Ravi Kiran) is a developer working on Microsoft Technologies at Hyderabad. These days, he is spending his time on JavaScript frameworks like AngularJS, latest updates to JavaScript in ES6 and ES7, Web Components, Node.js and also on several Microsoft technologies including ASP.NET 5, SignalR and C#. He is an active blogger, an author at SitePoint and at DotNetCurry. He is rewarded with Microsoft MVP (Visual Studio and Dev Tools) and DZone MVB awards for his contribution to the community


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!