Server Side Render an Angular CLI Project with Angular Universal

Bram Borggreve
InstructorBram Borggreve
Share this video with your friends

Social Share Links

Send Tweet

In this lesson we will add Server Side Rendering to our application using Angular Universal. We'll build a simple Express application and see how Angular Universal builds and renders our app on the server.

Since Angular CLI v6 we can simple run ng generate universal --clientProject store, where store is the name of our app.

In angular.json in the architect.server object we define a configurations object, to make sure the correct environment gets copied on a production build.

To make Angular Universal with lazy loaded modules we need to add the ModuleMapLoaderModule to AppServerModule, which we get after installing @nguniversal/module-map-ngfactory-loader from npm.

npm install --save @nguniversal/module-map-ngfactory-loader @nguniversal/express-engine

In the project root we implement a server.ts and define our simple express server.

When we run the new server we see that the app gets rendered before it hits the browser.

Instructor: [00:00] The default index.html file that is generated with our project is a simple file with a few tags inside the head, and only one tag in the body -- the selector of our app component, where we run ngBuildStore --prod in our project folder, and serve the output with NPX serve -s dist/store.

[00:19] We can check the source and see that it only consists of the content of our index.html, plus some additional scripts. This is also how most search engines and social media sites will see the page, which is far from ideal.

[00:33] Let's have Angular Universal to make our application SEO-friendly. Since Angular CLI version six, this process has been simplified a lot. We can simply run the command ngGenerateUniversal, and pass in the flag, --client project store, which is the project name found in angular.json.

[00:51] We see that this command generated a few files for us, updated a few others, and it ran npm install. Let's take a look at angular.json first. In our project, in the architect object, we see that there is a new object called server.

[01:04] In the options we see that the output path is set to dist/store/server, and that it uses a different main file and tsConfig. Here, we're going to add a new object called configuration, and inside that, an object called production.

[01:16] We add a file replacements array, where we replace source/environments/environments.ts with source/environments/environments/prod.ts. This makes sure that our production apply settings get applied where we build the server-side version of our app.

[01:33] The tsConfig for the server extends the default tsConfig. The main difference here is that the output is set to common JS to make sure that Node JS can handle it. We also see that the Angular compiler options has an entry module, which is our new app server module.

[01:47] The file main.server.ts is used to bootstrap our app on the server. Because our Angular app uses Zone JS, this is a great place to import Zone JS for Node. The app server module is a simple module that imports app module, server module from Angular/platform-server, and it bootstraps app component, just like app module does for the browser.

[02:06] Because we're using lazy loading, we need to add an extra module to app server module. From NPM, we install the module @ngUniversal/module-map-ngfactory-loader, and also at ngUniversal/Express [inaudible] that we need in server.ts in a bit.

[02:23] Once installed, we can add module map loader module to the imports array, and make sure to import it. Now, in package.json, let's update the build script, set it to ngBuildStore -- prod, and n ngRunStore:server:production.

[02:39] We run npm run build, and we see that both apps got built. Angular Universal requires a web server to host a generated app. Let's implement that using Node and Express. We create a new file, server.ts, in the project root, and first, add some imports.

[02:54] We import Express. We import joinFromPath. We important ngExpressEngine from ngUniversal/ExpressEngine, and we import provideModuleMap from ngUniversal module map ngFactory loader.

[03:04] We add a const port that we take from the environment variable, or set it to a default 8080. We add a const static root that returns the join method with the current working directory, and then dist and store, so it points to the browser build.

[03:20] Next, we destructure app server module, ngFactory, and lazy module map that we will require from a file called dist/store-server-main, which is our server build. The last const is app.invokesExpress. We use the app.engine method to define the view engine, and we pass in two parameters.

[03:38] The first is HTML. The second parameter is a method that gets invoked for this type of file. We pass in the ngExpressEngine function, we add a property, bootstrap, and set it to app server module ngFactory. The second property, providers, which is an array.

[03:54] Inside that array, we invoke the provideModuleMaps function, and pass in lazy module map as a parameter. Now that the view engine is defined, we can use it using app.set, and we set views to static root.

[04:06] We then use app.get to listen to start of star, and pass in express.static, with static root as a parameter, and listen to star. Pass in a method with the standard express signature request and result. This method implicitly returns rest.render method, takes a string index, and an object with one value, the request.

[04:26] Now that the server is configured, we tell it to listen to the port, and log a friendly message, so we know it's listening. We open package.json, update the start script, and set it to value ts-node./server.ts. We start the server with npm start, and when we open the listening port in the browser, we should see the server side rendered version of our page.

[04:47] We can view the source and see that the content gets rendered and added inside the HTML body by the server.