Table of Contents
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:
- Error: ENOENT: no such file or directory, open ‘google/protobuf/api.proto’
- (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,
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