Angular Universal

Featured image

Hello, how are you? Today I want to dedicate this post to our friend Angular Universal. If you already worked with Angular Universal and you are thinking to migrate, or if you want to know what it is to decide whether or not to apply it in your app orooooooo if you are making you renegade because they tell you it’s easy but it’s not soooo soooo this post is for you!

enter image description here

What it is

Angular Universal is the technology that allows us to run our Angular apps in server-side Angular, SSR (Server Side Rendering). Yes, you read that right. We are used all our lives (all our lives? what’s wrong with it?) to build and serve applications on the client side (Javascript, with or without your favorite framework). BUT, Angular Universal promises us to build our application and serve it directly from the server.

**How? Well basically it helps us to implement certain features to apply in our app and we can **prerender**, that is, we generate static HTML with all the data ready (for example with the responses of the services already mocketed and other herbs) and that we serve it in one!

**What do we achieve with this? Our application will have high speed in the initial loading of those pages and routes where we apply the prerendering and static HTML. As we are already serving the HTML, we are not waiting for the execution of calls to services, or their responses, no promises or observables, nothing at all, HTML and bye-bye).

**What do we do with dynamic data? You don’t have to worry about that, because once you load the app, you see the Angular Universal prerendering but then in case of an event, ANGULAR **takes full control** and your app continues working as usual (with calls to services, dynamic content, data binding, dynamic loading and rendering, everything normal as usual). You can choose whether to only prerender the index.html of the initial load of your app, and also choose which paths you want to have that prerendering and which not.

Fresh Starter Configuration

enter image description here If you want to build an app from scratch in Angular and put Universal, or if you already have an Angular app but never had Universal, the configuration should be simple and we detail it here. If you are complaining about other migrations, go straight to the next section!

Well let’s start with the initial command

ng add @nguniversal/express-engine

This will generate a few base configuration files of our server to build our SSR app. Once finished, it takes the configuration of our angular cli to configure the server based on our app. To run it we use

npm run dev:ssr

If we open our local http://localhost:4200 we will see our usual app, BUT let’s see if the magic is there, we open the source code (ctrl + u) and check the HTML. If we can see it is that it works, in fact the newest versions of universal add a comment: This page was prerendered with Angular Universal. What is the difference? If you look at the source code of a normal Angular app you will see only the meta tags, scripts, links etc BUT you will see the

<sc-root></sc-root>

where the Angular app rests (?) so you won’t see anything. If we look at the source code of an app with Angular Universal, we will be able to see what is inside the root, we will see body, divs, span, p, etc all our HTML content we will be able to see it!

Advanced Configuration

enter image description here

In this section I want to detail some tips and tricks to get Universal working with the arrival of Ivy, the new Angular 9 compiling and rendering engine. If we have our Angular app with Universal installed and configured BUT we are going to migrate to another version of Angular (9 or more) things start to fail and the Universal migration is not so easy. In fact, the Universal commands you were using before are going to fail.

To go a little deeper, the problem of all this lies in the compatibility of migration, from Angular 9 the Ivy compiler is introduced, and this affects Universal too, especially in the way it searches and compiles the modules, which is not the same as before. So, the way in which we were implementing Universal changed. If you are in Angular 8 or earlier, the way to implement Universal was more complex and more or less like this, you had server.ts and prerender.ts files that you could tune a little by hand, and also you had your webpack file with your config and your command to get the server to raise and prerender everything accordingly.

Now, from Angular 9 onwards, Universal needs to adapt to Ivy, so we will probably have compatibility issues. So…

There is no longer a prerender.ts, nor is there a custom webpack configuration. All this is delegated and who is in charge now is the package we installed from Universal BUT we need to adapt and confirm some configurations so that the changes really take effect and we can continue with Universal in our migrated app.

Here are the most important points, check all of this to get it running

enter image description here

