Project

General

Profile

1

    
2
<p align="center">
3

    
4
    <img src="https://cloud.githubusercontent.com/assets/1016365/10639063/138338bc-7806-11e5-8057-d34c75f3cafc.png" alt="Universal Angular 2" height="320"/>
5

    
6
</p>
7

    
8
# Angular 2 Universal Starter [![Universal Angular 2](https://img.shields.io/badge/universal-angular2-brightgreen.svg?style=flat)](https://github.com/angular/universal)
9
> Server-Side Rendering for Angular 2
10

    
11
A minimal Angular 2 starter for Universal JavaScript using TypeScript 2 and Webpack 2
12

    
13
> If you're looking for the Angular Universal repo go to [**angular/universal**](https://github.com/angular/universal)  
14

    
15
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
16

    
17

    
18
## Installation
19

    
20
* `npm install`
21

    
22
## Serve
23

    
24
* `npm start` to build your client app and start a web server
25
* `npm run build` to prepare a distributable bundle
26

    
27
## Development
28
* run `npm start` and `npm run watch` in two separate terminals to build your client app, start a web server, and allow file changes to update in realtime
29

    
30
## Watch files
31
* `npm run watch` to build your client app and start a web server
32

    
33
## AoT and Prod
34
* `npm run build:prod:ngc` to compile the ngfactory files and build prod
35

    
36
######
37
npm run build:prod:ngc
38
PORT = XXXX npm run server
39

    
40
## Universal "Gotchas"
41

    
42
> When building Universal components in Angular 2 there are a few things to keep in mind.
43

    
44
 - To use `templateUrl` or `styleUrls` you must use **`angular2-template-loader`** in your TS loaders.
