Universally Speaking

2018

Hi, I'm Craig

Hi, I'm Craig

I'm from Wellington,

New Zealand

We're here

@phenomnominal
2018
@phenomnominal
2018

This is a deep dive!

Assumptions:

You know some Angular ✅

You've heard of Angular Universal ✅

What we're covering:

1️⃣ What problems does Angular Universal solve?

2️⃣ How does it solve those problems?

3️⃣ How do we make our applications work with Universal?

4️⃣ Why would you not want to use Universal?

@phenomnominal
2018

I work for 

Founded in 1999

NZ population: ~ 4.9 million

Trade Me accounts: ~ 4.5 million

Auctions, classifieds, "e-commerce"

> 650 employees, > 100 developers

New Zealand's biggest website

@phenomnominal
2018

Classic (1999 - ?)

Touch (2012 - ?)

@phenomnominal
2018
@phenomnominal
2018

FrEnd - 2016

Not fast.

Aiming for mobile-first

Lots of JavaScript

Lots of DOM

Lots of $$watchers

Lots of jank

@phenomnominal
2018

Goodbye AngularJS

@phenomnominal
2018

Hello Angular

FrEnd - mid 2018

@phenomnominal
2018

Rolling out to mobile users

682

@NgModules

Not slow?

Too much JavaScript

Making good 💰💰💰

Fully rebuilt in Angular

Angular

@phenomnominal
2018
trademe.nz/listing/:id
  <html>
      <link 
          rel="stylesheet"
          href="/static/styles.css">
      <script
          src="/static/script.js">
      <body>
          <trade-me>
              <div
                  class="tm-loading">
              </div>
          </trade-me>
      </body>
  </html>
          rel="stylesheet"
          href="/static/styles.css"

          src="/static/script.js"



                  class="tm-loading"


              "stylesheet"
               "/static/styles.css"

              "/static/script.js"



                        "tm-loading"


Generic application loading state

Static resources

Angular

@phenomnominal
2018
trademe.nz/static/script.js
trademe.nz/static/styles.css
       function () {
           ...
       }
       
       html { 
           ...
       }
                () {
           ...
       }
       
            { 
           ...
       }
                
           ...
       
       
             
           ...
       

Angular

@phenomnominal
2018
trademe.nz/api/listing/:id.json
trademe.nz/api/categories.json
    {"id":"1234567890", ... }

  {"name":"Arts & Crafts", ... }

@phenomnominal
2018

What can we do to remove some of those round trips?

🤔

@phenomnominal
2018

Universal

Angular

Angular Universal

@phenomnominal
2018
trademe.nz/listing/:id
  <html>
      <link 
          rel="stylesheet"
          href="/static/styles.css">
      <script
          src="/static/script.js">
      <body>
          <trade-me>
              <trade-me-homepage>
                  <trade-me-search>
                  <trade-me-categories>
              </trade-me-homepage>
          </trade-me>
      </body>
      <script
          id="server-app-state">
      </script>
</html>
          rel="stylesheet"
          href="/static/styles.css"

          src="/static/script.js"









          id=

              "stylesheet"
               "/static/styles.css"

              "/static/script.js"

          







             "server-app-state"

Real application content!

Inlined server request data

@phenomnominal
2018

Angular Universal

Why?

Performance

SEO/No JavaScript environments

Social media links

1️⃣

2️⃣

3️⃣

@phenomnominal
2018

Performance

Slower "Time to First Byte" due to server processing time

Faster "First Paint" over client-only application, particularly for mobile devices

This can be mitigated with caching - both on the server & on the client with a service worker

Slower "Time to Interactive" due to client-side rehydration

Faster discovery of resources required for your application

Measure, experiment, and find what works for your app

@phenomnominal
2018

SEO/No JS Environments

Some crawlers have JavaScript capabilities, but who knows how that really works (not me!)

Support for browsers with JavaScript disabled, or old engines.

