Angular Universal Render and Render Again

Angular Universal is a engineering which allows you to create Angular apps that return both in the browser and on a Node.js server. This is useful for many reasons, the most of import of which is generally considered to be SEO - universal rendering allows your application to exist indexable by search engines such as Google.

While for the most part your Angular application should run on the server exactly equally information technology does in the browser, there are a few gotchas, and a few adaptions you lot must make to your codebase for it to run smoothly as an Angular Universal app.

In this article I will address these gotchas, and hash out how y'all can work effectually them.

If afterward reading this article you would like to learn more nearly Angular Universal, the post-obit Udemy course has a dedicated section on it, equally well as covering many other important Angular concepts (affiliate link):

Angular - The Consummate Guide (2020 Edition)

1) Avoid referencing window, document and other DOM specific globals

These globals include window, certificate, localStorage, indexedDB, setTimeout and setInterval.

Node.js does non have a DOM api and is as well missing some of the other apis which are bachelor equally global variables in the browser, and then any reference to these globals volition throw an error - 'window is non defined', 'document is not defined' etc… In the case of setTimout and setInterval, you volition not become an mistake as these globals exist in Node, simply practise have a slightly different api.

Specifically for server side rendering, we just demand to avoid referencing these globals in code which runs during the first render of a page. eg. accessing window would not cause a problem inside of a click handler as this code would only ever run in the browser.

1 approach to solve this trouble is to apply the isPlatformBrowser and isPlatformServer functions, which Angular provides in gild to allow you to run code only in the specified surroundings.

          import { DOCUMENT } from '@angular/common'; import { Component, PLATFORM_ID, Inject } from '@angular/core'; import { isPlatformBrowser, isPlatformServer } from '@angular/common';  @Component({      ...  }) export course MyComponent {      constructor(         @Inject(DOCUMENT) individual certificate: Document,         @Inject(PLATFORM_ID) private platformId: any,         windowRefService: WindowRefService,     ) {}      ngOnInit() {         this.scrollToTop();     }      scrollToTop() {         if (isPlatformBrowser(this.platformId)) {             this.windowRefService.nativeWindow.scrollTo(0);         }     } }                  

Annotation that in the above, we are not really working directly with the global values document and window. Instead, we inject them via Angular'due south dependency injection - this keeps our code decoupled and testable, and gives united states the choice to after inject different values for these Injectables based on the environment.

certificate is injectable via the Document Injection Token which is part of Angular. In Node, this will inject a domino implementation of document.

To inject window in this way we must implement something ourselves past creating a WindowRefService:

          /*  * This code snippet is based on  * https://juristr.com/blog/2016/09/ng2-get-window-ref/  */ import { Injectable } from '@athwart/core';  function getWindow (): whatsoever {     render window; }  @Injectable({     providedIn: 'root', }) export form WindowRefService {     get nativeWindow (): Window {         return getWindow();     } }                  

This strategy will work for code which we write ourselves, but what if nosotros are using a third political party library which accesses window, document or other DOM globals?

JavaScript within a third party library may throw errors due to attempting to admission these globals during our server render, but we have no command over the library'southward code. In this case we can install a library such as domino, and then create a shim for the window and document objects in the server.

          npm install domino                  

In our server.ts file.

          const domino = require('domino'); const fs = crave('fs'); const path = require('path');  // Use the browser index.html as template for the mock window const template = fs.readFileSync(path.join(__dirname, '.', 'dist', 'alphabetize.html')).toString();  // Shim for the global window and certificate objects. const window = domino.createWindow(template); global['window'] = window; global['document'] = window.document;                  

These shims will permit our 3rd party library to access the window or certificate globals without causing any errors.

A full example of a server.ts file which uses this approach tin can exist found here.

two) Avoid manipulating the DOM via nativeElement (Angular < 6.i.0 just).

We often need to manipulate the DOM directly in some way, and in Angular we are able to manipulate a native DOM element using the nativeElement belongings of an ElementRef.

nativeElement exposes a HTML element from the DOM via the HTMLElement interface. But as this is function of the native DOM api, it does not exist in Node.

