Firebase + Angular Universal = impossible is possible

Firebase is a great tool for for quick backend development. But the following issues may arise in development using Firebase and Angular Universal:

  • What firebase package to use in client and on the server?
  • What mechanism use for asynchronous operations?
  • How pass data from server to client avoiding requests duplication?

When we started developing our Angular Commerce, on of the main requirenments was SEO optimisation of the application for search engines. For this requirenment search crawler need to recognize content on visited pages. Server rendered applications return external data on user request embedded directly into HTML template. But when we deal with single page applications there are different asynchronous requests. So when you open source code of SPA page you will see something like this:

Search engine doesn’t know what content to index, because of missing content in fetched page, so our page can’t rich first pages of search engines.

We started to find solution for server rendening of SPA and stumbled upon Angular Universal. It's a universal (isomorphic) JavaScript support for Angular. In other words our Angular application renders on server and we get SEO friendly templates in response. You can inject meta tags to you page, get async data from database or apis and all this informations will be presented in source template.

But we got stuck on the problem. Angular Universal doesn’t deal well with WebSockets. As we are using Firebase as backend for our Angular Commerce it was critical to us. So we want to tell about some pitfalls that we met in development and how we solved them.

Using client and server firebase

As we understood client Firebase package is opening WebSocket for authorization, so Universal doesn't know when terminate server rendering. So we had an infinite loop on requesting some page.

We solved this by using client Firebase package in client module and node Firebase in server module. So providers in app.module.ts look like this:

And in app.server.module.ts:

Initialization of firebaseServer is presented in server.ts. Also you must provide credentials key from Firebase Console:

Now on server we are using node Firebase package with Firebase Realtime Database only and client Angular is using client Firebase package with all necessary functional.

Using Observables instead of Promises

As we noticed Angular Universal doesn't deal well with promises. But Firebase Realtime Database library query methods return promises in most cases. So we wrapped those method calls into RxJs Observables. Here is an example of simple query to products bucket in Firebase Realtime Database:

Passing server data to client

How does Angular Universal works? When user makes request, server run renderModuleFactory method that gets use server build and immediately returns html template with rendered page. Then it starts to render browser Angular build. When rendering is over Universal substitutes the view to the browser version. It's interesting that server and client build almost do the same work. It's noticeable in asynchronous requests to database when data is on view, than disappears (because Universal substitutes the view to client version without data), and than appears again.

Here is demo with passing data from server to client:

universal-firebase.firebaseapp.com

And here without passing data:

universal-firebase-flashing.firebaseapp.com

In Angular 5 in @angular/platform-server module there is aTransferStateModule. This module helps you to transfer it’s state from the server to the browser, to remove the need of requesting data again in the browser.

As we are working with Angular 4, we discovered a solution how to pass data from server to client without Transfer State.

renderModuleFactory method have an optional argument options:

To extraProviders you can pass providers that will be added to server module and will be present in server app. So we created a serverStore object that will store all data that we want to pass to client app. Also providing object with setter of global serverStore helped us setting it in server app.

app.engine callback in server.ts will look like this:

Finally we set our serverStore to Window.store before importing other scripts. Getting server data is simple in AppModule:

Getting server data on client

What about using store in components? It’s simple. In server app get the data and set it to store. In client app we need to check if there is necessary data on store and get it. Here is a simple example how to get products from Firebase Realtime Database in ngOnInit method:

Notice that serverStore provider only exist on server build so it's injected with @Optional decorator for avoiding error on client:

So, using Angular Universal with Firebase is fairly unobvious, but there are way how to deal with it.