Lukas Marx
June 22, 2017
Angular

Common mistakes with Angular Universal

Angular Universal can be quite tricky sometimes. Using the wrong statement at the wrong place can render your application useless.

The good news is, these mistakes can be avoided if you know them beforehand. I have done them. You don't need to!

In this tutorial, we will take a closer look, what we need to do, to keep our angular universal applications compatible with server-side rendering.

I will tell you about all the mistakes I've made, so you can avoid them.

In doing that, I will show you, how to manipulate the DOM in a save and universal friendly way. We will also take a look at angular's rendering API.

So let's get started!

angular-ssr

What is Server-Side Rendering?

When using server-side rendering, your angular application is run on a node.js server. Once your application is fully loaded, the server takes the html, that is generated by your app and presents it to the client.   The framework is smart enough, to know when your application is done.

For example, the renderer waits with its task, until your HTTP requests have returned, before delivering the content. That way it ensures, that the content is included in the rendered version of your app.

Under The Hood

Angular is embedding all your html templates in your JavaScript code.

This is why, if you take a look at the dist folder, you can't find any other HTML files than the index.html.

If you are using ahead of time compilation, which is required for using server-side rendering, angular creates ngFactory files, which contain your template.

But instead of HTML, they contain pure JavaScript, that creates the corresponding DOM elements at runtime. That way, the component generation can be much faster, since there is no HTML that has to be parsed. You can generate ngFactory files by using the Angular Compiler (ngc).

Since these JavaScript files contain all view information about your components, than can be used to generate HTML files, independent of the runtime environment. When using server-side-rendering, the renderModuleFactory is used to get the static HTML. For that, it requires the Module factory of your apps root module. Then generated HTML files are then served to the client.

But enough background information for now. So, what do you need to do, to make your app compatible with server-side rendering and what could go wrong?

angular-healing-banner

There is no DOM

The biggest pitfall might be, that there is no (full) DOM implementation in a node.js environment. That means, that all methods of direct DOM manipulation, e.g. setting the class of an element, become unavailable to you. Since direct DOM access is not recommended in angular anyway, you are probably fine with that, if you stay within the boundaries of the framework.

No JQuery for you, Sir!

Only because it is not recommended, does not mean that there are no cases where direct access to the DOM comes in handy. In that case people tend to fall back to old, proven frameworks like JQuery.

But here comes the bad news: JQuery is directly accessing the DOM, which means that it is not working on the server.

Even if you only use the ways of manipulating the DOM, angular provides, it does not mean that other people also do that…

angular-error-banner

Be careful with your libraries

Unfortunately, there are many libraries out there, that use direct DOM manipulation. This is because server-side rendering was not a first-class citizen from the beginning of Angular.

It started as a separate project called Angular Universal, that recently got added to the main framework. Since version 4, angular supports server-side rendering out of the box. Now, the authors of the libraries need some time to react to this change and add support for server-side rendering.

So, if you decide to use server-side rendering, in any form, in your application, you have to make sure, that you only use (component) libraries that support the technology. Also, make sure to test server-side rendering right from the beginning.

Otherwise you are maybe surprised, as it is not obvious with most libraries, if rendering on the server can cause issues.

Of course, that advice only applies to libraries which offer either components or directives. Libraries, that only contain business logic, should work fine anyways.

angular-build-banner

How to manipulate the Dom indirectly

So, if you can’t manipulate the DOM directly, how are you supposed to create some fancy components? Don’t worry, angular provides some ways to solve that problem, by creating an abstraction on top of the, browser only, DOM API. Here are some examples.

ngClass & ngStyle

These two directives (ngClass, ngStyle) allow you to influence the appearance of your element by either binding an elements style to a JavaScript variable or applying classes to the element, based on JavaScript conditions. They are very simple to use and I would consider them as the fastest and easiest way shown here, to get your dynamic component styling to the server. However, they are also limited to what their names imply.

Manipulating the style and the class of an element. Other modifications are not possible. You can learn how these directives work at this great article: https://scotch.io/tutorials/angular-2-classes-with-ngclass-and-ngstyle.

ngIf

The ngIf directive allows you to add or remove DOM elements. Other than setting the css "display" to hidden, this directive fully removes the element and all its children from the output HTML, shrinking its size, as well. However, you need to declare these elements in the template at design time. Dynamically adding or removing unknown elements is not possible with this method.