In Angular >= half dozen.one.0, Athwart Universal uses domino as an implementation of the DOM in Node, and the nativeElement property of ElementRef exposes the domino implementation of HTMLElement. This means that we can now manipulate the DOM directly - in the browser nativeElement will give us a reference to the native browser DOM implementation of HTMLElement, while in Node it will give us a reference to a domino implementation of HTMLElement.

However, prior to Angular 6.1.0, lawmaking such as the following won't work in a Universal app, as nativeElement will be undefined on a Node server.

          ngOnInit() {     this.elementRef.nativeElement.classList.add("my-form"); }                  

We could wrap this in an isPlatformBrowser provisional, only things start to get messy if nosotros have too many of these conditionals nosotros have in our codebase.

We should instead use the Renderer2 service for DOM manipulation:

          constructor(private renderer: Renderer2) {}  ngOnInit() {     this.renderer.addClass(this.elementRef.nativeElement, 'my-class'); }                  

Although the api is rather different, all of the DOM manipulations which nosotros would usually practise using the native DOM api tin be done using Renderer2 - some good examples of this tin can be plant here.

3) Exist aware of retentivity direction techniques - memory leaks are a showstopper on a Node server.

Whether you are edifice an Angular application as a single page application or a Universal app, you should ever accept steps to avert memory leaks.

However, when your application runs as an SPA, unless there is a huge memory leak, or the user is running the app for an extremely long fourth dimension without refreshing the page, it is likely that the memory leak will go unnoticed by users.

The aforementioned is non true of an Athwart app runnning on a Node server - your Node server lawmaking and your Angular application lawmaking all run in the aforementioned environs and share the aforementioned retention. Each time a request is fabricated, the server will bootstrap an instance of your Angular app, render the requested folio and and so make clean itself up. The memory used past the application can then exist freed up when garbage collection runs.

If you have a memory leak of some kind, and then each request may go out something backside in retentiveness which cannot be cleaned up, significant that the memory contour of your app volition slowly increase over time, something like the below.

Memory Profile

This will eventually effect in your Node server running out of retentivity.

The main thing to exercise to avoid this issue is to follow best practices for memory management in JavaScript, as well every bit Angular memory management best practices such as unsubscribing from Observables where necessary.

If you find yourself needing to profile retention usage in Node.js y'all can apply the Chrome debug tools to do this, or use a memory monitoring tool such as node-memwatch.

4) Avoid ho-hum http requests and long running asynchronous operations during the initial page load where possible.

When rendering on the server, Angular volition keep rail of certain asynchronous operations such every bit http requests made using HttpClient, and wait for them to complete before rendering the page and serving upwardly index.html.

What are the consequences of this?

In a browser rendered app, if a http request which loads data for some ui element is dull but the rest of the data for the page is available, then the page tin notwithstanding (mostly) be rendered.

When a page renders on the server in a Universal app, a http request which takes a long period of time volition block the server render until information technology receives a response, and increase the load time of the page (ie. the time before the user tin brainstorm seeing and interacting with some elements of the folio).

You should take the to a higher place into consideration when designing your application - if a http request is slow and is blocking the server return of a page, could that request be deferred from the showtime render and made after the user clicks a push button or interacts with the folio in some way? Could the page be cached by the server if it's content doesn't update often?

Decision

Nosotros have been through what I have found over the terminal year or so of working with Angular Universal to be the biggest gotchas. Past making a few adaptions to the way you build your application, it volition be robust enough to run smoothly in both browser and server environments.

Some resources that helped me to write this article, and that you lot may also find helpful, are given below.

Helpful Resources

I highly recommend the post-obit Udemy course which has an fantabulous department on Athwart Universal, likewise every bit covering many other Angular concepts (chapter link):

Athwart - The Consummate Guide (2020 Edition)

In add-on, the post-obit were really helpful for writing this commodity, and I very much recommend them as farther reading:

  • https://github.com/angular/universal/hulk/principal/docs/gotchas.md
  • https://github.com/angular/universal-starter
  • https://medium.com/@MarkPieszak/angular-universal-server-side-rendering-deep-dive-dc442a6be7b7

krousemorry1939.blogspot.com

Source: https://www.willtaylor.blog/angular-universal-gotchas/

0 Response to "Angular Universal Render and Render Again"

Postar um comentário

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel