Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Angular Universal - essentials

Angular Universal - essentials

Maciej Treder

October 09, 2019
Tweet

More Decks by Maciej Treder

Other Decks in Programming

Transcript

  1. SPA problem <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index\.html$

    - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . index.html [L] </IfModule> .htaccess SPA pitfall
  2. Is it worth? curl localhost:8080 <!DOCTYPE html><html lang="en"><head> <meta charset="utf-8">

    <title>SomeProject</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="stylesheet" href="styles.3ff695c00d717f2d2a11.css"><style ng-transition="app-root"> /*# 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
  3. —prod vs. universal Load HTML Bootstrap Load HTML Bootstrap SSR

    NO SSR First meaningful paint First meaningful paint SSR
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. DRY(c) Don’t repeat your calls export class AppComponent implements OnInit

    { public post: Observable<any>; constructor(private httpClient: HttpClient) {} public ngOnInit(): void { this.post = this.httpClient.get('https://jsonplaceholder.typicode.com/posts/1'); } } APIs
  10. 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
  11. 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
  12. HTTP_INTERCEPTOR @Injectable() export class ServerStateInterceptor implements HttpInterceptor { constructor(private _transferState:

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

    TransferState) { } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { 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
  14. 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