Slide 1

Slide 1 text

@maciejtreder

Slide 2

Slide 2 text

Outline • SPA pitfall • Server-side rendering • API optimization SPA pitfall SSR APIs

Slide 3

Slide 3 text

SPA problem RewriteEngine On RewriteBase / RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . index.html [L] .htaccess SPA pitfall

Slide 4

Slide 4 text

SPA Problem GET / GET /anotherPage index.html GET /subpage GET /contact GET /home SPA pitfall

Slide 5

Slide 5 text

SPA Problem GET / GET /anotherPage SPA pitfall

Slide 6

Slide 6 text

SPA Problem SPA pitfall

Slide 7

Slide 7 text

Server Side Rendering SSR

Slide 8

Slide 8 text

Server Side Rendering GET / GET /anotherPage SSR

Slide 9

Slide 9 text

Is it worth? curl localhost:8080 SomeProject /*# sourceMappingURL=data:application/ json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJzcmMvYXBwL2FwcC5jb21wb25lbnQuY3NzIn0= */</ style></head> <body> <script type="text/javascript" src="runtime.26209474bfa8dc87a77c.js"></script><script type="text/javascript" src="es2015- polyfills.c5dd28b362270c767b34.js" nomodule=""></script><script type="text/javascript" src="polyfills.8bbb231b43165d65d357.js"></ script><script type="text/javascript" src="main.8a9128130a3a38dd7ee5.js"></script> <app-root _nghost-sc0="" ng-version="7.2.9"><div _ngcontent-sc0="" style="text-align:center"><h1 _ngcontent-sc0=""> Welcome to someProject! </h1><img _ngcontent-sc0="" alt="Angular Logo" src="data:image/ svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERD AwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJ GIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1 Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogI Dwvc3ZnPg==" width="300"></div><h2 _ngcontent-sc0="">Here are some links to help you start: </h2><ul _ngcontent-sc0=""><li _ngcontent-sc0=""><h2 _ngcontent-sc0=""><a _ngcontent-sc0="" href="https://angular.io/tutorial" rel="noopener" target="_blank">Tour of Heroes</a></h2></li><li _ngcontent-sc0=""><h2 _ngcontent-sc0=""><a _ngcontent-sc0="" href="https:// angular.io/cli" rel="noopener" target="_blank">CLI Documentation</a></h2></li><li _ngcontent-sc0=""><h2 _ngcontent-sc0=""><a _ngcontent-sc0="" href="https://blog.angular.io/" rel="noopener" target="_blank">Angular blog</a></h2></li></ul></app-root> SSR

Slide 10

Slide 10 text

—prod vs. universal Load HTML Bootstrap Load HTML Bootstrap SSR NO SSR First meaningful paint First meaningful paint SSR

Slide 11

Slide 11 text

How to start? SSR ng add @nguniversal/express-engine ng add @ng-toolkit/universal

Slide 12

Slide 12 text

