Angular Universal For The Rest Of Us

Hello you, Web developer,

Let me guess. You have been working on your Web project for months, which is most likely a Web application and more specifically, a “Single Page Application”. But now comes the time for your application to be shipped and published to millions of users and … Search engines. Yep! In order for your application to be successful, it has to be indexed by search engines. So now you’re starting to wonder: damn! I need to add SEO support!


In your journey, you are starting to realize that this is not going to be an easy task. Because we all know how hard is it to setup a whole pipeline for server side rendering support of an SPA. But hey, you’re a developer and you like challenges, right?

After a couple of days (or weeks?) of trials and errors, you managed to add SEO support. Congratulations!

However, your users are starting to complain about the startup time of your application. Let’s be honest, 6 or 10 seconds to display the first page is not an ideal experience. Right? We, humans, are not patient. When I visit a website, I need it to show me what I came looking for almost instantly; I can wait for 3 seconds but definitely not 10! Life is too short to wait staring at an empty page with a spinner, for 10 seconds.

So now you have one more issue to solve and it is not one of the easiest ones. Besides, your other users — the Web crawlers — report that your application is not “social media friendly”. What this means is that when a user shares a link of your application on social medias, the generated preview of your application is kind of “broken”: it shows a blank preview or, in the best case, only some parts of the page. This is an expected result, because at the end of the day, the social media backend generates a screenshot of what gets rendered when it requests your application.

So now you have two issues to solve. You still like challenges, right? However, your PM usually tells you that time is money and these issues must be fixed for… well… yesterday!?

What if I tell you that you can avoid these kind of issues? These are exactly the kind of tasks that Angular Universal helps you solve. If you are using Angular, of course.

Yeah! That is right. Angular has a native support for server side rendering thanks to the official Universal module.

Let’s take a look at Universal, and see how it works. But most importantly, how Universal can help you ship SEO-instant-rendered-social-media friendly Angular applications.


What is Universal?

The official site states that Universal is: Server-side Rendering for Angular apps. However, here is how I like to define Angular Universal:

Universal is Angular for the Headless Web.

You do not need a browser container anymore, aka a WebView, to run Angular. Since it is not tied to the DOM, Angular can run anywhere, where there is a JavaScript runtime.

I shamefully borrowed the term “Headless Web” from Paul Kinlan. I strongly recommend reading his blog post where he gives his vision about what the Web should be in the future.

The big picture

Before using Universal, let’s take a look at the overall architecture.

Angular Universal High Level Architecture

This diagram illustrates the ability of Universal to run your typical Angular Web application outside the browser. Obviously we need a JavaScript runtime, that is why we support Node.js (which is powered by the V8 engine) by default. However, some efforts have started to support other server side technologies such as PHP, Java, Python, Go … and I’m happy to say that Universal already works on the .Net stack thanks to Steve’s work.

Now that your application can be interpreted outside the browser —let’s take a server as an example— the client which requested your SPA will receive a static fully rendered page of the requested route/URL. This page contains all the related resources, i.e. images, stylesheets, fonts… even data coming through your Angular service.

In fact, Universal takes care of handling and fetching all required data before rendering the page— that you usually fetch with Angular’s Http service. Universal is capable of rewiring some of the default Angular’s providers so they can work on the target platform. There is more. When the client receives the rendered page, it also receives the original Angular application — we only help your application loads almost instantly. Once loaded, Angular takes care of the rest.

But what happens when Angular bootstraps over the rendered page? Won’t we have some sort of states issues? Or maybe the user would want to use your application just after they see the rendered page — which makes sense? How would you then handle state? How does Universal itself handle state?

We got you covered…

In fact, Universal comes bundled with the Preboot.js library which sole role is to make sure of that both states are synced.

What Preboot.js does under the hood is simply and intelligently record the events that occur before Angular bootstraps; and play them back after Angular has completed loading. Simple, isn’t it?

Renderers

Universal is made possible thanks to Angular’s rendering abstractions. In fact, when you write your application code, that logic gets parsed into an AST by Angular’s compiler — we are really simplifying things here . That AST is then consumed by the Angular’s rendering layer, which uses an abstract renderer that is not tied to the DOM. Angular allows you to use different renderers. By default Angular ships the DOMRenderer so your application can be rendered in a browser, which is probably 95% of the use cases.

And that’s where Universal comes in. Universal comes with bunch of prerenderers, for all the mainstream technologies and build tools.

Dependency Injection and Providers

Another area where Angular shines in is with its DI system. Angular is in fact the only front-end framework that implements this design pattern which allows to accomplish so many great tasks easily (see IOC). Thanks to DI you could for instance swap two different implementations at run time, which is heavily used in testing.

In Universal, we take advantage of this DI system and provide you with many services that are specific to the targeted platform. For Node, we provide a custom HTTP_PROVIDERS that implements Node’s server requests rather than Browser’s XHR. Universal is also shipped with a custom renderer specific to Node, and of course we provide you with a bunch of prerenderers — as we call them — such as Express renderer or Webpack renderer for your Node backend technologies. For other non-JavaScript technologies, such as .NetCore or Java, you should expect other prerenderers as well.


Hello World

The good news is that a Universal application is no different from a classic Angular application. Usually your application logic remains the same, literally. Of course, this would be true if you strictly follow Angular’s guidelines and best practices. One of THE MOST IMPORTANT best practices I’d recommend you to follow is:

