Universal Angular 2 In Action

汤桂川

@GF SECURITIES

汤桂川

Speaker/Writer/Globetrotter

-- Front End Expert @ GF.S

-- Ex Tencenter

A

① What

  • What 's universal

  • what's  the purpose

② How

  • How to start

  • How it works

More 

  • More Universal

  • In FinTech

g

e

n

a

d

What

What happens

when you type

www.google.com

in your browser?

Client         Server

Three Periods

client

Get Page

Static HTML

server

(CDN

/CGI)

Get CSS Files (or inline)

CSS (page parse)

Get JavaScript Files 

JS (events, xhr)

Request Data

Data

Render

Template

Period  I

* Picture by Wassim Chegham

Get Page

Dynamic HTML(with data in tag...)

server

(CDN

/CGI)

Data fetch by server

Render Template

Period  II

Async Get Assets(css, js)

Assets(parse, events)

client

- DNS, TCP,RTT 

Get Page

Dynamic HTML(with rendered templ)

server

(CDN

/CGI)

Async get assets(css, js)

Assets(parse, events)

Data fetch by server

Interaction

or re-render

Period  III server-side rendering

Render Template

client

More

Optimize?

Node.js

Front End Community

renderToString

2.0

 Backbone
 FastBoot

WAKE UP

Angular Universal is coming!!!

There are only two hard things in Computer Science:

  • Cache invalidation

  • Naming things

-- Phil Karlton

not the same

but only look the same

Full Stack 

Front End:

Back End:

  • Angular 2 (DI, Router...)
  • TypeScript
  • Webpack, system.js...
  • Sass, postcss...
  • rx.js (Observable)
  • zone.js (async)
  • Node.js (Express, Hapi)
  • Asp.net
  • preboot.js
  • Parse5...

广发证券原创书籍

《揭秘 Angular 2》

Github:

lightningtgc/ awesome-ng2

Get Page

HTML(pre-render+preboot.js(inline))

server

(CDN)

Async Get Assets(css, js)

PrebootJS records events

Angular 2 Bootstraping

clientView rendered to hidden div

PrebootJS replays events

PrebootJS switches view context to previously hidden div (clientView)

client

< 1s

interaction

What's

the purpose?

(Why?)

< 1s

Initial Load

APP take over

Server rendered view
became visible

Without server-side rendering

With server-side rendering

SEO

Initial Load

Legacy Browers

YES

YES

YES

NO

NO

NO

Angular 2

Universal

Angular 2

SPA

Social Link

YES

NO

HTML Mail

or non-js

FB, Twitter

Link Preview

Lazy Loding

Performance

② How

How To Start?

At  First

Projects Relations:

universal-starter

universal

preboot

angular 2

parse5

Express(Hapi)

Engine

gulp(webpack)

Prerender

modules

 Universal Starter Kit (Express.js + Webpack)

git clone https://github.com/angular/universal-starter.git

npm install

npm start

How it works?

Node

npm start

server.ts

main.node.ts

index.html

client.ts

main.browser.ts

app.component.ts

Express

engine

Browser

send

①Server Line

②Browser Line

Show me the code?

前方高能。。。

{
  "name": "universal-starter",
  "version": "2.0.0",
  "scripts": {
     "start": "npm run server",

     "prestart": "npm run build",
     "prebuild": "rimraf dist",
     "build": "webpack",

     "server": "nodemon dist/server/index.js"
  }
}

package.json (mini version) 

   > npm start

①Server Line

import * as express from 'express';
const app = express();
// Angular 2 Universal
import { expressEngine } from 'angular2-universal';

// Express View
app.engine('.html', expressEngine);
app.set('views', __dirname);
app.set('view engine', 'html');

import { ngApp } from './main.node';
// ensure routes match client-side-app
app.get('/', ngApp);
app.get('/home', ngApp);

// Server
let server = app.listen(process.env.PORT || 3000, () => {
  console.log(`See: localhost:${server.address().port}`);
});

