Your Internet Explorer version is not compatible with our shopping cart system. Please use version 9 or higher to avoid problems with your order(s). Close
Bob at Genuitec
Virtual evangelist at large. The face of Genuitec often appearing in graphics. Pen-name for our more shy writers of content.
Posted on Jan 24th 2018
We will be creating a blog using Angular 5, and Firebase in a 3 part series – our app will allow for easy creation and publishing of blog posts. A normal Angular application executes in the browser, rendering pages in the DOM in response to user actions. In order to improve the user experience, we want to render the pages server side and send the generated static content to the client side. The technology that allows us to do this is Angular Universal. Angular Universalgenerates static application pages on the server through a process called server-side rendering (SSR). It can generate and serve those pages in response to requests from browsers. It can also pre-generate pages as HTML files that you serve later.
Now, why would you want something like this? Fundamentally, the fact that JavaScript is not involved in a pre-generated page, makes it render faster – your application’s first page shows up faster, it’s easier for web crawlers (better SEO), and it works better on low-powered devices or on slower connections.
We would recommend that you follow the tutorial step by step, copying snippets from the article into a project you create. This will help you understand both the tool as well as Angular Universal concepts better. However, the final source is also available on GitHub.
New Angular Project
If you don’t already have Angular IDE, please download and install it before starting this tutorial.
Open Angular IDE, and from the top menu select `File`, then `New`, then `Angular Project`. In the `New Angular Project` wizard, enter `FireBlog` as the project name, 1.6.5 as the Angular CLI version, and you can choose whatever Node and NPM versions you wish, or stick with the defaults. Click Next, then click Finish.
Project Module Directories
Our project is a simple blog built with Angular, which will use FireBase for authentication and as a database. In this project we will use at least two modules to organize our code:
Editor Module: This module will provide functionality authors to create and modify content for the blog.
Reader Module: This module will provide functionality for users who come to the blog to read an interact.
Do you need a full-stack IDE that can cover both your back-end and front-end needs? Ready to go beyond what Eclipse has to offer? Try our fullstack MyEclipse IDE, which will rock your world.
Components for Editor Module
We need at least two components for the editor module:
EditorPostsComponent to show the title, published status and other summary details for a list of all blog posts in the application.
EditorPostComponent to show the details for a single post and allows us to edit this single post.
In the top menu select File > New > Component, in the New Component dialog, enter `/FireBlog/src/app/modules/editor/components` as Source Folder and `editor-posts` as element name, and click Finish. Also create a new component in `/FireBlog/src/app/modules/editor/components` with element name `editor-post`, click Finish.
The Editor Module
For our Editor Module we will need two classes, `EditorModule` – which will be our feature module, and `EditorRoutingModule` – which will configure the routing for `EditorModule`.
In the project explorer, right click on the editor folder.
In the pop up menu select New > Class.
In the ‘New Typescript Class’ wizard, enter `EditorModule` as class name, and `editor.module.ts` as file name, then click Finish.
Repeat steps 1 and 2.
In the ‘New Typescript Class’ wizard, enter `EditorRoutingModule` as class name, and `editor-routing.module.ts` as file name, then click Finish.
EditorRoutingModule
Update the content of `src/app/modules/editor/editor-routing.module.ts`:
The Angular Router enables navigation from one view to the next as users perform application tasks. In `EditorRoutingModule`, we are setting up 3 child routes:
`/posts` to render a view of all post titles and some metadata like published status, author, etc.
`/post/:id` to render a view that allows editors to make changes to a single post.
EditorModule
Update the content of `src/app/modules/editor/editor.module.ts`:
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { EditorRoutingModule } from './editor-routing.module';
import { EditorPostsComponent } from './components/editor-posts/editor-posts.component';
import { EditorPostComponent } from './components/editor-post/editor-post.component';
@NgModule({
imports: [
CommonModule,
FormsModule,
EditorRoutingModule,
],
declarations: [EditorPostsComponent, EditorPostComponent],
exports: [],
providers: []
})
export class EditorModule {}
In `EditorModule` we import `EditorRoutingModule` and include it among the imports for `NgModuleEditorModule`. We also import `EditorPostsComponent`, and `EditorPostComponent`, which we add to the declarations for NgModule `EditorModule`.
Components for Reader Module
We need at least two components for the reader module:
ReaderPostsComponent to show title, published status and other summary details for a list of published blog posts in the application.
ReaderPostComponent for the read only view for a single post.
Create two components with element names `reader-posts` and `reader-post`using `FireBlog/src/app/modules/reader/components` as the Source folder.
The Reader Module
For our Editor Module we will need two classes, `ReaderModule` which will be our feature module, and `ReaderRoutingModule` which will configure the routing for `ReaderModule`.
In the project explorer, right click on the reader folder
In the pop up menu select New > Class
In the ‘New Typescript Class’ wizard, enter `ReaderModule` as class name, and `reader.module.ts` as file name, then click Finish.
Repeat steps 1 and 2
In the ‘New Typescript Class’ wizard, enter `ReaderRoutingModule` as class name, and `reader-routing.module.ts` as file name, and then click Finish.
Reader Routing Module
Update the content of `src/app/modules/reader/reader-routing.module.ts`:
Similar to `EditorRoutingModule`, we set up an NgModule `ReaderRoutingModule`, to configure routing for views to be used by readers on the site, we just have two routes, the empty path which will render `ReaderPostsComponent` to show a list of posts available on the blog, and `post/:id` to read a particular blog post.
Reader Module
Update the content of `src/app/modules/reader/reader.module.ts`:
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ReaderRoutingModule } from './reader-routing.module';
import { ReaderPostsComponent } from './components/reader-posts/reader-posts.component';
import { ReaderPostComponent } from './components/reader-post/reader-post.component';
@NgModule({
imports: [
CommonModule,
FormsModule,
ReaderRoutingModule,
],
declarations: [ReaderPostsComponent, ReaderPostComponent],
exports: [],
providers: []
})
export class ReaderModule {}
In `ReaderModule`, we import ReaderRoutingModule and add it to the imports for NgModule, then we import `ReaderPostsComponent`, `ReaderPostComponent` and add them to the declarations.
Routing Module for AppModule
We will create a routing module for our AppModule, but before that lets create a 404 page component. Let’s follow these steps:
Right click on the `app` folder and select New > Component.
Change the Source Folder to `/FireBlog/src/app/components`
Enter `page-not-found` as the element name and then click Finish.
Now let us create a routing module for `AppModule`:
Right click on the `app` folder in the project explorer.
In the pop up menu select New > Class.
In the ‘New Typescript Class’ wizard, enter `AppRoutingModule` as class name, and app-routing.module.ts as file name.
Update the content of `src/app/app-routing.module.ts`:
In `AppRoutingModule`, we have configured our `ReaderModule`, which imports `ReaderRoutingModule` to handle urls without the editor prefix and we have configured `EditorModule` which imports `EditorRoutingModule` to handle urls with the editor prefix. For example `fireblog-domain.tld/post/1` will be routed through `ReaderRoutingModule`, while `fireblog-domain.tld/editor/post/1` will be routed through `EditorRoutingModule`.
Now let us update the content of `src/app/app.module.ts`, and import `AppRoutingModule` and add it to our imports, also checking to ensure `PageNotFoundComponent` is in the declarations array.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
@NgModule({
declarations: [
AppComponent,
PageNotFoundComponent,
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Add Bootstrap CSS
Update `src/index.html` by using a link tag in the document head to include Bootstrap 4 from `maxcdn`. The file content becomes
We will update `src/app/app.component.html`, to add a simple navigation bar and create a sidebar and a content area:
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">{{title}}!</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
<a class="nav-link" routerLink="" >Home</a>
</li>
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{ }">
<a class="nav-link" routerLink="/editor" >Editor</a>
</li>
</ul>
</div>
</nav>
<div class="container mt-4">
<div class="row">
<div class="col-9"><router-outlet></router-outlet></div>
<div class="col-3">
<p class="text-center">About FireBlog!</p>
<p><img src="https://via.placeholder.com/300x246" class="img-fluid"></p>
<p class="text-justify">
FireBlog is a blog created using Angular 5 and FireBase. It is a technology blog, bringing you the latest updates from around the world. Never miss out on new stuff.
</p>
</div>
</div>
</div>
Now let’s update `src/app/modules/editor/components/editor-posts/editor-posts.component.html` with placeholder text, the full functionality will be implemented in the next post in this series:
<p>
Posts will be managed here.
</p>
Also update `src/app/modules/reader/components/reader-posts/reader-posts.component.html`:
<p>
Recent posts will be listed here.
</p>
Server Side Rendering
So far we’ve been setting up our app, just like we would any regular Angular application. We’re now moving on to the Universal part of this tutorial, setting up Angular Universal.
Install dependencies
In order to implement server side rendering, we need to install some additional dependencies. Open a new terminal session in the Terminal+ view, ensuring that the FireBlog project is selected there. Enter the following commands:
This will add `fire-blog` to the style-names of server-rendered pages, so they can be differentiated from the regular styles – these will be removed when the client application takes over.
Create an `app.server.module.ts` file in the `src/app/` directory with the following `AppServerModule` code:
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
@NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule
],
providers: [
// Add universal-only providers here
],
bootstrap: [ AppComponent ],
})
export class AppServerModule {}
Create a `main.server.ts` file in the `src` directory with the following code. Note you can right click the `src` folder, and use File > New > TypeScript Source File to create this file.
export { AppServerModule } from './app/app.server.module';
Create a `server.ts` file in the project’s root directory and add the following code:
// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';
import * as express from 'express';
import { join } from 'path';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// Express server
const app = express();
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main.bundle');
// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
// TODO: implement data requests securely
app.get('/api/*', (req, res) => {
res.status(404).send('data requests are not supported');
});
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});
This looks complex, but what it’s doing is responding to HTTP requests from clients – serving HTML pages, styles, images, etc. It could even respond to data requests.
Note: This sample server is not secure – please do not use it, as-is, in production!
Create a `tsconfig.server.json` file in the project’s `src` directory to configure TypeScript and AOT compilation of the universal app.
This is the TypeScript configuration for Universal.
Create a `webpack.server.config.js` file in the project’s root directory with the following code.
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: { server: './server.ts' },
resolve: { extensions: ['.js', '.ts'] },
target: 'node',
// this makes sure we include node_modules and other 3rd party libraries
externals: [/(node_modules|main\..*\.js)/],
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js'
},
module: {
rules: [{ test: /\.ts$/, loader: 'ts-loader' }]
},
plugins: [
// Temporary Fix for issue: https://github.com/angular/angular/issues/11580
// for 'WARNING Critical dependency: the request of a dependency is an expression'
new webpack.ContextReplacementPlugin(
/(.+)?angular(\\|\/)core(.+)?/,
path.join(__dirname, 'src'), // location of your src
{} // a map of your routes
),
new webpack.ContextReplacementPlugin(
/(.+)?express(\\|\/)(.+)?/,
path.join(__dirname, 'src'),
{}
)
]
};
This Webpack configuration transpiles the server, which is a TypeScript application, to JavaScript.
Update apps section of `.angular-cli.json` – this file can be found in the project’s root folder.
Note: If you don’t see the .angular-cli.json file, you need to disable a resource filter from the View’s menu, as shown below. You can alternatively open the file using File > Open.
Build and Run with Universal
Now that you’ve created the TypeScript and Webpack config files, you can build and run the Universal application.
First add the build and serve commands to the scripts section of the `package.json`:
After building the application, start the server.
`npm run serve:universal`
The console window should say
`Node server listening on http://localhost:4000`
Open a browser to `http://localhost:4000/`.
Serve the App without Server Side Rendering
In the Server window right click on FireBlog and click the green icon with label Start Server:
Demo
Now we have our project running in two modes
http://localhost:4200/ running the regular version of our blog.
http://localhost:4000/ running the server side rendered version of our blog.
Both versions should look like the image below:
Disable JavaScript
In order to see the difference between the two versions, let us go ahead and disable JavaScript in our browser. We are using Google Chrome, so we will check the disable JavaScript option in the settings of developer tools.
Now we will see that the regular Angular project running displays a blank screen, while the server side rendered version shows the rendered page even with JavaScript disabled.
Conclusion
With server-side rendering we can ensure that search engines, browsers with JavaScript disabled, or browsers without JavaScript can still access our site content. In the next article we will add the markdown editor for our authoring posts on our blog, and in the final article we will set up Firebase for authentication and persisting data to remote storage.