(Check out Igor Minar's  talk from ng-conf 2018 for how angular.io handles SEO without Angular Univeral)

If you're competing against other websites, server rendering can give you an advantage over client-only SEO

@phenomnominal
2018

Social links

Pre-rendered content for Facebook, Twitter, etc.

@phenomnominal
2018

Doop.

@phenomnominal
2018

Angular Universal!

Fast ✅

Still too much JavaScript

Making even more 💰💰💰

SEO ✅

Social ✅

🚀

🦖

🌎

🦕

@phenomnominal
2018

⭐️

Neat.

🛰

⭐️

⭐️

⭐️

⭐️

⭐️

⭐️

⭐️

⭐️

🆖

🛸

@phenomnominal
2018

 Sam Neil!   (He's a kiwi!)

@phenomnominal
2018

https://www.youtube.com/watch?v=wU1HbwC71j4

 Flea!
 (He's Aussie, close enough)

@phenomnominal
2018
@phenomnominal
2018

Asteroids

@phenomnominal
2018

Facts about asteroids

Categorised as "minor planets"

Not comets or meteoroids

14,464 known "near-Earth" asteroids

ALLEGEDLY killed the dinosaurs

Cool names like "Odysseus" and "James Bond"

💥

💥

💥

💥

💥

💥

💥

💥

@phenomnominal
2018

Asterank

@phenomnominal
2018

We can rebuild it

Angular CLI

@phenomnominal
2018
@phenomnominal
2018

Demo.

@phenomnominal
2018
@phenomnominal
2018

⭐️ A new app Angular CLI app

⭐️ Build steps for two different versions of the app and the server

⭐️ All the necessary Angular Universal dependencies

🎉🎉🎉🎉🎉

⭐️ A new server.ts file which contains the server code

⭐️ A modified app.module.ts that uses withServerTransition

⭐️ A new app.server.module.ts file for the server application

⭐️ A modified main.ts that waits for DOMContentLoaded before             bootstrap

⭐️  A new main.server.ts file for the server bootstrap

What do we have?

@phenomnominal
2018

JavaScript runtime (no DOM)

Driven by Chrome's V8 engine

Use everyone else's code via 

JS Logo as seen from NZ

@phenomnominal
2018

Node.js

Require the built Universal app

Set up the engine for running the application

Pass all requests to the engine

Create the Express server

Start the server

@phenomnominal
2018

Time passing

@phenomnominal
2018

Demo.

@phenomnominal
2018
@phenomnominal
2018

What do we have?

⭐️ @angular/cli application

⭐️ Tangram (Trade Me's component library)

⭐️ Asteroid data requested from Asterank

⭐️ WebGL animation

⭐️ @ngrx/store + router-state + entity

😔 Client render only

@phenomnominal
2018
window is not defined
@phenomnominal
2018

How do we run this on the server?

🤔

@phenomnominal
2018

Angular Universal

Run your Angular application anywhere...

https://www.youtube.com/watch?v=_trUBHaUAR0
@phenomnominal
2018

Anywhere...

⚠️ Don't forget workers ⚠️

@phenomnominal
2018

How do we run this anywhere?

🤔

@phenomnominal
2018

One implementation for all environments

One implementation for a specific environment

Different implementations for each environment

Different functionality for each environment

1️⃣

2️⃣

3️⃣

4️⃣

Four patterns

@phenomnominal
2018

One implementation for all environments

1️⃣

@phenomnominal
2018

Avoiding the DOM

Do not use the DOM directly! There's almost always a way to do the same thing with Angular's abstractions.

@phenomnominal
2018

Components

@Input, @Output, @HostBinding, @ViewChild, @ContentChild, ngStyle, ngClass.

You may occasionally need to reach for the Renderer:

Normal dependency injection

@phenomnominal
2018

But what if you have to use the DOM?

🤔

@phenomnominal
2018

Be explicit about it

Be defensive!

Explicit null type is good

Only run code if window is available.

@phenomnominal
2018

Provide an alternative

Use custom services to abstract around DOM APIs.

Only fall back to the actual DOM if you have to

Optional injection token!

Use the optionally injected value if available

@phenomnominal
2018

Hack it?

Some third-party code won't want to play nicely...

(here's lookin' at you three-trackballcontrols 😅)

Set up globals

Require troublesome module

Clean up after

Directly check for window

@phenomnominal
2018

Check the platform...

@phenomnominal
2018

Check the platform

If you really must...

Injectable PLATFORM_ID token

Use the method for the specific platform

@phenomnominal
2018

Demo.

@phenomnominal
2018
document is not defined
@phenomnominal
2018

One implementation for a specific environment

2️⃣

Just turn it off

@phenomnominal
2018

Add @Optional injection annotation

Trusty if statement

Check for the presence of an injected service:

Just turn it off

@phenomnominal
2018

Turn things off at a provider level:

Provide null for specific platform

Corresponding injection token

@phenomnominal
2018

Demo.

@phenomnominal
2018

Different implementation for each environment

3️⃣

@phenomnominal
2018

Platform-specific modules

Remember app.module.ts & app.server.module.ts?

@phenomnominal
2018

Services

Provide different implementations for different environments:

Server implementation for Renderer

Server implementation for HttpClient

@phenomnominal
2018

Different functionality for each environment

4️⃣

State Transfer

@phenomnominal
2018

The whole application runs twice.

This means duplicate API calls! 🤯

This call happens twice

State Transfer

@phenomnominal
2018

Inject TransferState

Create a state key

Set the state on the first run

Re-use it if it's available

@phenomnominal
2018

State Transfer

The state transfer data is inlined into the HTML response as a blob of JSON. This means...

Don't send too much data!

Your whole store must be JSON serialisable!

State Transfer

@phenomnominal
2018

Or just transfer your whole store state!

One state key for your whole store

Read the whole store and serialise

State Transfer

@phenomnominal
2018

Or just transfer your whole store state!

Use the same key on the client

Dispatch a new action with the whole serialised store 

State Transfer

@phenomnominal
2018

Use a simple cache layer to prevent duplicated calls

Split the GET into two parts

Mark each API response with a cache time

Only do the call if the cache time has expired

@phenomnominal
2018

Demo.

@phenomnominal
2018

One implementation for all environments

One implementation for a specific environment

Different implementations for each environment

Different functionality for each environment

1️⃣

2️⃣

3️⃣

4️⃣

Cool.

@phenomnominal
2018

Why wouldn't you use Angular Universal?

@phenomnominal
2018

🤔

@phenomnominal
2018

Complexity

You now need a "real" server, not just static files.

Harder to get working, harder mental model, harder to get people shipping value

There will be lots more code - logging, monitoring

@phenomnominal
2018

Performance

Universal is not a magical silver rocket.

If performance is your only goal, just ship less JavaScript

Rocket Lab! (Also NZ)

It can definitely make things worse if you're not careful!

@phenomnominal
2018

Workflow/DX

It's very slow...

Bazel will fix it?

"Ivy will fix it" - @yearofmoo

@phenomnominal
2018

Lint rules for using DOM APIs

Lint rules for using isBrowser

Meta-reducer for unserialisable data

Test for too much serialised data

Universal E2E tests

💥

💥

💥

💥

💥

💥

💥

💥

Fix it before you break it

@phenomnominal
2018

So...

@phenomnominal
2018

If you want/need your Angular app to be crawled by Search Engines

@phenomnominal
2018

If you want/need improved No JS/old browser support

@phenomnominal
2018

If you want/need content previews for Social Media

@phenomnominal
2018

AND if you're willing to fight for better perceived performance

@phenomnominal
2018

Universal

Angular

Chur.

@phenomnominal
2018

(that means cheers 😎)

(that means thanks 😅)

universally-speaking

By Craig Spence

universally-speaking

Craig Spence - Angular Connect 2018 - Universally Speaking

  • 5,153