Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@ocombe
Created March 13, 2017 13:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ocombe/8af9d555ab2da45cd1042ef2ccb0ef6b to your computer and use it in GitHub Desktop.
Save ocombe/8af9d555ab2da45cd1042ef2ccb0ef6b to your computer and use it in GitHub Desktop.
ng2-translate + universal
/*
* 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
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();
}
}
// 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}`);
});
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