Making an Angular 2 App render server side
I’m going to expect familiarity with Angular 2 from here on, so if you’re not a developer, then be aware that here be monsters.
Let’s assume you’ve got an app basically up and running. The stack looks something like node, Express, Angular, gulp/grunt/broccoli/webpack/build chain of choice etc. If not, go to the Angular Universal quickstart guide and follow what it says. That will get you to a basic application which you can use.
The magic here is in Express having something to render beyond just the scaffold for Angular to fill in. With Angular 2 we can render entire pages on the server, with different data inserted based on the page, and therefore solve our no-data headaches. We do this with dependency injection.
Universal Header Template for Angular 2
At the top of your template component you’ll want to import and make available two objects, one called UniversalService, the other UniversalModel. We’ll get to what they look like later. Then in the head area, you’ll want to contain something like the following:
<!-- SEO -->
{{UniversalModel.title}}
<!-- Social -->
<!-- Social: OG -->
<!-- Social: Article -->
<!-- Social: Twitter -->
<!-- JS includes -->
<!-- CSS includes -->
<!-- Favicon -->
You’ll also need to tie the Universal service and model to that component. A basic version of that might look something like this:
export class HeaderTemplateComponent implements OnInit, OnDestroy {
public universalModel: UniversalModel;
private subscriber: EventEmitter;
// Inject UniversalService with @param universalService
constructor(private universalService: UniversalService) {}
// Instantiate an empty model to populate and subscribe to universalService
public ngOnInit() {
this.universalModel = new UniversalModel();
this.subscriber = this.universalService.eventEmitter.subscribe((universalModel) => {
this.universalModel = universalModel;
});
}
// unsubscribe from universalService
public ngOnDestroy() { this.subscriber.unsubscribe() }
}
UniversalService
The first part of then making that template work is a service which allows the data from our back end to pass to components. That’d look like this:
import {Injectable, EventEmitter} from 'angular2/core';
import {UniversalModel} from './universal.model';
// Allow for DI
@Injectable()
export class UniversalService {
public eventEmitter: EventEmitter;
private universalModel: UniversalModel;
// Instatiate all the things
constructor() {
this.eventEmitter = new EventEmitter();
this.universalModel = new UniversalModel();
}
// Populate universalModel and emit an event to notify subscribers that there is now something here
public set(universalModel: UniversalModel): void {
this.universalModel = universalModel;
this.eventEmitter.emit(this.universalModel);
}
}
UniversalModel
The second part is creating a default model for our data:
export class UniversalModel {
public title: string = '';
public description: string = '';
public robots: string = '';
public keywords: string = '';
public canonical: string = '';
public publisher: string = '';
public ogLocale: string = '';
public ogType: string = '';
public ogTitle: string = '';
public ogDescription: string = '';
public ogUrl: string = '';
public ogSite_name: string = '';
public ogUpdatedTime: string = '';
public ogImage: string = '';
public articlePublisher: string = '';
public articleTag: string = '';
public articleTag: string = '';
public articleSection: string = '';
public articlePublishedTime: string = '';
public articleModifiedTime: string = '';
public twitterCard: string = '';
public twitterDescription: string = '';
public twitterTitle: string = '';
public twitterSite: string = '';
public twitterImage: string = '';
public twitterCreator: string = '';
}
Putting it all together
Now when you come to create components for parts of your application, you can use your Universal Header template and inject values to the Universal Model like this:
constructor(private universalService: UniversalService) {
// Set up the data for UniversalModel. Would come from an API in real life. Must be a value set in UniversalModel
let universalModel: UniversalModel = {
title: 'Builtvisible Homepage',
description: 'The home page of Builtvisible, a digital marketing agency',
canonical: 'https://builtvisible.com/',
publisher: 'https://plus.google.com/+Builtvisible'
};
// Set the data for the service from the model
universalService.set(universalModel);
}
Note that you can use the same principle to deal with authentication, checking for a logged in status and rendering different outcomes based on that flag. All you have to do is let the application know the user state, and it can then do different things based on that state value. We can also use this to render different blog posts in a CMS blog template, products in an ecommerce CMS and so on. Thanks to Angular’s adoption of DI, it becomes possible to render any conventional website on the server and deliver the page to the user ready to go.
Thanks to Johannes Werner for his work and inspiration for the code.
Pierre-Yves Poli
Hey, thanks for the very interesting and informative article!
I’m trying to run this code alongside the github Universal repo (https://github.com/angular/universal) – specifically the “todo” example – and unfortunately I’m getting the following error: Type ‘EventEmitter’ is not assignable to ‘EventEmitter’. Type ‘{}’ is not assignable to type ‘UniversalModel’. Property ‘title’ is missing in type ‘{}’.
Any idea where this might be coming from?