1- In app.server.module.ts before you had this

      @NgModule({
       imports: [
            AppModule,
            ServerModule,
            ModuleMapLoaderModule

Now it looks like this:

      @NgModule({
       imports: [
            AppModule,
            ServerModule

And also delete the import

 import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';

Yes, we blow up the ModuleMapLoaderModule, because it is no longer Ivy compatible.

2- The server.ts that generates you new (with the nguniversal command) has most of the changes done, but you will need to add this if you have errors due to window and/or document issue

const domino = require('domino');
const template = join(process.cwd(), 'dist/browser', 'index.html').toString();
const win = domino.createWindow(template);
global['window'] = win;
global['document'] = win.document;

3- In main.server.ts you need to delete the following

export { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

as we said before, the moduleMapper is no longer compatible with Ivy.

By the way check and confirm that in this file you have this line that works with the new module render:

export { renderModule, renderModuleFactory } from '@angular/platform-server';

4- In tsconfig.server.json you have to keep:

"files": [
 "src/main.server.ts",
 "server.ts"

5- En Angular cli, verificar la config del serve local “serve-ssr”

"serve-ssr": {
"builder": "@nguniversal/builders:ssr-dev-server",
"options": {
"browserTarget": ":build", //este va a tomar las configs de nuestro objeto build
"serverTarget": ":server" //este tomara la config de nuestro objeto server que veremos mas abajo
},
"configurations": {
    "production": {
	    "browserTarget": ":build:production",
	    "serverTarget": ":server:production"
    }
}

Note: For each environment you need to adapt the object configurations according to your environments and thus have 2, 3 or as many environment objects as you have, for example:* *.

"production": {
    "browserTarget": ":build:",
    "serverTarget": ":server:"
}

6- In Angular cli, verify the “server “ configuration.

"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/server",
"main": "/server.ts",
"tsConfig": "/tsconfig.server.json"
},
"configurations": {
"dev": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
],
"sourceMap": true,
"optimization": true
...
},

7- In Angular cli, verify “prerender “ config.

"prerender": {
"builder":"@nguniversal/builders:prerender",
"options": {
    "browserTarget":":build:production",
    "serverTarget":":server:production",
    "routes": [
	    "/"
    ]
},
"configurations": {
"dev": {
    "browserTarget":":build:",
    "serverTarget":":server:",
    "routes": [
	    "/"
    ]
},

8- If you get some typing problems or errors when running server.ts, you can try these versions of universal and express which currently work without errors:

"@nguniversal/builders":"^10.0.0-rc.1",
"@types/express": "^4.17.0"

Deploys

To deploy SSR you will need to run the SSR build, SSR server and also the prerender commands.

If you have more than one environment configured, you need to add in the config of those operations (build, server and prerender) mentioned above for each environment, and in the commands make use of those variables to deploy the corresponding build.

If you can move the files left in dist/browser to the folder where your app is deployed, that’s all. In case you can’t, you will have to run node dist/server/main.js on a node server to get it up.

Finally, in case you need your build (SSR or not) to compile and deploy to an x folder, remember that you can configure it in Angular cli like this:

"architect": {
  "build": {
    "options": {
      "baseHref":"/app_folder/",
      "deployUrl":"/app_folder/",

enter image description here

Conclusion

If we have an Angular app and we want it to load fast the first page load and/or the loading of certain specific paths, that we give priority to SEO and that Google indexes our app correctly, Angular Universal is the option! Angular Universal is easy mmmmmm enter image description here

If you’ve never implemented it, you probably did, because it practically automated everything that used to be manual. But, if you already had one and you got the Ivy bug, you have to migrate and configure by hand everything that the migration could not do automatically.

It can also happen that you have more than one application (project type) running in the same repository, if you configure the variables mentioned above, with the names of those projects in app_name and the environments you need, all the solutions apply to this scenario as well. During this migration of compatibility between Universal and Ivy, I did not find so much documentation so I hope this post will serve as a guide.

enter image description here

Angular Universal Oficial

Blog Angular University

NgDevelop

Blog Universal y Angular 9