Angular Universal
module introduction
all content gets rendered after the page has loaded
javascript code then gets executed
main.ts loads the app.module which renders the application
the html sent to client is shown below
    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <title>Course Project</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>
      <script type="text/javascript" src="inline.bundle.js"></script>
      <script type="text/javascript" src="polyfills.bundle.js"></script>
      <script type="text/javascript" src="styles.bundle.js"></script>
      <script type="text/javascript" src="vendor.bundle.js"></script>
      <script type="text/javascript" src="main.bundle.js"></script>
    </body>
    </html>
        
not favorable for SEO because page has no content for search engine web crawlers
can use server-side rendering for at least the page's initial view as determine by the URL
if initial request is for http://someServer:somePort/somePage the components which compose somepage will be rendered server side
doing this permits search engine web crawlers to view content

Top

Index

a look at prerequisites
univeral rendering means run in the browser but rendered on the server
caveats how to set a project up for universal rendering can be found in the
Angular Wiki
first install the required packages
    npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader   
             
With these steps complete, you should be able to build a server bundle for your application, using the --app flag to tell the CLI to build the server bundle, referencing its index of 1 in the "apps" array in .angular-cli.json
    REM This builds the client application in dist/browser/
    ng build --prod
    ...
    REM This builds the server bundle in dist/server/
    ng build --prod --app 1 --output-hashing=false
     
outputs
    Date: 2017-07-24T22:42:09.739Z
    Hash: 9cac7d8e9434007fd8da
    Time: 4933ms
    chunk {0} main.bundle.js (main) 9.49 kB [entry] [rendered]
    chunk {1} styles.bundle.css (styles) 0 bytes [entry] [rendered]      
     

Top

Index

