Angular 9 CLI + Angular Universal Setup Steps

Update as on 15th April 2020:

This Github has the working code for Angular 9 with Express server. I really struggled a lot to make angular universal happen with angular 9 with firebase. But finally with the help of this github link only I was able to make it done. But still with firebase I was facing this issue, so could not find the solution yet. So far time being I have moved all those logics to my backend (Java + Spring Boot) side.

Issue with Angular 9 universal + Firebase:

  1. Error: ENOENT: no such file or directory, open ‘google/protobuf/api.proto’
  2. (node:47408) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)

Angular 4/5/6 CLI + Angular Universal Setup Steps:

Angular universal is superb component for angular projects for server side rendering, which really resolves SEO related issues to the great extend. But setting up a angular universal will become little complex if you try after developing your complete applications. Because some of the component you used in your angular 4 project may not have a proper configuration to support angular universal.

So I highly recommend to run angular universal beginning itself and ensure whenever you add a new third party component run and ensure nothing has been reported by angular universal for the new component added.

1. Create New Angular 4 CLI project

ng new czc-user-universal

Note: Ensure you have angular-cli version greater than 1.3 (in my case it is “@angular/cli”: “1.4.3”, and 1.0.1 it did not work).

If you have 1.0.1 or less version then run the below command,

npm install @angular/cli@latest

2. Update “scripts” in package.json [prestart & start]:

[plain highlight=”6,7″]
{
“name”: “czc-user-universal”,
“version”: “0.0.0”,
“license”: “MIT”,
“scripts”: {
“prestart”: “ng build –prod && ngc”,
“start”: “ts-node src/server.ts”
},
“private”: true,
“dependencies”: {
“@angular/animations”: “^4.2.4”,
“@angular/common”: “^4.2.4”,
“@angular/compiler”: “^4.2.4”,
“@angular/core”: “^4.2.4”,
“@angular/forms”: “^4.2.4”,
“@angular/http”: “^4.2.4”,
“@angular/platform-browser”: “^4.2.4”,
“@angular/platform-browser-dynamic”: “^4.2.4”,
“@angular/router”: “^4.2.4”,
“core-js”: “^2.4.1”,
“rxjs”: “^5.4.2”,
“zone.js”: “^0.8.14”
},
“devDependencies”: {
“@angular/cli”: “1.4.3”,
“@angular/compiler-cli”: “^4.2.4”,
“@angular/language-service”: “^4.2.4”,
“@types/jasmine”: “~2.5.53”,
“@types/jasminewd2”: “~2.0.2”,
“@types/node”: “~6.0.60”,
“codelyzer”: “~3.1.1”,
“jasmine-core”: “~2.6.2”,
“jasmine-spec-reporter”: “~4.1.0”,
“karma”: “~1.7.0”,
“karma-chrome-launcher”: “~2.1.1”,
“karma-cli”: “~1.0.1”,
“karma-coverage-istanbul-reporter”: “^1.2.1”,
“karma-jasmine”: “~1.1.0”,
“karma-jasmine-html-reporter”: “^0.2.2”,
“protractor”: “~5.1.2”,
“ts-node”: “~3.2.0”,
“tslint”: “~5.3.2”,
“typescript”: “~2.3.3”
}
}
[/plain]

3. Add “angularCompilerOptions” to tsconfig.json

[plain]
{
“compileOnSave”: false,
“compilerOptions”: {
“outDir”: “./dist/out-tsc”,
“sourceMap”: true,
“declaration”: false,
“moduleResolution”: “node”,
“emitDecoratorMetadata”: true,
“experimentalDecorators”: true,
“target”: “es5”,
“typeRoots”: [
“node_modules/@types”
],
“lib”: [
“es2017”,
“dom”
]
},
“angularCompilerOptions”:{
“genDir”: “./dist/ngfactory”,
“entryModule”: “./src/app/app.module#AppModule”
}
}
[/plain]

4. Add “server.ts” to “exclude” in tsconfig.app.json:

[plain]
{
“extends”: “../tsconfig.json”,
“compilerOptions”: {
“outDir”: “../out-tsc/app”,
“baseUrl”: “./”,
“module”: “es2015”,
“types”: []
},
“exclude”: [
“server.ts”,
“test.ts”,
“**/*.spec.ts”
]
}
[/plain]

5. Update app.module.ts -BrowserModule.withServerTransition

[typescript]
import { BrowserModule } from ‘@angular/platform-browser’;
import { NgModule } from ‘@angular/core’;
import { AppComponent } from ‘./app.component’;
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule.withServerTransition({appId: ‘czc-user-universal’})
],
providers: [],
bootstrap: [AppComponent]
})

export class AppModule { }
[/typescript]

6. create app.server.module.ts under app folder [New File]:

[typescript]
import { NgModule } from ‘@angular/core’;
import { ServerModule } from ‘@angular/platform-server’;
import { AppModule } from ‘./app.module’;
import { AppComponent } from ‘./app.component’;
@NgModule({
imports: [
ServerModule,
AppModule
],
bootstrap: [AppComponent]
})

export class AppServerModule { }
[/typescript]

Important Note:

If you get

“cannot find module ”@angular/platform-server’.” then you need to install
platform-server as well which can be done using the below command,

npm install –save @angular/platform-server

Also install @angular/animations

npm install –save @angular/animations

(or)

In single command,

npm install –save @angular/platform-server @angular/animations

If you face still some issue then try with @latest

npm install –save @angular/platform-server@latest @angular/animations@latest

7. create a server.ts file under src and paste the following [New]:

[typescript]
import ‘reflect-metadata’;
import ‘zone.js/dist/zone-node’;
import { platformServer, renderModuleFactory } from ‘@angular/platform-server’
import { enableProdMode } from ‘@angular/core’
import { AppServerModuleNgFactory } from ‘../dist/ngfactory/src/app/app.server.module.ngfactory’
import * as express from ‘express’;
import { readFileSync } from ‘fs’;
import { join } from ‘path’;

const PORT = 4000;
enableProdMode();
const app = express();
let template = readFileSync(join(__dirname, ‘..’, ‘dist’, ‘index.html’)).toString();
app.engine(‘html’, (_, options, callback) => {
const opts = { document: template, url: options.req.url };
renderModuleFactory(AppServerModuleNgFactory, opts)
.then(html => callback(null, html));
});
app.set(‘view engine’, ‘html’);
app.set(‘views’, ‘src’)
app.get(‘*.*’, express.static(join(__dirname, ‘..’, ‘dist’)));
app.get(‘*’, (req, res) => {
res.render(‘index’, { req });
});
app.listen(PORT, () => {
console.log(`listening on http://localhost:${PORT}!`);
});
[/typescript]

Success!

You have successfully completed setting up angular universal with angular 4 CLI.

Run the below command,

npm run start

And you will be able to see something similar to this,

Angular 4 CLI + Angular Universal Setup Steps

Now open and run your application at http://localhost:4000, now your app will be appeared from server. So no more SEO and prerendering default app works! text.

You may get the below error sometime,

ERROR { Error: Uncaught (in promise): ReferenceError: System is not defined
ReferenceError: System is not defined

If so, try running the below command because the above error can occur due to router loaders and it resolves after you add this dependency.

npm install angular2-router-loader — save-dev

Leave a Reply