45
    - This is already setup within this starter repo. Look at the webpack.config file [here](https://github.com/angular/universal-starter/blob/master/webpack.config.ts) for details & implementation.
46
 - **`window`**, **`document`**, **`navigator`**, and other browser types - _do not exist on the server_ - so using them, or any library that uses them (jQuery for example) will not work. You do have some options, if you truly need some of this functionality:
47
    - If you need to use them, consider limiting them to only your main.client and wrapping them situationally with the imported *isBrowser / isNode* features from Universal.  `import { isBrowser, isNode } from 'angular2-universal'`;
48
    - Another option is using `DOM` from ["@angular/platform-browser"](https://github.com/angular/angular/blob/e3687706c71beb7c9dbdae1bbb5fbbcea588c476/modules/%40angular/platform-browser/src/dom/dom_adapter.ts#L34)
49
 - **Don't manipulate the nativeElement directly**. Use the _Renderer_. We do this to ensure that in any environment we're able to change our view.
50
```
51
constructor(element: ElementRef, renderer: Renderer) {
52
  renderer.setElementStyle(element.nativeElement, 'font-size', 'x-large');
53
}
54
```
55
 - The application runs XHR requests on the server & once again on the Client-side (when the application bootstraps)
56
    - Use a [UniversalCache](https://github.com/angular/universal-starter/blob/master/src/%2Bapp/shared/model/model.service.ts#L34-L50) instead of regular Http, to save certain requests so they aren't re-ran again on the Client. ([Example useage here](https://github.com/angular/universal-starter/blob/cc71e2d5b2d783f2bb52eebd1b5c6fa0ba23f08a/src/%2Bapp/%2Bhome/home.component.ts#L22-L24))
57
 - Know the difference between attributes and properties in relation to the DOM.
58
 - Keep your directives stateless as much as possible. For stateful directives, you may need to provide an attribute that reflects the corresponding property with an initial string value such as url in img tag. For our native `<img src="">` element the src attribute is reflected as the src property of the element type HTMLImageElement.
59

    
60
### Brotli Compression Support
61

    
62
To enable Brotli compression for server response with fallback for gzip.  Install the following packages
63
```
64
npm install --save-dev iltorb accepts @types/accepts express-interceptor memory-cache @types/memory-cache
65
```
66
and replace the following code from src/server.aot.ts.
67
```
68
  import * as compression from 'compression';
69

    
70
  app.use(compression());
71
```
72
with
73
```
74
import * as mcache from 'memory-cache';
75
const { gzipSync } = require('zlib');
76
const accepts = require('accepts');
77
const { compressSync } = require('iltorb');
78
const interceptor = require('express-interceptor');
79

    
80
app.use(interceptor((req, res)=>({
81
  // don't compress responses with this request header
82
  isInterceptable: () => (!req.headers['x-no-compression']),
83
  intercept: ( body, send ) => {
84
    const encodings  = new Set(accepts(req).encodings());
85
    const bodyBuffer = new Buffer(body);
86
    // url specific key for response cache
87
    const key = '__response__' + req.originalUrl || req.url;
88
    let output = bodyBuffer;
89
    // check if cache exists
90
    if (mcache.get(key) === null) {
91
      // check for encoding support
92
      if (encodings.has('br')) {
93
        // brotli
94
        res.setHeader('Content-Encoding', 'br');
95
        output = compressSync(bodyBuffer);
96
        mcache.put(key, {output, encoding: 'br'});
97
      } else if (encodings.has('gzip')) {
98
        // gzip
99
        res.setHeader('Content-Encoding', 'gzip');
100
        output = gzipSync(bodyBuffer);
101
        mcache.put(key, {output, encoding: 'gzip'});
102
      }
103
    } else {
104
      const { output, encoding } = mcache.get(key);
105
      if(encodings.has(encoding)){
106
          res.setHeader('Content-Encoding', encoding);
107
          send(output);
108
          return;
109
      }
110
    }
111
    send(output);
112
  }
113
})));
114
```
115
this will check the support, compress and cache the response.
116

    
117
## Edge case of server compatibility with Promise polyfills
118

    
119
If you have node modules with promise polyfill dependency on server - there is chance to get the following exception:
120
```
121
Error: Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.
122
```
123
It occurs because [Zone.js](https://github.com/angular/zone.js/) Promise implementation is not
124
detected as Promise by some polyfills (e.g. [es6-promise](https://github.com/stefanpenner/es6-promise) before 4.x).
125

    
126
To sort it out, you need such polyfills initialized before zone.js. Zone.js is initialized in 'angular2-universal-polyfills'
127
import of [server.ts](https://github.com/angular/universal-starter/blob/master/src/server.ts#L4). So import problematic
128
modules before this line.
129

    
130
### Documentation
131
[Design Doc](https://docs.google.com/document/d/1q6g9UlmEZDXgrkY88AJZ6MUrUxcnwhBGS0EXbVlYicY)
132

    
133
### Videos
134
Angular 2 Universal Patterns - ng-conf, May 2016  
135
[![Angular 2 Universal Patterns](http://img.youtube.com/vi/TCj_oC3m6_U/0.jpg)](https://www.youtube.com/watch?v=TCj_oC3m6_U)
136

    
137
Angular Universal Source Code - ReadTheSource, January 2016  
138
[![Angular Universal Source Code](http://img.youtube.com/vi/qOjtFjXoebY/0.jpg)](https://www.youtube.com/watch?v=qOjtFjXoebY)
139

    
140
Full Stack Angular 2 - AngularConnect, Oct 2015  
141
[![Full Stack Angular 2](https://img.youtube.com/vi/MtoHFDfi8FM/0.jpg)](https://www.youtube.com/watch?v=MtoHFDfi8FM)
142

    
143
Angular 2 Server Rendering - Angular U, July 2015  
144
[![Angular 2 Server Rendering](http://img.youtube.com/vi/0wvZ7gakqV4/0.jpg)](http://www.youtube.com/watch?v=0wvZ7gakqV4)
145

    
146
## [preboot.js](https://github.com/angular/preboot)
147
> Control server-rendered page and transfer state before client-side web app loads to the client-side-app.
148

    
149
# License
150
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](/LICENSE)
(3-3/13)