Created
March 13, 2017 13:02
-
-
Save ocombe/8af9d555ab2da45cd1042ef2ccb0ef6b to your computer and use it in GitHub Desktop.
ng2-translate + universal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* THIS IS TEMPORARY TO PATCH 2.1.1+ Core bugs | |
*/ | |
/* tslint:disable */ | |
let __compiler__ = require('@angular/compiler'); | |
import {__platform_browser_private__} from '@angular/platform-browser'; | |
import {__core_private__} from '@angular/core'; | |
let patch = false; | |
if(!__core_private__['ViewUtils']) { | |
patch = true; | |
__core_private__['ViewUtils'] = __core_private__['view_utils']; | |
} | |
if(__compiler__ && __compiler__.SelectorMatcher && __compiler__.CssSelector) { | |
patch = true; | |
(__compiler__).__compiler_private__ = { | |
SelectorMatcher: __compiler__.SelectorMatcher, | |
CssSelector: __compiler__.CssSelector | |
} | |
} | |
if(patch) { | |
var __universal__ = require('angular2-platform-node/__private_imports__'); | |
__universal__.ViewUtils = __core_private__['view_utils']; | |
__universal__.CssSelector = __universal__.CssSelector || __compiler__.CssSelector; | |
__universal__.SelectorMatcher = __universal__.SelectorMatcher || __compiler__.SelectorMatcher; | |
} | |
// Fix Material Support | |
function universalMaterialSupports(eventName: string): boolean { | |
return Boolean(this.isCustomEvent(eventName)); | |
} | |
__platform_browser_private__.HammerGesturesPlugin.prototype.supports = universalMaterialSupports; | |
// End Fix Material Support | |
// Fix Universal Style | |
import {NodeDomRootRenderer, NodeDomRenderer} from 'angular2-universal/node'; | |
function renderComponentFix(componentProto: any) { | |
return new NodeDomRenderer(this, componentProto, this._animationDriver); | |
} | |
NodeDomRootRenderer.prototype.renderComponent = renderComponentFix; | |
// End Fix Universal Style |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {NgModule} from '@angular/core'; | |
import {FormsModule} from '@angular/forms'; | |
import {RouterModule} from '@angular/router'; | |
import {UniversalModule, isBrowser, isNode} from 'angular2-universal/node'; // for AoT we need to manually split universal packages | |
import {AppModule, AppComponent} from '../app/app.module'; | |
import {SharedModule} from '../app/shared/shared.module'; | |
import {CacheService} from '../app/shared/cache.service'; | |
// Will be merged into @angular/platform-browser in a later release | |
// see https://github.com/angular/angular/pull/12322 | |
import {Meta} from '../misc/angular2-meta'; | |
import {TranslateModule, TranslateLoader} from "ng2-translate"; | |
import {TranslateUniversalLoader} from "./translateUniversalLoader.service"; | |
import {Title} from "@angular/platform-browser"; | |
import {Lang} from "../app/services/lang"; | |
import e = require("express"); | |
import Request = e.Request; | |
declare const Zone: {current: any}; | |
export function getLRU() { | |
return new Map(); | |
} | |
export function getRequest() { | |
return Zone.current.get('req') || {}; | |
} | |
export function getResponse() { | |
return Zone.current.get('res') || {}; | |
} | |
// TODO(gdi2290): refactor into Universal | |
export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE'; | |
export function translateFactory() { | |
return new TranslateUniversalLoader('assets/i18n', '.json'); | |
} | |
export function langFactory(req: Request) { | |
let userLang = req.cookies['hmx-lang']; | |
if(!userLang) { | |
userLang = req.acceptsLanguages('en', 'fr') || 'en'; | |
} | |
return {userLang}; | |
} | |
@NgModule({ | |
bootstrap: [AppComponent], | |
imports: [ | |
// MaterialModule.forRoot() should be included first | |
UniversalModule, // BrowserModule, HttpModule, and JsonpModule are included | |
FormsModule, | |
RouterModule.forRoot([], {useHash: false}), | |
SharedModule.forRoot(), | |
AppModule, | |
TranslateModule.forRoot({ | |
provide: TranslateLoader, | |
useFactory: translateFactory | |
}) | |
], | |
providers: [ | |
{provide: 'isBrowser', useValue: isBrowser}, | |
{provide: 'isNode', useValue: isNode}, | |
{provide: 'req', useFactory: getRequest}, | |
{provide: 'res', useFactory: getResponse}, | |
{provide: 'LRU', useFactory: getLRU, deps: []}, | |
CacheService, | |
Meta, | |
Title, | |
{provide: Lang, useFactory: langFactory, deps: ['req']} | |
] | |
}) | |
export class MainModule { | |
constructor(public cache: CacheService) { | |
} | |
/** | |
* We need to use the arrow function here to bind the context as this is a gotcha | |
* in Universal for now until it's fixed | |
*/ | |
universalDoDehydrate = (universalCache) => { | |
universalCache[CacheService.KEY] = JSON.stringify(this.cache.dehydrate()); | |
} | |
/** | |
* Clear the cache after it's rendered | |
*/ | |
universalAfterDehydrate = () => { | |
// comment out if LRU provided at platform level to be shared between each user | |
this.cache.clear(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// the polyfills must be one of the first things imported in node.js. | |
// The only modules to be imported higher - node modules with es6-promise 3.x or other Promise polyfill dependency | |
// (rule of thumb: do it if you have zone.js exception that it has been overwritten) | |
// if you are including modules that modify Promise, such as NewRelic,, you must include them before polyfills | |
import 'angular2-universal-polyfills'; | |
import 'ts-helpers'; | |
import '../misc/__workaround.node'; // temporary until 2.1.1 things are patched in Core | |
import * as path from 'path'; | |
import * as express from 'express'; | |
import * as bodyParser from 'body-parser'; | |
import * as cookieParser from 'cookie-parser'; | |
import * as morgan from 'morgan'; | |
import * as mcache from 'memory-cache'; | |
import * as compression from 'compression'; | |
const interceptor = require('express-interceptor'); | |
declare const Zone: { current: any }; | |
// Angular 2 | |
import {enableProdMode} from '@angular/core'; | |
// Angular 2 Universal | |
import {createEngine} from 'angular2-express-engine'; | |
// App | |
import {MainModule} from './node.module'; | |
// enable prod for faster renders | |
enableProdMode(); | |
const app = express(); | |
const ROOT = path.join(path.resolve(__dirname, '../..')); | |
// Express View | |
app.engine('.html', createEngine()); | |
app.set('port', process.env.PORT || 3000); | |
app.set('views', __dirname); | |
app.set('view engine', 'html'); | |
app.set('json spaces', 2); | |
app.use(compression()); | |
app.use(cookieParser('Angular 2 Universal')); | |
app.use(bodyParser.json()); | |
// in prod, compress requests | |
if(process.env.ENV === 'prod') { | |
app.use(interceptor((req, res) => ({ | |
// don't compress responses with this request header | |
isInterceptable: function() { | |
return !req.headers['x-no-compression'] && /text\/html/.test(res.get('Content-Type')); | |
}, | |
intercept: (body, send) => { | |
let buffer = new Buffer(body); | |
let userLang = req.cookies['hmx-lang']; | |
if(!userLang) { | |
userLang = req.acceptsLanguages('en', 'fr') || 'en'; | |
} | |
// url specific key for response cache, based on user lang | |
const key = '__response__' + (req.originalUrl || req.url) + '/' + userLang; | |
// check if cache exists | |
if(mcache.get(key) === null) { | |
mcache.put(key, buffer); | |
} else { | |
buffer = mcache.get(key); | |
} | |
send(buffer); | |
} | |
}))); | |
} | |
app.use(morgan(<any>'dev')); | |
function cacheControl(req, res, next) { | |
// instruct browser to revalidate in 1h | |
res.header('Cache-Control', 'max-age=3600000'); | |
next(); | |
} | |
// Serve static files | |
app.use(cacheControl, express.static(ROOT, {index: false})); | |
process.on('uncaughtException', function(err) { | |
console.error('Catching uncaught errors to avoid process crash', err); | |
}); | |
function ngApp(req, res) { | |
function onHandleError(parentZoneDelegate, currentZone, targetZone, error) { | |
console.warn('Error in SSR, serving for direct CSR'); | |
console.error(error); | |
res.sendFile(path.join(ROOT, 'index.html')); | |
return false; | |
} | |
Zone.current.fork({name: 'CSR fallback', onHandleError}).run(() => { | |
res.render(path.join(ROOT, 'index'), { | |
req, | |
res, | |
// time: true, // use this to determine what part of your app is slow only in development | |
preboot: false, | |
baseUrl: '/', | |
requestUrl: req.originalUrl, | |
originUrl: `http://localhost:${ app.get('port') }`, | |
ngModule: MainModule | |
}); | |
}); | |
} | |
/** | |
* use universal for specific routes | |
*/ | |
app.use('/', ngApp); | |
// Server | |
let server = app.listen(app.get('port'), () => { | |
console.log(`Listening on: http://localhost:${server.address().port}`); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {TranslateLoader} from "ng2-translate"; | |
import {Observable} from "rxjs/Observable"; | |
const fs = require('fs'); | |
export class TranslateUniversalLoader implements TranslateLoader { | |
constructor(private prefix: string = 'i18n', private suffix: string = '.json') {} | |
/** | |
* Gets the translations from the server | |
* @param lang | |
* @returns {any} | |
*/ | |
public getTranslation(lang: string): Observable<any> { | |
return Observable.create(observer => { | |
observer.next(JSON.parse(fs.readFileSync(`${this.prefix}/${lang}${this.suffix}`, 'utf8'))); | |
observer.complete(); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment