I this post I will show how to use Angular Universal to generate Angular applications on the server.
When I first heard about Angular Universal I thought the idea was really cool. Being able to serve a pre-generated application from the server is great – especially in applications where SEO is important.
Hybrid server side - client side rendering already exists in React, but it's great to see it in Angular as well now. Btw, I have an article about server side React here if you are interested.
So how does server side generation work in Angular?
One great benefit of Angular is that the framework is designed to be agnostic of the runtime environment. Since Angular abstracts the details of the host, the same application can be deployed to very different environments like browsers, express servers or even native devices.
As long as we avoid accessing environment specific functions, our code can be deployed to different environments without modification.
Examples of things to avoid are browser specific apis like direct DOM access since it doesn't have a counter part outside the browser. In cases where you need DOM access it's recommended to use the Renderer abstraction instead.
Using the Angular Universal starter project is probable the easiest way to get started. The starter takes care of all the ceremony of configuring the project and gives you a pretty comprehensive sample as well.
I've decided to use Universal to build a sitemap for my blog based on an Angular treeview component.
The treeview component is simple with a recursive template and some basic data binding.
Next I have defined a sitemap component to handle the http call to get the list of articles needed to build the treeview.
Notice how all the code I've written so far is totally standard Angular code – without any server vs browser specific code.
So how can we get this code to render on the server?
I am using node with express to run my server. My code is a modified version of the server code from the universal starter project.
There is a lot going on here, but much of it might seem vaguely familiar if you have some previous experience with Express. You might also notice some familiar imports from Angular as well, including my sitemap component.
Next we have to set up the client side bootstrapping.
This part is relatively similar to standard client side bootstrapping.
The last step is index.html
Notice the script tag referring to bundle.js. This script tag is what enables us to kick of a client side angular application once the static server side page has rendered in the browser.
Running the code
I have deployed the code here in case you want to take a look.
If you go to my sitemap I recommend looking at the network tab while the page is loading. You will see the initial document served with fully generated html for the sitemap component. This is the result of generating the component on the server.
It's however important to point out that the server side generation is not a replacement for the client side application. In most cases we would need both, which means the components would actually instantiate twice. Once on the server and again when index.html renders in the browser.
In my sample I need the client side application to respond to user clicks to expand and collapse the category nodes.
One downside to instantiating the component twice is that any http call made during initialization would also be made twice. In my sample you can see the call to fetch the sitemap articles is made again from the browser.
I can come up with a few workarounds for short circuiting the http call. I have however decided to wait to see what the recommendations will be from the Universal team. Two workarounds I can think of are
- Embed json in a hidden field and pass it down to the client (essentially view state)
- Include a script tag from the server that leaves behind a global variable that contains the data
Both solutions would probably work, but I think this is something that should be streamlined as part of the framework.
Angular Universal is really cool! I see it mostly as an SEO feature, but I am curious to see if there are scenarios where it can be used to improve performance as well.