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