Getting Started with Angular Universal
Overview
Angular Universal builds on Angular‘s widespread adoption and support to provide server-side rendering and faster perceived performance to Angular applications. By pre-rendering an application at build time, or re-rendering at run time, web applications using Angular Universal benefit from better site previews, search engine optimization, and quicker page loads. Unlike traditional server-rendered strategies (JSPs, etc), Angular Universal is more of a hybrid approach. The initial page is rendered by the server, while the client-side (traditional Angular) application is loaded in the background. Once the client-side application is ready, it takes over and does all of the work from there. Therefore Angular Universal benefits from the initial load speeds of server rendering and the user experience of a client-side application. This post will illustrate the basic usages of Angular Universal and its integration with an Angular 2 application.
Getting Started
We will begin with the starter application provided by the angular universal team. The source code can be found here. This application has Angular Universal already setup and ready to go. Prerequisites are git and node.
git clone https://github.com/angular/universal-starter cd universal-starter npm install npm start
Navigate to http://localhost:3000 to see the running application!
Setup Explained
For the bulk of the Angular Universal configuration, visit main.node.ts
// Application import {App} from './app/app.component'; import {routes} from './app/app.routes'; export function ngApp(req, res) { let baseUrl = '/'; let url = req.originalUrl || '/'; let config: ExpressEngineConfig = { directives: [ App ], platformProviders: [ {provide: ORIGIN_URL, useValue: 'http://localhost:3000'}, {provide: APP_BASE_HREF, useValue: baseUrl}, ], providers: [ {provide: REQUEST_URL, useValue: url}, NODE_HTTP_PROVIDERS, provideRouter(routes), NODE_LOCATION_PROVIDERS ], async: true, preboot: false }; res.render('index', config); }
This is the main configuration for Angular Universal in conjunction with Express. The main application component ‘App’ is specified in the directive array. The base url http://localhost:3000 is set to the Origin with the APP_BASE_HREF set to the root of the application. This means that angular universal will take control at the root of the application rather than at a sub-path. The router is set to Angular’s default router with the client-side routes defined in app.routes.ts. Finally, async and preboot are set to true and false respectively. We will touch on their significance later.
Then, visit server.ts to tie the Angular Universal config to the server.
import { expressEngine } from 'angular2-universal'; // Express View app.engine('.html', expressEngine); app.set('views', __dirname); app.set('view engine', 'html');
import { ngApp } from './main.node'; // Routes with html5pushstate // ensure routes match client-side-app app.get('/', ngApp); app.get('/about', ngApp); app.get('/about/*', ngApp); app.get('/home', ngApp); app.get('/home/*', ngApp);
These two snippets from server.ts first load the Angular Universal expressEngine, then setup each of the routes with the configuration file from above. That’s all that is needed to configure Angular Universal into the application!
Page Load
Using the default server-side rendering with Angular Universal, we get the following initial page load of the starter application:
With Angular Universal, the first response will be rendered on the server. Meanwhile, the client-side application will download/bootstrap and take over when it is ready. This means that the user is immediately presented with a usable web app. Therefore, all web-crawlers, social media previews, etc. will see the fully loaded site.
After disabling Angular Universal, we get the following initial page load:
The index.html page, which shows a loading message, is shown while the client-side application downloads and bootstraps. Although a user might know to wait for the application to load, a bot might mistakenly assume that this is the fully-loaded page.
Preboot
Preboot is a toggled feature of Angular Universal. When enabled, Preboot takes over during the time after the server-rendered view is displayed, but before the client-side application is fully bootstrapped. Ideally, the client-side application should handle all of the user interaction with the web application, but for a brief period of time, only the server-rendered view is available. During this time, Preboot will record all of the user’s interactions and play them back to the client-side application when it is ready. This ensures that no interactions with the web app are lost.
Async
Another feature of Angular Universal is async. Using the power of Zone.js, Async keeps track of all the asynchronous events that occur during the server rendering, so that it can serve the page when they are all completed.
Conclusion
Angular Universal can provide better perceived performance for a little bit of added complexity in an Angular application. At this point Universal should only be considered by applications where initial response time or social media sharing are paramount. Angular Universal, along with most Angular 2 tools, is still in its infancy and unfortunately, the current scope of Angular Universal only applies to Angular 2 applications using a Node backend. Overall, it seems like the project is well-maintained and I believe that the documentation and adoption of Universal will continue to grow along with Angular 2.