How To Add Universal Ssr To Your Angular Cli And Firebase(Angularfire2) Project

    Table of Contents:
  1. 1. Add universal to our app

To be able to use universal with your Angularfire app, make sure to install the latest angularfire2 package.

1
npm install @angular/fire firebase --save

Next set up the Firebase config to environments variable.

You will usually do this in your enironment.ts files.

1
2
3
4
5
6
7
8
export const environment = {  
 production: false, 
 firebase: { 
 apiKey: '', authDomain: '', 
 databaseURL: '', projectId: '', 
 storageBucket: '', messagingSenderId: '' 
 }
 }; 

Now lets import the angularfire2 module to our app.module.ts and the config the firebase variables.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { BrowserModule } from '@angular/platform-browser'; 
 import { NgModule } from '@angular/core'; 
 import { AppComponent } from './app.component'; 
 import { AngularFireModule } from '@angular/fire';
 import { AngularFirestoreModule } from '@angular/fire/firestore'; 
 import { AngularFireStorageModule } from '@angular/fire/storage'; 
 import { AngularFireAuthModule } from '@angular/fire/auth'; 
 import { environment } from '../environments/environment'; 
 @NgModule({ 
 declarations: [ AppComponent ], 
 imports: [ 
  BrowserModule.withServerTransition({ appId: 'my-app' }),
  BrowserAnimationsModule, 
  AngularFireModule.initializeApp(environment.firebase, 'my-app'),  // imports firebase/app needed for everything 
  AngularFirestoreModule, // imports firebase/firestore, only needed for database features 
  AngularFireAuthModule, // imports firebase/auth, only needed for auth features, 
  AngularFireStorageModule, // imports firebase/storage only needed for storage features
 ], 
 providers: [], 
 bootstrap: [AppComponent]
 }) 
export class AppModule { }

Now that we have Angularfire2 set up and import, lets make sure it's working...

Go to ```app.component.ts``` and set up your database to get some data from firebase

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { Component } from '@angular/core'; 
 import { AngularFirestore } from '@angular/fire/firestore'; 
 import { Observable } from 'rxjs'; 
 @Component({ 
    selector: 'app-root',
    template: `{{item.title}}`, 
    styleUrls: ['./app.component.scss'] 
}) export class AppComponent { 
    title = 'my-app';
    items: Observable; 
    constructor(db: AngularFirestore) { 
      this.items = db.collection('items').valueChanges(); 
    }
 } 

Assuming you have a collection in your firebase project call ```items``` with a document that has field ```title```.

Adding Universal for Seo

Now that we have our app working and displaying list items from firebase, it's time to add universal to our app. First thing to do is add the universal package to our project. Lucky for us all is packed we just need to run the following command:

Add universal to our app

1
ng generate universal --client-project

Make sure to replace all instances of ```my-app``` with your app-name.

```ng generate universal``` will create some new files and do necessary updates. That means you don't have to do much manually. Now that, almost everything is done by ng universal, we need to add some files manually. Just two files to be specific.

Build a Server with ExpressJS

Create a file call ```server.ts``` in the root of your project.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// server.ts 
// These are important and needed before anything else
 import 'zone.js/dist/zone-node'; import 'reflect-metadata'; 
 import { enableProdMode } from '@angular/core'; 
 import { ngExpressEngine } from '@nguniversal/express-engine'; 
 import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader'; 
 import * as express from 'express'; import { join } from 'path'; 
 import { readFileSync } from 'fs';

// Polyfills required for Firebase 
(global as any).WebSocket = require('ws'); 
(global as any).XMLHttpRequest = require('xhr2'); 

// Faster renders in prod mode 
enableProdMode(); 

// Export our express server 
export const app = express(); 
const DIST_FOLDER = join(process.cwd(), 'dist'); 
const APP_NAME = 'my-app'; // TODO: replace me! 
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require(`./dist/${APP_NAME}-server/main`); 

// index.html template 
const template = readFileSync(join(DIST_FOLDER, APP_NAME, 'index.html')).toString(); 
app.engine('html', ngExpressEngine({ 
 bootstrap: AppServerModuleNgFactory, 
 providers: [ provideModuleMap(LAZY_MODULE_MAP) ]
 })); 

app.set('view engine', 'html'); 
app.set('views', join(DIST_FOLDER, APP_NAME));

// Serve static files 
app.get('*.*', express.static(join(DIST_FOLDER, APP_NAME)));

// All regular routes use the Universal engine 
app.get('*', (req, res) => { res.render(join(DIST_FOLDER, APP_NAME, 'index.html'), { req }); }); 

// If we're not in the Cloud Functions environment, spin up a Node server
if (!process.env.FUNCTION_NAME) { const PORT = process.env.PORT || 4000; app.listen(PORT, () => { 
    console.log(`Node server listening on http://localhost:${PORT}`); 
}); 

}

Now lets bundle our server app with webpack

Add a Webpack Config for the Express Server

create another file call ```webpack.server.config.js```

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// webpack.server.config.js file 
const path = require('path');
const webpack = require('webpack');

const APP_NAME = 'my-app'; // TODO: replace me!

module.exports = {
  entry: {  server: './server.ts' },
  resolve: { extensions: ['.js', '.ts'] },
  mode: 'development',
  target: 'node',
  externals: [
    /* Firebase has some troubles being webpacked when in
       in the Node environment, let's skip it.
       Note: you may need to exclude other dependencies depending
       on your project. */
    /^firebase/
  ],
  output: {
    // Export a UMD of the webpacked server.ts & deps, for
    // rendering in Cloud Functions
    path: path.join(__dirname, `dist/${APP_NAME}-webpack`),
    library: 'app',
    libraryTarget: 'umd',
    filename: '[name].js'
  },
  module: {
    rules: [
      { test: /\.ts$/, loader: 'ts-loader' }
    ]
  },
  plugins: [
    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'),
      {}
    )
  ]
}

All we need to do now is add our build scripts to ```package.json```

1
2
3
4
5
// ... omitted 
"build": "ng build && npm run build:ssr", 
"build:ssr": "ng run my-app:server && npm run webpack:ssr", 
"webpack:ssr": "webpack --config webpack.server.config.js", 
"serve:ssr": "node dist/my-app-webpack/server.js"

That's it! To test your app locally

1
2
npm run build && npm run serve:ssr
npm run serve:ssr 

opens a server at http://localhost:4000/.

You can navigate there, and view app source to confirm your universal app is all working well.

Related Posts

0 Comments

12345

    00