server.ts (mini version)

import {
  REQUEST_URL,ORIGIN_URL,NODE_LOCATION_PROVIDERS,
  NODE_HTTP_PROVIDERS,ExpressEngineConfig
} from 'angular2-universal';
import { provideRouter } from '@angular/router';
import { APP_BASE_HREF } from '@angular/common';
// Application
import {App} from './app/app.component';
import {routes} from './app/app.routes';

export function ngApp(req, res) {
  let baseUrl = '/';
  let url = req.originalUrl || '/';
  let config: ExpressEngineConfig = {
    directives: [ App ],
    platformProviders: [
      {provide: ORIGIN_URL, useValue: 'http://localhost:3000'},
      {provide: APP_BASE_HREF, useValue: baseUrl},
    ],
    providers: [
      {provide: REQUEST_URL, useValue: url},
      NODE_HTTP_PROVIDERS,NODE_LOCATION_PROVIDERS
      provideRouter(routes)
    ],
    async: true,
    preboot: true
  };
  res.render('index', config);
}

main.node.ts

Async

Gap Event

@Component({
  selector: 'app', // <app></app>
  directives: [  ...ROUTER_DIRECTIVES,   XLarge  ],
  styles: [` * { padding:0; margin:0; }`],
  template: `<span x-large>Hello {{ title }}!</span>`
})
export class App {
  title: string = 'Tang Guichuan';
  data = {};
  server: string;

  constructor(public http: Http) { }

  ngOnInit() {
    setTimeout(() => {
      this.server = 'This was rendered from the server!';
    }, 10);
    // use services for http calls
    this.http.get('/data.json')
      .subscribe(res => {
        this.data = res.json();
      });
  }
}

app.component.ts ( just Angular 2 )

Preboot.js

1.preboot.complete()

& replayEvent

3.getInlineCode to insert into the server view.

2.prebootstrap.toString() & recordEvent

export function createPrebootHTML(code: string, config?: any): string {
  let html = '';

  html += `
    <style>
    ${ getPrebootCSS(config && config.uglify) }
    </style>
  `;

  html += `
    <script>
    ${ code }
    </script>
  `;

  if (config && config.start === true) {
    html += '<script>\n preboot.start(); \n</script>';
  }

  return html;
}

universal/src/node/ng_preboot.ts

<!doctype html>
<html lang="en">
<head>
  <title>Angular 2 Universal Starter</title>
  <meta charset="UTF-8">
  <meta name="description" content="Angular 2 Universal">
  <link rel="icon" href="data:;base64,iVBORw0KGgo=">
  <base href="/">
</head>
<body>
  <app>
    Loading Universal ...
  </app>
  <!-- In server.ts: 
    app.use(express.static(
        path.join(ROOT,'dist/client'),
        {index: false}
    )); -->
  <script src="/index.js"></script>
</body>
</html>

index.html

<!DOCTYPE html><html lang="en"><head>
  <title>Angular 2 Universal Starter</title>
  <meta charset="UTF-8">
  <meta name="description" content="Angular 2 Universal">
  <link rel="icon" href="data:;base64,iVBORw0KGgo=">
  <base href="/">

<style>*[_ngcontent-yyj-1] { padding:0; margin:0; }
    #universal[_ngcontent-yyj-1] { text-align:center; font-weight:bold; padding:15px 0; }
}</style>
</head><body>
  <app _nghost-yyj-1="">
  <h3 _ngcontent-yyj-1="" id="universal">Angular2 Universal</h3>
  </app>

    <style>
    .preboot-overlay{background:grey;opacity:.27}@keyframes spin{to{transform:rotate(1turn)}}.preboot-spinner{position:relative;display:inline-block;width:5em;height:5em;margin:0 .5em;font-size:12px;text-indent:999em;overflow:hidden;animation:spin 1s infinite steps(8)}
    </style>
  
    <script>
    !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var ]});
    </script>

    <script>
    preboot.start();
    </script>

    <script src="/index.js"></script>

