Quickstart : Pre-rendering with Angular 2 Universal

You have Angular 2 based webapp hosted on Firebase or GitHub pages or any other static hosting provider and you would like to make sure that first view of your app is almost instant then this article is for you. This article is intended to be ‘Hello World’ of Angular 2 Universal pre-rendering solution. Getting server side rendering done right for most production apps is more complex than ‘Hello World’ example we show here. In the later half of this post, we will talk about some of those complexities.

If you came here looking for re-rendering solution then you can jump to Things to consider section.

Lets see how Angular Universal describes two different use cases,

Pre-render — Generating static HTML at build time that can be deployed to CDN or static web host
Re-render — Run application code on the server with each request to generate the server view on the fly.

‘Hello World’ with Angular 2 Universal Pre-rendering

Lets create ‘Hello World’ component and module. Instead of BrowserModule we are going to use NodeModule. Your code should look like below. This is the isomorphic part of Angular Universal. Following code looks similar(iso morphic) to what you would write for browser except for the change of module.

/*********** App component and module *****************/
import { NgModule, Component } from '@angular/core';
import { NodeModule } from 'angular2-platform-node'
@Component({
selector: 'app',
template: `<h1>Hello World from server</h1>`
})
export class App {}
@NgModule({    
bootstrap: [App],
declarations: [App],
imports: [
NodeModule
],
entryComponents : [
App
]
})
export class MainModule {}

Now lets see how we can write it in universal form, code which is same on server and browser side.

/*********** App component and module *****************/
import { NgModule, Component } from '@angular/core';
import { UniversalModule } from 'angular2-universal'
@Component({
selector: 'app',
template: `<h1>Hello World from server</h1>`
})
export class App {}
@NgModule({    
bootstrap: [App],
declarations: [App],
imports: [
UniversalModule
],
entryComponents : [
App
]
})
export class MainModule {}

Instead of BrowserModule or NodeModule we are importing UniversalModule now. Based on environment, whether it is node or browser it is going to resolve to BrowserModule or NodeModule. Cool!

OK, that was good enough for introduction. Lets get this code into form where you can use it in browser and node without seeing errors.

We will need zone.js. Angular Universal provides utility package where it imports appropriate dependencies based on environment.

import 'angular2-universal-polyfills';

Our module is all set. Lets serialize the module. This is where things start to differ between node and browser environment.

/************ Serialize module  *********************/
import { platformUniversalDynamic } from 'angular2-universal';
var platformRef : any = platformUniversalDynamic([]);
declare var Zone: any;
const zone = Zone.current.fork({
name: 'UNIVERSAL prerender',
properties: {
document: `
<html><head></head><body><app>Loading...</app></body></html>
`
}
});
zone.run(() => (platformRef.serializeModule(MainModule, {
}))
.then((html) => {
console.log(html);
})); // zone.run

If all dependencies are set correctly then with above snippets we should have Angular 2 running in node environment. Here is output on console.

<html><head><title></title></head><body><app><h1>Hello World from server</h1></app><universal-script><script>
try {window.UNIVERSAL_CACHE = ({"APP_ID":"fbb2"}) || {};} catch(e) { console.warn("Angular Universal: There was a problem parsing data from the server")}
</script></universal-script></body></html>

There are couple of things to notice. UNIVERSAL_CACHE and APP_ID. Angular 2 Universal provides with few helpers to reuse the styles and some state data using these helps.

Things to consider

Objects such as window, document or functions such as requestAnimationFrame are not defined in node environment.

If you are using library/framework which provides number of components then its pretty likely that you would face this issue. In future libraries/frameworks will get compatible with server side rendering. Angular Material with some limitations does work with server side rendering so is the case for NG Bootstrap. These two libraries are good but I think one of the most important Angular 2 framework is Ionic which provides the most comprehensive set of components. At the time of publishing this post Ionic doesn’t support server side rendering but they do seem to have something planned.

Universal project also has proxy document helper which can abstract document object in node environment. In future we can expect better support for handling global objects by just using universal project.

Preboot event handling

Assume your server side rendered view is seen by user and user starts clicking/tapping expecting to see something should happen but nothing happens. This problem is particularly severe if client view takes sufficiently long time to take over. There is preboot library which helps addressing some of these situations.

Flicker effect when client view is taking over

When the client view is taking over it can cause flicker

One solution to reduce complexity of above problems is using App Shell. With App Shell concept you would not have to worry about user clicking/tapping parts of the UI which results in state changes in application. Also number of components that need to be supported and rendered on server are reduced significantly. Solution provided by mobile-toolkit does seem to be on these lines. mobile-toolkit has directives shellRender and shellNoRender which seems much more developer friendly solution.