undefined
<div *ngIf="post != null"></div>

This div will only be shown when the variable post is not null.

Renderer

If you need to do something fully dynamic, like creating a bunch of DOM-Elements at runtime, the renderer service is for you. It is an abstraction on top of the DOM-API and has a similar feeling to it, but is designed to run on the server, as well as in the browser. However, as Angular is evolving quite quickly, the original renderer is already deprecated, while the follow-up, with the odd name renderer2, is still in experimental phase (not anymore since 4.2).
Here is what the API looks like.

undefined
class Renderer2 {
  data: { [key: string]: any }
  destroy(): void
  createElement(name: string, namespace?: string | null): any
  createComment(value: string): any
  createText(value: string): any
  destroyNode: ((node: any) => void) | null
  appendChild(parent: any, newChild: any): void
  insertBefore(parent: any, newChild: any, refChild: any): void
  removeChild(parent: any, oldChild: any): void
  selectRootElement(selectorOrNode: string | any): any
  parentNode(node: any): any
  nextSibling(node: any): any
  setAttribute(
    el: any,
    name: string,
    value: string,
    namespace?: string | null
  ): void
  removeAttribute(el: any, name: string, namespace?: string | null): void
  addClass(el: any, name: string): void
  removeClass(el: any, name: string): void
  setStyle(
    el: any,
    style: string,
    value: any,
    flags?: RendererStyleFlags2
  ): void
  removeStyle(el: any, style: string, flags?: RendererStyleFlags2): void
  setProperty(el: any, name: string, value: any): void
  setValue(node: any, value: string): void
  listen(
    target: 'window' | 'document' | 'body' | any,
    eventName: string,
    callback: (event: any) => boolean | void
  ): () => void
}

CreateElement? Sounds familiar right? The renderer is a very powerful service, that is capable of any DOM-Operation you can think of. It is even possible to register Javascript event listener. To use the renderer, you can demand it via dependency injection. Here is an example:

First, we need an element, we can add the newly generated DOM elements to. There always has to be a root element. For that, just create a div container in the view template and assign it to an id, so we can access it via @ViewChild().

example.component.html
<div #content class="renderer"></div>

Next, we import ViewChild from @angular/core and declare it in out component.

example.component.ts
@ViewChild('content') content;

To get a reference of the renderer2, we request it via dependency injection in the component's constructor.

@import { Renderer2 } from '@angular/core'
 
constructor(private renderer: Renderer2){}

Afterward, we can use it as the root element for the elements created by the renderer. Here is an example of creating a simple p element. Also, you need to make sure, that the view child already exists. Because of that, we use ngAfterViewInit in this example.

example.component.ts
ngAfterViewInit(){
  var p = this.renderer.createElement('p');
  var text = this.renderer.createText('test');
  // Append the text to the new p element
  this.renderer.appendChild(p, text);
  // Add the p element to the root element #content
  this.renderer.appendChild(this.content.nativeElement, p);
}

The result should look like something like this:

Result
<div #content class="renderer"><p>test</p></div>

angular-performance-banner

No Javascript at all

If you decide to use the generated HTML on its own, without e.g. preboot to load the angular framework in the background, it is also important to know, that the generated HTML does only contain inline CSS and HTML, but does not contain any Javascript.

You can work around this to some degree, but adding a script tag to the header of your index.html file. Then the script will be included in the final result. If you need more flexibility, you can also generate script tags with the renderer discussed above. As the renderer can create any element, even non-standard tags, you can use it to create inline scripts like that:

this.renderer.createElement('script',  this.renderer.createText("alert('test')"));

Those scripts are then also included in the rendering result.

angular-explore-banner

Routing

Although there is no Javascript available, your static routing will stay intact, as long as you meet the following conditions:

Define your links with the [routerLink] directive The element, you attach the [routerLink] to, has to be an HTML link ('a') element

But, of course, there also some limitations. For example, you can't change the active route via Javascript, since that is unavailable.

Conclusion

Depending on the use case of your application, it can be quite some work to enable server-side rendering support. But in my opinion, it is a great feature, that is definitely worth the effort.

If you want to read more about server-side rendering with angular, please check out my angular universal guide, which contains everything to get you started!

If you liked this article, please share it.

Have a nice day and happy coding.


Leave a comment

We save your email address, your name and your profile picture on our servers when you sing in. Read more in our Privacy Policy.