creating the server main file
in app.module's imports property add withServerTransition method to the BrowserModule import
the property is an identifier unique to the project
allow Angular to differentiate what was rendered on the server from what is rendered on the client
    ...
    @NgModule({
      declarations: [
        AppComponent,
      ],
      imports: [
        BrowserModule.withServerTransition({ appId: 'my-universal-app'}),
        ...
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
            
in the app folder add a new file app.server.module.ts as shown below
the AppServerModule should import your AppModule followed by the ServerModule from @angular/platform-server
the ModuleMapLoaderModule is needed to have lazy-loaded routes work
the bootstrapped component is not inherited from the imported AppModule, it needs to be repeated here
module used for server-side rendering
    import {NgModule} from '@angular/core';
    import {ServerModule} from '@angular/platform-server';
    import {ModuleMapLoaderModule} from '@nguniversal/module-map-ngfactory-loader';

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

    @NgModule({
      imports: [
        AppModule,
        ServerModule, 
        ModuleMapLoaderModule 
      ],
      bootstrap: [AppComponent],
    })
    export class AppServerModule {}
            
in the src folder add a new file named main.server.ts
    import { enableProdMode } from '@angular/core';
    export { AppServerModule } from './app/app.server.module';

    enableProdMode();        
       

Top

Index

working on tsconfig configurations
in the video the changes described to tsconfig.json and the new tsconfig.app.json were made by the CLI when the project was created
in the src folder create a file named tsconfig.server.json
copy content of tsconfig.app.json and paste into tsconfig.server.json
under compiler options change the module property's value to commonjs
add the angularCompilerOptions property and set the child entry module property to the <path>#<class name>
this tells angular where to look for server-side rendering
    {
        "extends": "../tsconfig.json",
        "compilerOptions": {
          "outDir": "../out-tsc/app",
          "baseUrl": "./",
          "module": "commonjs",
          "types": []
        },
        "exclude": [
          "test.ts",
          "**/*.spec.ts"
        ],
        "angularCompilerOptions": {
            "entryModule": "app/aap.server.module#AppServerModule"
        }
    }
             

Top

Index

handling SSR (server-side rendering) as a new app (in .angular-cli.json)
in angular-cli.json and a second element to the apps property of the json data
the element is for the app which actually does the SSR
    {
      "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
      "project": {
        "name": "course-project"
      },
      "apps": [
        {
          "root": "src",
          "outDir": "dist",
          "assets": [
            "assets",
            "favicon.ico"
          ],
          "index": "index.html",
          "main": "main.ts",
          "polyfills": "polyfills.ts",
          "test": "test.ts",
          "tsconfig": "tsconfig.app.json",
          "testTsconfig": "tsconfig.spec.json",
          "prefix": "app",
          "styles": [
            "styles.css",
            "../node_modules/bootstrap/dist/css/bootstrap.min.css"
          ],
          "scripts": [],
          "environmentSource": "environments/environment.ts",
          "environments": {
            "dev": "environments/environment.ts",
            "prod": "environments/environment.prod.ts"
          }
        },
        { "name": "universal", "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.css", "../node_modules/bootstrap/dist/css/bootstrap.min.css"
            ], "scripts": [], "environmentSource": "environments/environment.ts", "environments":
            { "dev": "environments/environment.ts", "prod": "environments/environment.prod.ts"
            }
      ],
      "e2e": {
        "protractor": {
          "config": "./protractor.conf.js"
        }
      },
      "lint": [
        {
          "project": "src/tsconfig.app.json",
          "exclude": "**/node_modules/**"
        },
        {
          "project": "src/tsconfig.spec.json",
          "exclude": "**/node_modules/**"
        },
        {
          "project": "e2e/tsconfig.e2e.json",
          "exclude": "**/node_modules/**"
        }
      ],
      "test": {
        "karma": {
          "config": "./karma.conf.js"
        }
      },
      "defaults": {
        "styleExt": "css",
        "component": {}
      }
    }
    
command to build the client
    ng build --prod
            
command to buid the server
    ng build --prod --app 1 --output-hashing=false
            
an alternative to these commands is to add a script to build them both at the same time
add the script package.json
    {
      "name": "course-project",
      "version": "0.0.0",
      "license": "MIT",
      "scripts": {
        "ng": "ng",
        "start": "ng serve",
        "build": "ng build",
            "build:ssr": "ng build -- prod && ng build --prod --app 1 --output-hashing=none",
        "test": "ng test",
        "lint": "ng lint",
        "e2e": "ng e2e"
      },
      "private": true,
      "dependencies": {
        ...
      },
      "devDependencies": {
        ...
      }
    }
              
to execute the script use the command
    npm run build:ssr
                          

Top

Index

build a node.js server, node express server
permits running server-side Javascript unlike most servers
run the following commands
    npm install --save express
            
and
    npm install --save @nguniversal/express-engine
            
and
    npm install --save @nguniversal/module-map-ngfactory-loader
            
create new file in root named server.js
    'use strict';

    require('zone.js/dist/zone-node');
    require('reflect-metadata');

    const express = require('express');
    const ngUniversal = require('@nguniversal/express-engine');
    const { provideModuleMap } = require('@nguniversal/module-map-ngfactory-loader');

    const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist-server/main.bundle');


    function angularRouter(req, res) {
      res.render('index', {req, res});
    }

    const app = express();

    app.engine('html', ngUniversal.ngExpressEngine({
      bootstrap: AppServerModuleNgFactory,
      providers: [
        provideModuleMap(LAZY_MODULE_MAP)
      ]
    }));
    app.set('view engine', 'html');
    app.set('views', 'dist');

    app.get('/', angularRouter);

    app.use(express.static(`${__dirname}/dist`));

    app.get('*', angularRouter);

    app.listen(3000, () => {
      console.log('Listening on port 3000');
    });
            
to run the server use the command
    node server.js
            

Top

Index

to deploy the project these are needed
  • server.js
  • package.json
  • dist folder
  • dist-server folder

Top

Index

n4jvp.com