Adjust your modules app.module.ts app.server.module.ts @NgModule({ bootstrap: [AppComponent], imports: [ BrowserModule.withServerTransition({appId: //other imports ], }) export class AppModule {} import {NgModule} from '@angular/core'; import {ServerModule} from '@angular/platform-server'; import {ModuleMapLoaderModule} from ‘@nguniversal/module-map-ngfactory-loader'; import {AppModule} from './app.module'; import {AppComponent} from './app.component'; @NgModule({ imports: [ AppModule, ServerModule, ModuleMapLoaderModule ], bootstrap: [AppComponent], }) export class AppServerModule {} SSR

Slide 13

Slide 13 text

Adjust your modules Official guide app.module.ts app.server.module.ts @NgModule({ declarations: [AppComponent], imports: [ //common imports ] }) export class AppModule {} import {NgModule} from '@angular/core'; import {ServerModule} from '@angular/platform-server'; import {ModuleMapLoaderModule} from ‘@nguniversal/module-map-ngfactory-loader'; import {AppModule} from './app.module'; import {AppComponent} from './app.component'; @NgModule({ imports: [ AppModule, ServerModule, ModuleMapLoaderModule, //server specific imports ], bootstrap: [AppComponent], }) export class AppServerModule {} app.browser.module.ts @NgModule({ bootstrap: [AppComponent], imports: [ AppModule, BrowserModule.withServerTransition({appId: ' //browser specific imports ] }) export class AppModule {} //browser specific imports //server specific imports SSR

Slide 14

Slide 14 text

Under the hood export const app = express(); app.use(compression()); app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main'); app.engine('html', ngExpressEngine({ bootstrap: AppServerModuleNgFactory, providers: [ provideModuleMap(LAZY_MODULE_MAP) ] })); server.ts SSR

Slide 15

Slide 15 text

Under the hood app.get('/*', (req, res) => { res.render('index', {req, res}, (err, html) => { if (html) { res.send(html); } else { console.error(err); res.send(err); } }); }); server.ts SSR

Slide 16

Slide 16 text

Under the hood app.set('view engine', 'html'); app.set('views', './dist/browser'); app.get('*.*', express.static('./dist/browser', { maxAge: '1y' })); server.ts SSR

Slide 17

Slide 17 text

And let’s go! • npm run build:ssr • npm run serve:ssr Date: 2018-11-21T13:04:33.302Z Hash: 1a82cb687d2e22b5d12b Time: 10752ms chunk {0} runtime.ec2944dd8b20ec099bf3.js (runtime) 1.41 kB [entry] [rendered] chunk {1} main.09093ffa4ad7f66bc6ff.js (main) 169 kB [initial] [rendered] chunk {2} polyfills.c6871e56cb80756a5498.js (polyfills) 37.5 kB [initial] [rendered] chunk {3} styles.3bb2a9d4949b7dc120a9.css (styles) 0 bytes [initial] [rendered] > [email protected] server /Users/mtreder/myApp > node local.js Listening on: http://localhost:8080 SSR

Slide 18

Slide 18 text

API optimization APIs

Slide 19

Slide 19 text

DRY(c) Don’t repeat your calls export class AppComponent implements OnInit { public post: Observable; constructor(private httpClient: HttpClient) {} public ngOnInit(): void { this.post = this.httpClient.get('https://jsonplaceholder.typicode.com/posts/1'); } } APIs

Slide 20

Slide 20 text

2 1 3 4 5 6 external.api.com APIs

Slide 21

Slide 21 text

external.api.com my-website.com 1 2 3 APIs

Slide 22

Slide 22 text

HttpCacheModule npm install @nguniversal/common import { NgtUniversalModule } from '@ng-toolkit/universal'; import { CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { TransferHttpCacheModule } from '@nguniversal/common'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports:[ CommonModule, NgtUniversalModule, TransferHttpCacheModule, HttpClientModule ] }) export class AppModule { } APIs

Slide 23

Slide 23 text

TransferState • ServerTransferStateModule (@angular/platform-server) • BrowserTransferStateModule (@angular/platform-browser) • get(key, fallbackValue) • set(key, value) • has(key) • remove(key) APIs

Slide 24

Slide 24 text

HTTP_INTERCEPTOR • Provided in the AppModule • Every http request made with HttpClient goes threw it • Used to transform request or response ie: • Adding authentication headers APIs

Slide 25

Slide 25 text

HTTP_INTERCEPTOR @Injectable() export class ServerStateInterceptor implements HttpInterceptor { constructor(private _transferState: TransferState) {} intercept(req: HttpRequest, next: HttpHandler): Observable> { return next.handle(req).pipe(tap(event => { if (event instanceof HttpResponse) { this._transferState.set(makeStateKey(req.url), event.body); } })); } } APIs

Slide 26

Slide 26 text

HTTP_INTERCEPTOR @Injectable() export class BrowserStateInterceptor implements HttpInterceptor { constructor(private _transferState: TransferState) { } intercept(req: HttpRequest, next: HttpHandler): Observable> { if (req.method !== 'GET') { return next.handle(req); } const storedResponse: string = this._transferState.get(makeStateKey(req.url), null); if (storedResponse) { const response = new HttpResponse({ body: storedResponse, status: 200 }); this._transferState.remove(makeStateKey(req.url)); return of(response); } return next.handle(req); } } APIs

Slide 27

Slide 27 text

HTTP_INTERCEPTOR import {HTTP_INTERCEPTORS } from '@angular/common/http'; providers: [ { provide: HTTP_INTERCEPTORS, useClass: BrowserStateInterceptor, multi: true, } ] import {HTTP_INTERCEPTORS } from '@angular/common/http'; providers: [ { provide: HTTP_INTERCEPTORS, useClass: ServerStateInterceptor, multi: true, } ] APIs

Slide 28

Slide 28 text

https://www.twilio.com/blog/faster-javascript-web-apps-angular- universal-transferstate-api-watchdog APIs

Slide 29

Slide 29 text

APIs

Slide 30

Slide 30 text

Case Study /protected /login /auth/login Authy ID uuid uuid /auth/status uuid

Slide 31

Slide 31 text

Remembered User /protected /login Authy ID uuid /auth/status uuid Authy ID from cookie /auth/login uuid + uuid

Slide 32

Slide 32 text

https://www.twilio.com/blog/expedited-two-factor-authentication- angular-twilio-authy APIs

Slide 33

Slide 33 text

APIs

Slide 34

Slide 34 text

@maciejtreder