So, Angular universal is a thing... And it seems it is something you all have been very excited about for some time. Since I tweeted about this some time ago, it's been one of the top 3 most common questions asked of us (right behind "when's Ionic Vue going to be ready"). Well I did promise that it would be happening soon and that there would indeed be a blog post, so let's take care of that today!
Introducing the @ionic/angular-server
module for Angular Universal!
Universal JavaScript
First off, what is Universal JavaScript anyway? Essentially it's JavaScript that can run on the server and in the browser. This is what people refer as "Server Side Rendering" (SSR). By utilizing SSR and Universal JavaScript in our app, we can do an initial render of our app on the server and send over a precompiled version before any JavaScript has been run on the client. With this technique we can achieve
- Faster load times 1
- Better SEO ranking
- Preview cards on social media
- Faster load times can vary and can really fluctuate depending on your app & other circumstances. Your mileage may vary.
So if SSR can help us, how can we add it to our app? Well up until recently, you couldn't. A lot of Ionic's components utilize window
and other DOM specific APIs. Since we're on a server and running in Node, we don't have the DOM. This is where Angular Universal and @ionic/angular-server
module comes in.
Angular Universal
Angular Universal is Angular's library for running our apps on the server. In earlier releases of Angular (and AngularJS), setting up an app to run on the server was a bit of a pain. Thankfully, Angular Universal and it's various runtimes make this really straight forward.
First let's create an app and update it to the latest version of Angular
ionic start myApp blank --type angular
cd myApp
ng update @angular/core @angular/cli
npm install @angular/animations
Once this has completed, let's add the Express engine for Angular Universal
Express is a popular framework for Node. For more information, please checkout the Express website
ng add @nguniversal/express-engine
This will set up our project to be ready for Angular Universal. Before we continue any further, let's install the @ionic/angular-server
module.
npm install @ionic/angular-server@dev
@ionic/angular-server
is currently part of a dev release and will be part of an upcoming release of Ionic. Be sure to keep an eye out.
Now, let's open our editor and inspect our project.
Lay of the Land
After Angular Universal is added to our project, you'll notice some *.server.*
related files around the project. Specifically a main.server.ts
and a app.server.module.ts
. These two files in particular are responsible for bootstrapping our app once it is loaded on the server. We can see what this will be like by running:
npm run dev:ssr
This will start our live reload server for our SSR app. Don't be surprised if you see a lot of output from the terminal, we actually have a lot going on here. This script is performing a build of our client side app, as well as building out the server side portion as well. Once this has finished, we'll get a message that our app is available to load on localhost:4200
. Once loaded, our app looks like an normal Ionic App.
But if we disable JavaScript on our page, we'll end up getting a blank screen.
What gives? Well this is where the @ionic/angular-server
module comes in.
First, let's stop our server for a bit and open our src/app/app.server.module.ts
. Here is where our app will bootstrap when it is deployed to our server. Without diving into too many details, Angular Universal provides an entry hook so we can handle situations where our components will be rendered on the server. To take advantage of this, we'll import IonicServerModule
and add it to the imports array of our ngModule
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
// Tell Ionic components how to render on the server
import { IonicServerModule } from '@ionic/angular-server';
@NgModule({
imports: [
AppModule,
ServerModule,
IonicServerModule
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
Once this has been added, we can run our dev-server again and see that our app will still work, even if JavaScript has been disabled.
Some technical details
Now if we wanted to we could deploy our app to different providers (Firebase Cloud Functions for example) and be off to the races, but that doesn't cover everything. You should validate that any library being used can work in situations where there is no window
object. Since Universal still isn't too common of a practice, many DOM specific libraries either haven't added support for Universal, or simply just can’t. If you’re using libraries built with Angular, you should be good. If you’re not sure if your project will have issues, the output from a full server build should point you to the problematic library.
Parting thoughts
Since this is only a pre-release of Angular Universal support, we’re looking to get feedback from users. We’ve tested things pretty extensively, but we’d love for you to try it out and let us know if you run into any issues.
We've really only scratched the surface of what can be done. There's topic's like prerender, state transfer, and even static site generation (via Scully) that are now available to us with Angular Universal.
This really is an exciting time to be working in Angular with Ionic. There's a lot more information available out there and I'd encourage people to check out a few resources listed below. As for now, try out the Universal module, let us know if you run into any issues, and thank you for being so patient while we work on it! Cheers 🍻