Do Not Ever Never Touch The DOM directly. Make sure to use the Angular Renderer or rendering abstraction every time you want to interact with the browser’s DOM.

The, the only extra thing you need to provide is a second bootstrapping configuration. A configuration for your target platform or backend, e.g. Node, .Net…

The following bootstrapping structure is what is recommended by Universal:

Angular Universal Application Structure

Let’s explain the role of each file…

app.component.ts

This component and all other Angular API/features such as the component template, Directives, Services, Pipes…etc remain the same. Nothing fancy:

import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app works!';
}

app.browser.module.ts

In this module, you should put everything specific to the browser environment. For Universal, you would import the UniversalModule from the angular2-universal/browser package. This module contains a few Universal providers for the browser:

import { NgModule } from '@angular/core';
import { UniversalModule } from 'angular2-universal/browser';
import { AppComponent } from './index';
@NgModule({
bootstrap: [ AppComponent ],
declarations: [ AppComponent ],
imports: [
/**
* NOTE: Needs to be your first import (!)
* NodeModule, NodeHttpModule, NodeJsonpModule are included
*/
UniversalModule,
...
]
})
export class AppModule {}

app.node.module.ts

This module is dedicated to your server environment. The UniversalModule provide a set of providers from the angular2-platform-node package. Things like NodeDOMRender, NodeHttpModule…etc:

import { NgModule } from '@angular/core';
import { UniversalModule } from 'angular2-universal/node';
import { AppComponent } from './index';
@NgModule({
bootstrap: [ AppComponent ],
declarations: [ AppComponent ],
imports: [
/**
* NOTE: Needs to be your first import (!)
* NodeModule, NodeHttpModule, NodeJsonpModule are included
*/
UniversalModule,
...
]
})
export class AppModule {}
Please note the “angular2-universal” namespace. It may change in the future to something under the “@angular” organisation in NPM.

client.ts

This file is responsible of bootstrapping your application on the client. Please note the use of platformUniversalDynamic which basically abstract away the creation of a factory for your platform. We usually used to call platformBrowserDynamic for that but here we are trying to be more Universal:

// the polyfills must be the first thing imported
import 'angular2-universal-polyfills';
import { platformUniversalDynamic } from 'angular2-universal/browser';
import { AppModule } from './app/app.browser.module';
const platformRef = platformUniversalDynamic();
// bootstrap Angular on document ready
document.addEventListener('DOMContentLoaded', () => {
platformRef.bootstrapModule(AppModule);
});

server.ts

This file is really specific to your server/backend environment. Here, we are targeting Node.js and more precisely the Express framework to take care of handling all client requests and the rendering process. For that, we are using and registering the createEngine which represents the Angular Universal rendering engine for Express:

// the polyfills must be the first thing imported
import 'angular2-universal-polyfills';
import * as path from 'path';
import * as express from 'express';
import { createEngine } from 'angular2-express-engine';
import { AppModule } from './app/app.node.module';
const app = express();
// Express View
app.engine('.html', createEngine({}));
//...
// bootstrap universal app
function ngApp(req: any, res: any) {
res.render('index', {
req,
res,
ngModule: AppModule,
preboot: false,
baseUrl: '/',
requestUrl: req.originalUrl,
originUrl: req.hostname
});
}
// use universal for specific routes
app.get('/', ngApp);
app.get('/about', ngApp);
app.get('/about/*', ngApp);

Since you have total control over the server rendered content, you can easily add any SEO support you would like. We can imagine adding the following code:

<title>{{SEO.title}}</title>
<link rel="canonical" [attr.href]="SEO.canonical">
<meta name="robots" [attr.content]="SEO.robots">
<meta name="description" [attr.content]="SEO.description">
<meta name="keywords" [attr.content]="SEO.keywords">

You can also add any other Social Medias Tags or even Open Graph Tags.

This information could come from any data source, such an API, and be stored in a model for instance:

// SEO model
export class SEO {
public title: string = '';
public canonical: string = '';
public robots: string = '';
public description: string = '';
public keywords: string = '';
}
Note: The code sample above is here for illustration purposes only. Don’t except it to work with the final version of Angular. It used to work before the introduction of NgModules in Angular2 RC5. I’ll update this blog post when a solution is ready.

So now, you may be wondering: Oh! boy, that’s a lot of boilerplate! Well, we’ll help you with that. Let’s talk about the tooling…


Tooling

Angular Universal CLI support is currently in a Fork of the Angular CLI here, but hopefully will become integrated in the regular CLI sometime soon.. But till then, you can get started with Angular Universal quickly and easily by using the Official Angular Universal Starter, maintained by my buddy PatrickJs. If you are a .Net developer, please check this other starter by my bro Mark Pieszak.


That’s It

Dear Web developer, you now have all the necessary information to get started with Angular Universal and make your next Angular Application more performant and SEO friendly and so much more…

Don’t forget to give a huge shoutout for Jeff Whelpley and PatrickJS and also for all of The Universal Project contributors for making Angular Universal rock!

Edit: Don’t get me wrong, Universal is not only about server side rendering or SEO. It gives you so much more capabilities and power when you need to run your application outside the browser. We’ll talk about this in another blog post. Stay tuned!

Follow @manekinekko to learn more about the web platform.