</body></html>

index.html (with prebootJS)

import {prebootComplete} from 'angular2-universal';
import {ngApp} from './main.browser';

// on document ready bootstrap Angular 2
document.addEventListener('DOMContentLoaded', () => {
  ngApp()
    .then(prebootComplete);
});

client.ts

② Browser Line

import {bootstrap} from '@angular/platform-browser-dynamic';
import { provideRouter } from '@angular/router';
import { HTTP_PROVIDERS } from '@angular/http';

// Application
import {App} from './app/app.component';
import {routes} from './app/app.routes';

// you must return bootstrap for client.ts
export function ngApp() {
  return bootstrap(App, [
    ...HTTP_PROVIDERS,
    provideRouter(routes)
  ]);
}

main.browser.ts

export function prebootComplete(value?: any) {
  if ('preboot' in window && !prebootCompleted) {
    (<any>window).preboot.complete();
  }
  return value;
}

universal/src/browser/bootstrap.ts

  return {
    complete: complete,
    completeApp: completeApp,
    replayEvent: replayEvent,
    switchBuffer: switchBuffer,
    cleanup: cleanup,
    setFocus: setFocus,
    findClientNode: findClientNode,
    getNodeKey: getNodeKey
  };

preboot/src/browser/preboot_browser.ts

< 1s

interaction

Universal

Mechanism

Angular 1.x SSR?

DOM, jqLite

Adapter Pattern

Angular 1.x SSR in Github:

  • runvnc/angular-on-server
  • a-lucas/angular.js-server

      ...

Application(Components...)

Renderer(createElement, Event handlers..)

DomAdapter(Abstract DOM API)

Dom

Renderer

Native、

Web Worker...

ServerDom

Renderer

Browser

Node.js (Server View)

Parse5DomAdapter

BrowserDomAdapter

Document

pase5

extend

implement

implement

use

use

 

Bootloader

(bootstrap)

 

 

Rendering

Engine

(express...)

 

configure

① Abstract UI Layer      ② Platform Layer      ③ Runtime Layer

white: application

yellow: angular2

purple: execution environment

blue: angular-universal

Cross  platform

"Nothing

was achieved

in the comfort zone."

More

More

Universal

Universal CLI

Pre-rendering

At Build Time

import * as gulp from 'gulp';
import {gulpPrerender} from '@angular/universal';
import {App} from './app';

gulp.task("prerender", () => {
    return gulp.src(['index.html'])
        .pipe(gulpPrerender({
            directives: [App]
        }));
});

Killer feature

DEPENDENCY INJECTION

Use localStorage?

Same Interface,

Different implement,

Environmental Identity

isBrowser,isNoew

More

Back End

Universal Support :  

  • Node.js ( Express, hapi)
  • Asp.net 

Plan:

  • Java
  • PHP(Best Lang?)
  • Node: KoaJS 1 ( 2)

More

Optimize

  • node-cache (& Universal cache LRU)
  • Front End Optimization

  ( Without localstorage)

Call  HTTP request twice?

  • Lazy loading (Chunk):

      Webpack Code Splitting

Others?

  • Aot & tree-shaking

Best Practice

/////////////////////////
// ** Example Directive
// Notice we don't touch the Element directly

@Directive({
  selector: '[x-large]'
})
export class XLarge {
  constructor(element: ElementRef, renderer: Renderer) {
    // ** IMPORTANT **
    // we must interact with the dom through -Renderer-
    // for webworker/server to see the changes
    renderer.setElementStyle(element.nativeElement, 'fontSize', 'x-large');
    // ^^
  }
}

app.component.ts

DO NOT USE Global Namespace such as navigator or document from browser.

In FinTech

More and More?

<thank-you  author="汤桂川" [date]="'2016.12'">

</thank-you>

Email: tangguichuan@gmail.com

Universal Angular 2 In Action

By Tang Guichuan

Universal Angular 2 In Action

universal angular 2 in action

  • 1,721