mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-16 22:43:03 +00:00
initial commit
This commit is contained in:
13
src/+app/+about/about-routing.module.ts
Normal file
13
src/+app/+about/about-routing.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { AboutComponent } from './about.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: 'about', component: AboutComponent }
|
||||
])
|
||||
]
|
||||
})
|
||||
export class AboutRoutingModule { }
|
14
src/+app/+about/about.component.ts
Normal file
14
src/+app/+about/about.component.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Component, Inject, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
encapsulation: ViewEncapsulation.Emulated,
|
||||
selector: 'about',
|
||||
template: 'About component'
|
||||
})
|
||||
export class AboutComponent {
|
||||
constructor(@Inject('req') req: any) {
|
||||
// console.log('req', req)
|
||||
|
||||
}
|
||||
}
|
16
src/+app/+about/about.module.ts
Normal file
16
src/+app/+about/about.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { AboutComponent } from './about.component';
|
||||
import { AboutRoutingModule } from './about-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
AboutRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
AboutComponent
|
||||
]
|
||||
})
|
||||
export class AboutModule { }
|
13
src/+app/+home/home-routing.module.ts
Normal file
13
src/+app/+home/home-routing.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { HomeComponent } from './home.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: 'home', component: HomeComponent }
|
||||
])
|
||||
]
|
||||
})
|
||||
export class HomeRoutingModule { }
|
8
src/+app/+home/home.component.css
Normal file
8
src/+app/+home/home.component.css
Normal file
@@ -0,0 +1,8 @@
|
||||
blockquote {
|
||||
border-left:5px #158126 solid;
|
||||
background:#fff;
|
||||
padding:20px 20px 20px 40px;
|
||||
}
|
||||
blockquote::before {
|
||||
left: 1em;
|
||||
}
|
6
src/+app/+home/home.component.html
Normal file
6
src/+app/+home/home.component.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<div class="home">
|
||||
Home component
|
||||
<strong>Async data call return value:</strong>
|
||||
<pre>{{ data | json }}</pre>
|
||||
<blockquote>{{ data.data }}</blockquote>
|
||||
</div>
|
27
src/+app/+home/home.component.ts
Normal file
27
src/+app/+home/home.component.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Component, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
import { ModelService } from '../shared/model/model.service';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
encapsulation: ViewEncapsulation.Emulated,
|
||||
selector: 'home',
|
||||
styleUrls: [ './home.component.css' ],
|
||||
templateUrl: './home.component.html'
|
||||
})
|
||||
export class HomeComponent {
|
||||
data: any = {};
|
||||
constructor(public model: ModelService) {
|
||||
|
||||
// we need the data synchronously for the client to set the server response
|
||||
// we create another method so we have more control for testing
|
||||
this.universalInit();
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
this.model.get('/data.json').subscribe(data => {
|
||||
this.data = data;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
16
src/+app/+home/home.module.ts
Normal file
16
src/+app/+home/home.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { HomeComponent } from './home.component';
|
||||
import { HomeRoutingModule } from './home-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
HomeRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
HomeComponent
|
||||
]
|
||||
})
|
||||
export class HomeModule { }
|
13
src/+app/+lazy/lazy-routing.module.ts
Normal file
13
src/+app/+lazy/lazy-routing.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { LazyComponent } from './lazy.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: '', component: LazyComponent }
|
||||
])
|
||||
]
|
||||
})
|
||||
export class LazyRoutingModule { }
|
14
src/+app/+lazy/lazy.component.ts
Normal file
14
src/+app/+lazy/lazy.component.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Component, Inject, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
encapsulation: ViewEncapsulation.Emulated,
|
||||
selector: 'lazy',
|
||||
template: `
|
||||
<p>
|
||||
Lazy component
|
||||
</p>
|
||||
`
|
||||
})
|
||||
export class LazyComponent {
|
||||
}
|
16
src/+app/+lazy/lazy.module.ts
Normal file
16
src/+app/+lazy/lazy.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { LazyComponent } from './lazy.component';
|
||||
import { LazyRoutingModule } from './lazy-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
LazyRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
LazyComponent
|
||||
]
|
||||
})
|
||||
export class LazyModule { }
|
13
src/+app/+todo/todo-routing.module.ts
Normal file
13
src/+app/+todo/todo-routing.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { TodoComponent } from './todo.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: 'todo', component: TodoComponent }
|
||||
])
|
||||
]
|
||||
})
|
||||
export class TodoRoutingModule { }
|
43
src/+app/+todo/todo.component.ts
Normal file
43
src/+app/+todo/todo.component.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Component, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
import { ModelService } from '../shared/model/model.service';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
encapsulation: ViewEncapsulation.Emulated,
|
||||
selector: 'todo',
|
||||
styles: [`
|
||||
`],
|
||||
template: `
|
||||
<div class="todo">
|
||||
Todo component
|
||||
<form #f="ngForm" (ngSubmit)="addTodo(newTodo)">
|
||||
<input name="newTodo" [(ngModel)]="newTodo">
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
<ul>
|
||||
<li *ngFor="let todo of todos">
|
||||
{{ todo }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class TodoComponent {
|
||||
newTodo = '';
|
||||
todos = [];
|
||||
constructor(public model: ModelService) {
|
||||
// we need the data synchronously for the client to set the server response
|
||||
// we create another method so we have more control for testing
|
||||
this.universalInit();
|
||||
}
|
||||
|
||||
addTodo(newTodo) {
|
||||
this.todos.push(newTodo);
|
||||
this.newTodo = '';
|
||||
}
|
||||
|
||||
universalInit() {
|
||||
}
|
||||
|
||||
}
|
16
src/+app/+todo/todo.module.ts
Normal file
16
src/+app/+todo/todo.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { TodoComponent } from './todo.component';
|
||||
import { TodoRoutingModule } from './todo-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SharedModule,
|
||||
TodoRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
TodoComponent
|
||||
]
|
||||
})
|
||||
export class TodoModule { }
|
17
src/+app/app-routing.module.ts
Normal file
17
src/+app/app-routing.module.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
export function getLazyModule() {
|
||||
return System.import('./+lazy/lazy.module' + (process.env.AOT ? '.ngfactory' : ''))
|
||||
.then(mod => mod[(process.env.AOT ? 'LazyModuleNgFactory' : 'LazyModule')]);
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||
{ path: 'lazy', loadChildren: getLazyModule }
|
||||
])
|
||||
],
|
||||
})
|
||||
export class AppRoutingModule { }
|
66
src/+app/app.component.ts
Normal file
66
src/+app/app.component.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Component, Directive, ElementRef, Renderer, ChangeDetectionStrategy, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
//
|
||||
/////////////////////////
|
||||
// ** Example Directive
|
||||
// Notice we don't touch the Element directly
|
||||
|
||||
@Directive({
|
||||
selector: '[xLarge]'
|
||||
})
|
||||
export class XLargeDirective {
|
||||
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');
|
||||
// ^^
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
encapsulation: ViewEncapsulation.Emulated,
|
||||
selector: 'app',
|
||||
styles: [`
|
||||
* { padding:0; margin:0; font-family: 'Droid Sans', sans-serif; }
|
||||
#universal { text-align:center; font-weight:bold; padding:15px 0; }
|
||||
nav { background:#158126; min-height:40px; border-bottom:5px #046923 solid; }
|
||||
nav a { font-weight:bold; text-decoration:none; color:#fff; padding:20px; display:inline-block; }
|
||||
nav a:hover { background:#00AF36; }
|
||||
.hero-universal { min-height:500px; display:block; padding:20px; background: url('/assets/logo.png') no-repeat center center; }
|
||||
.inner-hero { background: rgba(255, 255, 255, 0.75); border:5px #ccc solid; padding:25px; }
|
||||
.router-link-active { background-color: #00AF36; }
|
||||
main { padding:20px 0; }
|
||||
pre { font-size:12px; }
|
||||
`],
|
||||
template: `
|
||||
<h3 id="universal">Angular2 Universal</h3>
|
||||
<nav>
|
||||
<a routerLinkActive="router-link-active" routerLink="home">Home</a>
|
||||
<a routerLinkActive="router-link-active" routerLink="about">About</a>
|
||||
<a routerLinkActive="router-link-active" routerLink="todo">Todo</a>
|
||||
<a routerLinkActive="router-link-active" routerLink="lazy">Lazy</a>
|
||||
</nav>
|
||||
<div class="hero-universal">
|
||||
<div class="inner-hero">
|
||||
<div>
|
||||
<span xLarge>Universal JavaScript {{ title }}!</span>
|
||||
</div>
|
||||
|
||||
Two-way binding: <input type="text" [value]="title" (input)="title = $event.target.value">
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<strong>Router-outlet:</strong>
|
||||
<main>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'ftw';
|
||||
}
|
27
src/+app/app.module.ts
Executable file
27
src/+app/app.module.ts
Executable file
@@ -0,0 +1,27 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { HomeModule } from './+home/home.module';
|
||||
import { AboutModule } from './+about/about.module';
|
||||
import { TodoModule } from './+todo/todo.module';
|
||||
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent, XLargeDirective } from './app.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [ AppComponent, XLargeDirective ],
|
||||
imports: [
|
||||
SharedModule,
|
||||
HomeModule,
|
||||
AboutModule,
|
||||
TodoModule,
|
||||
AppRoutingModule
|
||||
]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
|
||||
export { AppComponent } from './app.component';
|
29
src/+app/shared/api.service.ts
Normal file
29
src/+app/shared/api.service.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http } from '@angular/http';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/observable/throw';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/catch';
|
||||
|
||||
import { CacheService } from './cache.service';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class ApiService {
|
||||
constructor(public _http: Http) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* whatever domain/feature method name
|
||||
*/
|
||||
get(url: string, options?: any) {
|
||||
return this._http.get(url, options)
|
||||
.map(res => res.json())
|
||||
.catch(err => {
|
||||
console.log('Error: ', err);
|
||||
return Observable.throw(err);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
88
src/+app/shared/cache.service.ts
Normal file
88
src/+app/shared/cache.service.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Inject, Injectable, isDevMode } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class CacheService {
|
||||
static KEY = 'CacheService';
|
||||
|
||||
constructor(@Inject('LRU') public _cache: Map<string, any>) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* check if there is a value in our store
|
||||
*/
|
||||
has(key: string | number): boolean {
|
||||
let _key = this.normalizeKey(key);
|
||||
return this._cache.has(_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* store our state
|
||||
*/
|
||||
set(key: string | number, value: any): void {
|
||||
let _key = this.normalizeKey(key);
|
||||
this._cache.set(_key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* get our cached value
|
||||
*/
|
||||
get(key: string | number): any {
|
||||
let _key = this.normalizeKey(key);
|
||||
return this._cache.get(_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* release memory refs
|
||||
*/
|
||||
clear(): void {
|
||||
this._cache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* convert to json for the client
|
||||
*/
|
||||
dehydrate(): any {
|
||||
let json = {};
|
||||
this._cache.forEach((value: any, key: string) => json[key] = value);
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert server json into out initial state
|
||||
*/
|
||||
rehydrate(json: any): void {
|
||||
Object.keys(json).forEach((key: string) => {
|
||||
let _key = this.normalizeKey(key);
|
||||
let value = json[_key];
|
||||
this._cache.set(_key, value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* allow JSON.stringify to work
|
||||
*/
|
||||
toJSON(): any {
|
||||
return this.dehydrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* convert numbers into strings
|
||||
*/
|
||||
normalizeKey(key: string | number): string {
|
||||
if (isDevMode() && this._isInvalidValue(key)) {
|
||||
throw new Error('Please provide a valid key to save in the CacheService');
|
||||
}
|
||||
|
||||
return key + '';
|
||||
}
|
||||
|
||||
_isInvalidValue(key): boolean {
|
||||
return key === null ||
|
||||
key === undefined ||
|
||||
key === 0 ||
|
||||
key === '' ||
|
||||
typeof key === 'boolean' ||
|
||||
Number.isNaN(<number>key);
|
||||
}
|
||||
}
|
55
src/+app/shared/model/model.service.ts
Normal file
55
src/+app/shared/model/model.service.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/observable/of';
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/share';
|
||||
|
||||
import { CacheService } from '../cache.service';
|
||||
import { ApiService } from '../api.service';
|
||||
|
||||
export function hashCodeString(str: string): string {
|
||||
let hash = 0;
|
||||
if (str.length === 0) {
|
||||
return hash + '';
|
||||
}
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
let char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash; // Convert to 32bit integer
|
||||
}
|
||||
return hash + '';
|
||||
}
|
||||
|
||||
// domain/feature service
|
||||
@Injectable()
|
||||
export class ModelService {
|
||||
// This is only one example of one Model depending on your domain
|
||||
constructor(public _api: ApiService, public _cache: CacheService) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* whatever domain/feature method name
|
||||
*/
|
||||
get(url) {
|
||||
// you want to return the cache if there is a response in it.
|
||||
// This would cache the first response so if your API isn't idempotent
|
||||
// you probably want to remove the item from the cache after you use it. LRU of 10
|
||||
// you can use also hashCodeString here
|
||||
let key = url;
|
||||
|
||||
if (this._cache.has(key)) {
|
||||
return Observable.of(this._cache.get(key));
|
||||
}
|
||||
// you probably shouldn't .share() and you should write the correct logic
|
||||
return this._api.get(url)
|
||||
.do(json => {
|
||||
this._cache.set(key, json);
|
||||
})
|
||||
.share();
|
||||
}
|
||||
// don't cache here since we're creating
|
||||
create() {
|
||||
// TODO
|
||||
}
|
||||
}
|
52
src/+app/shared/shared.module.ts
Normal file
52
src/+app/shared/shared.module.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ApiService } from './api.service';
|
||||
import { ModelService } from './model/model.service';
|
||||
|
||||
const MODULES = [
|
||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||
CommonModule,
|
||||
RouterModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule
|
||||
];
|
||||
|
||||
const PIPES = [
|
||||
// put pipes here
|
||||
];
|
||||
|
||||
const COMPONENTS = [
|
||||
// put shared components here
|
||||
];
|
||||
|
||||
const PROVIDERS = [
|
||||
ModelService,
|
||||
ApiService
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
...MODULES
|
||||
],
|
||||
declarations: [
|
||||
...PIPES,
|
||||
...COMPONENTS
|
||||
],
|
||||
exports: [
|
||||
...MODULES,
|
||||
...PIPES,
|
||||
...COMPONENTS
|
||||
]
|
||||
})
|
||||
export class SharedModule {
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: SharedModule,
|
||||
providers: [
|
||||
...PROVIDERS
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
21
src/__workaround.browser.ts
Normal file
21
src/__workaround.browser.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
/*
|
||||
* 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';
|
||||
if (!__core_private__['ViewUtils']) {
|
||||
__core_private__['ViewUtils'] = __core_private__['view_utils'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (__compiler__ && __compiler__.SelectorMatcher && __compiler__.CssSelector) {
|
||||
(__compiler__).__compiler_private__ = {
|
||||
SelectorMatcher: __compiler__.SelectorMatcher,
|
||||
CssSelector: __compiler__.CssSelector
|
||||
}
|
||||
}
|
44
src/__workaround.node.ts
Normal file
44
src/__workaround.node.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
/*
|
||||
* 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
|
194
src/angular2-meta.ts
Normal file
194
src/angular2-meta.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injectable} from '@angular/core';
|
||||
// es6-modules are used here
|
||||
import {DomAdapter, getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
||||
/**
|
||||
* Represent meta element.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```ts
|
||||
* { name: 'application-name', content: 'Name of my application' },
|
||||
* { name: 'description', content: 'A description of the page', id: 'desc' }
|
||||
* // ...
|
||||
* // Twitter
|
||||
* { name: 'twitter:title', content: 'Content Title' }
|
||||
* // ...
|
||||
* // Google+
|
||||
* { itemprop: 'name', content: 'Content Title' },
|
||||
* { itemprop: 'description', content: 'Content Title' }
|
||||
* // ...
|
||||
* // Facebook / Open Graph
|
||||
* { property: 'fb:app_id', content: '123456789' },
|
||||
* { property: 'og:title', content: 'Content Title' }
|
||||
* ```
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface MetaDefinition {
|
||||
charset?: string;
|
||||
content?: string;
|
||||
httpEquiv?: string;
|
||||
id?: string;
|
||||
itemprop?: string;
|
||||
name?: string;
|
||||
property?: string;
|
||||
scheme?: string;
|
||||
url?: string;
|
||||
[prop: string]: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A service that can be used to get and add meta tags.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Injectable()
|
||||
export class Meta {
|
||||
private _dom: DomAdapter = getDOM();
|
||||
|
||||
/**
|
||||
* Adds a new meta tag to the dom.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```ts
|
||||
* const name: MetaDefinition = {name: 'application-name', content: 'Name of my application'};
|
||||
* const desc: MetaDefinition = {name: 'description', content: 'A description of the page'};
|
||||
* const tags: HTMLMetaElement[] = this.meta.addTags([name, desc]);
|
||||
* ```
|
||||
*
|
||||
* @param tags
|
||||
* @returns {HTMLMetaElement[]}
|
||||
*/
|
||||
addTags(...tags: Array<MetaDefinition|MetaDefinition[]>): HTMLMetaElement[] {
|
||||
const presentTags = this._flattenArray(tags);
|
||||
if (presentTags.length === 0) return [];
|
||||
return presentTags.map((tag: MetaDefinition) => this._addInternal(tag));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the meta tag by the given selector. Returns element or null
|
||||
* if there's no such meta element.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```ts
|
||||
* const meta: HTMLMetaElement = this.meta.getTag('name=description');
|
||||
* const twitterMeta: HTMLMetaElement = this.meta.getTag('name="twitter:title"');
|
||||
* const fbMeta: HTMLMetaElement = this.meta.getTag('property="fb:app_id"');
|
||||
* ```
|
||||
*
|
||||
* @param selector
|
||||
* @returns {HTMLMetaElement}
|
||||
*/
|
||||
getTag(selector: string): HTMLMetaElement {
|
||||
if (!selector) return null;
|
||||
return this._dom.query(`meta[${selector}]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the meta tag with the given selector.
|
||||
*
|
||||
* * ### Example
|
||||
*
|
||||
* ```ts
|
||||
* const meta: HTMLMetaElement = this.meta.updateTag('name=description', {name: 'description',
|
||||
* content: 'New description'});
|
||||
* console.log(meta.content); // 'New description'
|
||||
* ```
|
||||
*
|
||||
* @param selector
|
||||
* @param tag updated tag definition
|
||||
* @returns {HTMLMetaElement}
|
||||
*/
|
||||
updateTag(selector: string, tag: MetaDefinition): HTMLMetaElement {
|
||||
const meta: HTMLMetaElement = this.getTag(selector);
|
||||
if (!meta) {
|
||||
// create element if it doesn't exist
|
||||
return this._addInternal(tag);
|
||||
}
|
||||
return this._prepareMetaElement(tag, meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes meta tag with the given selector from the dom.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```ts
|
||||
* this.meta.removeTagBySelector('name=description');
|
||||
* ```
|
||||
*
|
||||
* @param selector
|
||||
*/
|
||||
removeTagBySelector(selector: string): void {
|
||||
const meta: HTMLMetaElement = this.getTag(selector);
|
||||
this.removeTagElement(meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes given meta element from the dom.
|
||||
*
|
||||
* ### Example
|
||||
* ```ts
|
||||
* const elem: HTMLMetaElement = this.meta.getTag('name=description');
|
||||
* this.meta.removeTagElement(elem);
|
||||
* ```
|
||||
*
|
||||
* @param meta meta element
|
||||
*/
|
||||
removeTagElement(meta: HTMLMetaElement): void {
|
||||
if (meta) {
|
||||
this._removeMetaElement(meta);
|
||||
}
|
||||
}
|
||||
|
||||
private _addInternal(tag: MetaDefinition): HTMLMetaElement {
|
||||
const meta: HTMLMetaElement = this._createMetaElement();
|
||||
this._prepareMetaElement(tag, meta);
|
||||
this._appendMetaElement(meta);
|
||||
return meta;
|
||||
}
|
||||
|
||||
private _createMetaElement(): HTMLMetaElement {
|
||||
return this._dom.createElement('meta') as HTMLMetaElement;
|
||||
}
|
||||
|
||||
private _prepareMetaElement(tag: MetaDefinition, el: HTMLMetaElement): HTMLMetaElement {
|
||||
Object.keys(tag).forEach((prop: string) => this._dom.setAttribute(el, prop, tag[prop]));
|
||||
return el;
|
||||
}
|
||||
|
||||
private _appendMetaElement(meta: HTMLMetaElement): void {
|
||||
const head = this._dom.getElementsByTagName(this._dom.defaultDoc(), 'head')[0];
|
||||
this._dom.appendChild(head, meta);
|
||||
}
|
||||
|
||||
private _removeMetaElement(meta: HTMLMetaElement): void {
|
||||
const head = this._dom.parentElement(meta);
|
||||
this._dom.removeChild(head, meta);
|
||||
}
|
||||
|
||||
private _flattenArray(input: any[], out: any[] = []): any[] {
|
||||
if (input) {
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const item: any = input[i];
|
||||
if (Array.isArray(item)) {
|
||||
this._flattenArray(item, out);
|
||||
} else if (item) {
|
||||
out.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
106
src/backend/api.ts
Normal file
106
src/backend/api.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
var util = require('util');
|
||||
var {Router} = require('express');
|
||||
|
||||
// Our API for demos only
|
||||
import {fakeDataBase} from './db';
|
||||
import {fakeDemoRedisCache} from './cache';
|
||||
|
||||
// you would use cookies/token etc
|
||||
var USER_ID = 'f9d98cf1-1b96-464e-8755-bcc2a5c09077'; // hardcoded as an example
|
||||
|
||||
// Our API for demos only
|
||||
export function serverApi(req, res) {
|
||||
let key = USER_ID + '/data.json';
|
||||
let cache = fakeDemoRedisCache.get(key);
|
||||
if (cache !== undefined) {
|
||||
console.log('/data.json Cache Hit');
|
||||
return res.json(cache);
|
||||
}
|
||||
console.log('/data.json Cache Miss');
|
||||
|
||||
fakeDataBase.get()
|
||||
.then(data => {
|
||||
fakeDemoRedisCache.set(key, data);
|
||||
return data;
|
||||
})
|
||||
.then(data => res.json(data));
|
||||
}
|
||||
|
||||
|
||||
// todo API
|
||||
|
||||
var COUNT = 4;
|
||||
var TODOS = [
|
||||
{ id: 0, value: 'finish example', created_at: new Date(), completed: false },
|
||||
{ id: 1, value: 'add tests', created_at: new Date(), completed: false },
|
||||
{ id: 2, value: 'include development environment', created_at: new Date(), completed: false },
|
||||
{ id: 3, value: 'include production environment', created_at: new Date(), completed: false }
|
||||
];
|
||||
|
||||
export function createTodoApi() {
|
||||
|
||||
var router = Router()
|
||||
|
||||
router.route('/todos')
|
||||
.get(function(req, res) {
|
||||
console.log('GET');
|
||||
// 70ms latency
|
||||
setTimeout(function() {
|
||||
res.json(TODOS);
|
||||
}, 0);
|
||||
|
||||
})
|
||||
.post(function(req, res) {
|
||||
console.log('POST', util.inspect(req.body, {colors: true}));
|
||||
var todo = req.body;
|
||||
if (todo) {
|
||||
TODOS.push({
|
||||
value: todo.value,
|
||||
created_at: new Date(),
|
||||
completed: todo.completed,
|
||||
id: COUNT++
|
||||
});
|
||||
return res.json(todo);
|
||||
}
|
||||
|
||||
return res.end();
|
||||
});
|
||||
|
||||
router.param('todo_id', function(req, res, next, todo_id) {
|
||||
// ensure correct prop type
|
||||
var id = Number(req.params.todo_id);
|
||||
try {
|
||||
var todo = TODOS[id];
|
||||
req.todo_id = id;
|
||||
req.todo = TODOS[id];
|
||||
next();
|
||||
} catch (e) {
|
||||
next(new Error('failed to load todo'));
|
||||
}
|
||||
});
|
||||
|
||||
router.route('/todos/:todo_id')
|
||||
.get(function(req, res) {
|
||||
console.log('GET', util.inspect(req.todo, {colors: true}));
|
||||
|
||||
res.json(req.todo);
|
||||
})
|
||||
.put(function(req, res) {
|
||||
console.log('PUT', util.inspect(req.body, {colors: true}));
|
||||
|
||||
var index = TODOS.indexOf(req.todo);
|
||||
var todo = TODOS[index] = req.body;
|
||||
|
||||
res.json(todo);
|
||||
})
|
||||
.delete(function(req, res) {
|
||||
console.log('DELETE', req.todo_id);
|
||||
|
||||
var index = TODOS.indexOf(req.todo);
|
||||
TODOS.splice(index, 1);
|
||||
|
||||
res.json(req.todo);
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
17
src/backend/cache.ts
Normal file
17
src/backend/cache.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
|
||||
var _fakeLRUcount = 0;
|
||||
export const fakeDemoRedisCache = {
|
||||
_cache: {},
|
||||
get: (key) => {
|
||||
let cache = fakeDemoRedisCache._cache[key];
|
||||
_fakeLRUcount++;
|
||||
if (_fakeLRUcount >= 10) {
|
||||
fakeDemoRedisCache.clear();
|
||||
_fakeLRUcount = 0;
|
||||
}
|
||||
return cache;
|
||||
},
|
||||
set: (key, data) => fakeDemoRedisCache._cache[key] = data,
|
||||
clear: () => fakeDemoRedisCache._cache = {}
|
||||
};
|
7
src/backend/db.ts
Normal file
7
src/backend/db.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// Our API for demos only
|
||||
export const fakeDataBase = {
|
||||
get() {
|
||||
let res = { data: 'This fake data came from the db on the server.' };
|
||||
return Promise.resolve(res);
|
||||
}
|
||||
};
|
97
src/browser.module.ts
Executable file
97
src/browser.module.ts
Executable file
@@ -0,0 +1,97 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { UniversalModule, isBrowser, isNode, AUTO_PREBOOT } from 'angular2-universal/browser'; // for AoT we need to manually split universal packages
|
||||
import { IdlePreload, IdlePreloadModule } from '@angularclass/idle-preload';
|
||||
|
||||
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 './angular2-meta';
|
||||
|
||||
// import * as LRU from 'modern-lru';
|
||||
|
||||
export function getLRU(lru?: any) {
|
||||
// use LRU for node
|
||||
// return lru || new LRU(10);
|
||||
return lru || new Map();
|
||||
}
|
||||
export function getRequest() {
|
||||
// the request object only lives on the server
|
||||
return { cookie: document.cookie };
|
||||
}
|
||||
export function getResponse() {
|
||||
// the response object is sent as the index.html and lives on the server
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
// TODO(gdi2290): refactor into Universal
|
||||
export const UNIVERSAL_KEY = 'UNIVERSAL_CACHE';
|
||||
|
||||
@NgModule({
|
||||
bootstrap: [ AppComponent ],
|
||||
imports: [
|
||||
// MaterialModule.forRoot() should be included first
|
||||
UniversalModule, // BrowserModule, HttpModule, and JsonpModule are included
|
||||
|
||||
FormsModule,
|
||||
RouterModule.forRoot([], { useHash: false, preloadingStrategy: IdlePreload }),
|
||||
|
||||
IdlePreloadModule.forRoot(),
|
||||
SharedModule.forRoot(),
|
||||
AppModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: 'isBrowser', useValue: isBrowser },
|
||||
{ provide: 'isNode', useValue: isNode },
|
||||
|
||||
{ provide: 'req', useFactory: getRequest },
|
||||
{ provide: 'res', useFactory: getResponse },
|
||||
|
||||
{ provide: 'LRU', useFactory: getLRU, deps: [] },
|
||||
|
||||
CacheService,
|
||||
|
||||
Meta,
|
||||
|
||||
// { provide: AUTO_PREBOOT, useValue: false } // turn off auto preboot complete
|
||||
]
|
||||
})
|
||||
export class MainModule {
|
||||
constructor(public cache: CacheService) {
|
||||
// TODO(gdi2290): refactor into a lifecycle hook
|
||||
this.doRehydrate();
|
||||
}
|
||||
|
||||
doRehydrate() {
|
||||
let defaultValue = {};
|
||||
let serverCache = this._getCacheValue(CacheService.KEY, defaultValue);
|
||||
this.cache.rehydrate(serverCache);
|
||||
}
|
||||
|
||||
_getCacheValue(key: string, defaultValue: any): any {
|
||||
// browser
|
||||
const win: any = window;
|
||||
if (win[UNIVERSAL_KEY] && win[UNIVERSAL_KEY][key]) {
|
||||
let serverCache = defaultValue;
|
||||
try {
|
||||
serverCache = JSON.parse(win[UNIVERSAL_KEY][key]);
|
||||
if (typeof serverCache !== typeof defaultValue) {
|
||||
console.log('Angular Universal: The type of data from the server is different from the default value type');
|
||||
serverCache = defaultValue;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Angular Universal: There was a problem parsing the server data during rehydrate');
|
||||
serverCache = defaultValue;
|
||||
}
|
||||
return serverCache;
|
||||
} else {
|
||||
console.log('Angular Universal: UNIVERSAL_CACHE is missing');
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
36
src/client.aot.ts
Normal file
36
src/client.aot.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
// the polyfills must be the first thing imported
|
||||
import 'angular2-universal-polyfills';
|
||||
import 'ts-helpers';
|
||||
import './__workaround.browser'; // temporary until 2.1.1 things are patched in Core
|
||||
|
||||
// Angular 2
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowser } from '@angular/platform-browser';
|
||||
import { bootloader } from '@angularclass/bootloader';
|
||||
// for AoT use platformBrowser
|
||||
// import { platformUniversalDynamic } from 'angular2-universal/browser';
|
||||
|
||||
import { load as loadWebFont } from 'webfontloader';
|
||||
|
||||
// enable prod for faster renders
|
||||
enableProdMode();
|
||||
|
||||
import { MainModuleNgFactory } from './browser.module.ngfactory';
|
||||
|
||||
export const platformRef = platformBrowser();
|
||||
|
||||
// on document ready bootstrap Angular 2
|
||||
export function main() {
|
||||
// Load fonts async
|
||||
// https://github.com/typekit/webfontloader#configuration
|
||||
loadWebFont({
|
||||
google: {
|
||||
families: ['Droid Sans']
|
||||
}
|
||||
});
|
||||
|
||||
return platformRef.bootstrapModuleFactory(MainModuleNgFactory);
|
||||
}
|
||||
|
||||
// support async tag or hmr
|
||||
bootloader(main);
|
34
src/client.ts
Normal file
34
src/client.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// the polyfills must be the first thing imported
|
||||
import 'angular2-universal-polyfills';
|
||||
import 'ts-helpers';
|
||||
import './__workaround.browser'; // temporary until 2.1.1 things are patched in Core
|
||||
|
||||
// Angular 2
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformUniversalDynamic } from 'angular2-universal/browser';
|
||||
import { bootloader } from '@angularclass/bootloader';
|
||||
|
||||
import { load as loadWebFont } from 'webfontloader';
|
||||
|
||||
// enable prod for faster renders
|
||||
// enableProdMode();
|
||||
|
||||
import { MainModule } from './browser.module';
|
||||
|
||||
export const platformRef = platformUniversalDynamic();
|
||||
|
||||
// on document ready bootstrap Angular 2
|
||||
export function main() {
|
||||
// Load fonts async
|
||||
// https://github.com/typekit/webfontloader#configuration
|
||||
loadWebFont({
|
||||
google: {
|
||||
families: ['Droid Sans']
|
||||
}
|
||||
});
|
||||
|
||||
return platformRef.bootstrapModule(MainModule);
|
||||
}
|
||||
|
||||
// support async tag or hmr
|
||||
bootloader(main);
|
23
src/index.html
Normal file
23
src/index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>DSpace</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,minimum-scale=1">
|
||||
|
||||
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
|
||||
|
||||
<link rel="prerender" href="http://localhost:3000/lazy">
|
||||
<link rel="preload" href="/assets/logo.svg">
|
||||
<base href="/">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<app>
|
||||
Loading DSpace ...
|
||||
</app>
|
||||
|
||||
<script async src="/main.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
73
src/node.module.ts
Executable file
73
src/node.module.ts
Executable file
@@ -0,0 +1,73 @@
|
||||
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 './angular2-meta';
|
||||
|
||||
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';
|
||||
|
||||
@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,
|
||||
],
|
||||
providers: [
|
||||
{ provide: 'isBrowser', useValue: isBrowser },
|
||||
{ provide: 'isNode', useValue: isNode },
|
||||
|
||||
{ provide: 'req', useFactory: getRequest },
|
||||
{ provide: 'res', useFactory: getResponse },
|
||||
|
||||
{ provide: 'LRU', useFactory: getLRU, deps: [] },
|
||||
|
||||
CacheService,
|
||||
|
||||
Meta,
|
||||
]
|
||||
})
|
||||
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();
|
||||
}
|
||||
}
|
147
src/server.aot.ts
Normal file
147
src/server.aot.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
// 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 './__workaround.node'; // temporary until 2.1.1 things are patched in Core
|
||||
|
||||
import * as fs from 'fs';
|
||||
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';
|
||||
|
||||
const { gzipSync } = require('zlib');
|
||||
const accepts = require('accepts');
|
||||
const { compressSync } = require('iltorb');
|
||||
const interceptor = require('express-interceptor');
|
||||
|
||||
// Angular 2
|
||||
import { enableProdMode } from '@angular/core';
|
||||
// Angular 2 Universal
|
||||
import { createEngine } from 'angular2-express-engine';
|
||||
|
||||
// App
|
||||
import { MainModuleNgFactory } from './node.module.ngfactory';
|
||||
|
||||
// Routes
|
||||
import { routes } from './server.routes';
|
||||
|
||||
// enable prod for faster renders
|
||||
enableProdMode();
|
||||
|
||||
const app = express();
|
||||
const ROOT = path.join(path.resolve(__dirname, '..'));
|
||||
|
||||
// Express View
|
||||
app.engine('.html', createEngine({
|
||||
precompile: false, // this needs to be false when using ngFactory
|
||||
ngModule: MainModuleNgFactory,
|
||||
providers: [
|
||||
// use only if you have shared state between users
|
||||
// { provide: 'LRU', useFactory: () => new LRU(10) }
|
||||
|
||||
// stateless providers only since it's shared
|
||||
]
|
||||
}));
|
||||
app.set('port', process.env.PORT || 3000);
|
||||
app.set('views', __dirname);
|
||||
app.set('view engine', 'html');
|
||||
app.set('json spaces', 2);
|
||||
|
||||
app.use(cookieParser('Angular 2 Universal'));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.use(interceptor((req, res)=>({
|
||||
// don't compress responses with this request header
|
||||
isInterceptable: () => (!req.headers['x-no-compression']),
|
||||
intercept: ( body, send ) => {
|
||||
const encodings = new Set(accepts(req).encodings());
|
||||
const bodyBuffer = new Buffer(body);
|
||||
// url specific key for response cache
|
||||
const key = '__response__' + req.originalUrl || req.url;
|
||||
let output = bodyBuffer;
|
||||
// check if cache exists
|
||||
if (mcache.get(key) === null) {
|
||||
// check for encoding support
|
||||
if (encodings.has('br')) {
|
||||
// brotli
|
||||
res.setHeader('Content-Encoding', 'br');
|
||||
output = compressSync(bodyBuffer);
|
||||
mcache.put(key, {output, encoding: 'br'});
|
||||
} else if (encodings.has('gzip')) {
|
||||
// gzip
|
||||
res.setHeader('Content-Encoding', 'gzip');
|
||||
output = gzipSync(bodyBuffer);
|
||||
mcache.put(key, {output, encoding: 'gzip'});
|
||||
}
|
||||
} else {
|
||||
const { output, encoding } = mcache.get(key);
|
||||
res.setHeader('Content-Encoding', encoding);
|
||||
send(output);
|
||||
}
|
||||
send(output);
|
||||
}
|
||||
})));
|
||||
|
||||
const accessLogStream = fs.createWriteStream(ROOT + '/morgan.log', {flags: 'a'})
|
||||
|
||||
app.use(morgan('common', {
|
||||
skip: (req, res) => res.statusCode < 400,
|
||||
stream: accessLogStream
|
||||
}));
|
||||
|
||||
function cacheControl(req, res, next) {
|
||||
// instruct browser to revalidate in 60 seconds
|
||||
res.header('Cache-Control', 'max-age=60');
|
||||
next();
|
||||
}
|
||||
// Serve static files
|
||||
app.use('/assets', cacheControl, express.static(path.join(__dirname, 'assets'), {maxAge: 30}));
|
||||
app.use(cacheControl, express.static(path.join(ROOT, 'dist/client'), {index: false}));
|
||||
|
||||
//
|
||||
/////////////////////////
|
||||
// ** Example API
|
||||
// Notice API should be in a separate process
|
||||
import { serverApi, createTodoApi } from './backend/api';
|
||||
// Our API for demos only
|
||||
app.get('/data.json', serverApi);
|
||||
app.use('/api', createTodoApi());
|
||||
|
||||
function ngApp(req, res) {
|
||||
res.render('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') }`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* use universal for specific routes
|
||||
*/
|
||||
app.get('/', ngApp);
|
||||
routes.forEach(route => {
|
||||
app.get(`/${route}`, ngApp);
|
||||
app.get(`/${route}/*`, ngApp);
|
||||
});
|
||||
|
||||
|
||||
app.get('*', function(req, res) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
var pojo = { status: 404, message: 'No Content' };
|
||||
var json = JSON.stringify(pojo, null, 2);
|
||||
res.status(404).send(json);
|
||||
});
|
||||
|
||||
// Server
|
||||
let server = app.listen(app.get('port'), () => {
|
||||
console.log(`Listening on: http://localhost:${server.address().port}`);
|
||||
});
|
17
src/server.routes.ts
Normal file
17
src/server.routes.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Server-side routes. Only the listed routes support html5pushstate.
|
||||
* Has to match client side routes.
|
||||
*
|
||||
* Index (/) route does not have to be listed here.
|
||||
*
|
||||
* @example
|
||||
* export const routes: string[] = [
|
||||
* 'home', 'about'
|
||||
* ];
|
||||
**/
|
||||
export const routes: string[] = [
|
||||
'about',
|
||||
'home',
|
||||
'todo',
|
||||
'lazy',
|
||||
];
|
103
src/server.ts
Normal file
103
src/server.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
// 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 './__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 compression from 'compression';
|
||||
|
||||
// Angular 2
|
||||
import { enableProdMode } from '@angular/core';
|
||||
// Angular 2 Universal
|
||||
import { createEngine } from 'angular2-express-engine';
|
||||
|
||||
// App
|
||||
import { MainModule } from './node.module';
|
||||
|
||||
// Routes
|
||||
import { routes } from './server.routes';
|
||||
|
||||
// enable prod for faster renders
|
||||
enableProdMode();
|
||||
|
||||
const app = express();
|
||||
const ROOT = path.join(path.resolve(__dirname, '..'));
|
||||
|
||||
// Express View
|
||||
app.engine('.html', createEngine({
|
||||
ngModule: MainModule,
|
||||
providers: [
|
||||
// use only if you have shared state between users
|
||||
// { provide: 'LRU', useFactory: () => new LRU(10) }
|
||||
|
||||
// stateless providers only since it's shared
|
||||
]
|
||||
}));
|
||||
app.set('port', process.env.PORT || 3000);
|
||||
app.set('views', __dirname);
|
||||
app.set('view engine', 'html');
|
||||
app.set('json spaces', 2);
|
||||
|
||||
app.use(cookieParser('Angular 2 Universal'));
|
||||
app.use(bodyParser.json());
|
||||
app.use(compression());
|
||||
|
||||
app.use(morgan('dev'));
|
||||
|
||||
function cacheControl(req, res, next) {
|
||||
// instruct browser to revalidate in 60 seconds
|
||||
res.header('Cache-Control', 'max-age=60');
|
||||
next();
|
||||
}
|
||||
// Serve static files
|
||||
app.use('/assets', cacheControl, express.static(path.join(__dirname, 'assets'), {maxAge: 30}));
|
||||
app.use(cacheControl, express.static(path.join(ROOT, 'dist/client'), {index: false}));
|
||||
|
||||
//
|
||||
/////////////////////////
|
||||
// ** Example API
|
||||
// Notice API should be in aseparate process
|
||||
import { serverApi, createTodoApi } from './backend/api';
|
||||
// Our API for demos only
|
||||
app.get('/data.json', serverApi);
|
||||
app.use('/api', createTodoApi());
|
||||
|
||||
function ngApp(req, res) {
|
||||
res.render('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') }`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* use universal for specific routes
|
||||
*/
|
||||
app.get('/', ngApp);
|
||||
routes.forEach(route => {
|
||||
app.get(`/${route}`, ngApp);
|
||||
app.get(`/${route}/*`, ngApp);
|
||||
});
|
||||
|
||||
app.get('*', function(req, res) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
var pojo = { status: 404, message: 'No Content' };
|
||||
var json = JSON.stringify(pojo, null, 2);
|
||||
res.status(404).send(json);
|
||||
});
|
||||
|
||||
// Server
|
||||
let server = app.listen(app.get('port'), () => {
|
||||
console.log(`Listening on: http://localhost:${server.address().port}`);
|
||||
});
|
73
src/typings.d.ts
vendored
Normal file
73
src/typings.d.ts
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Custom Type Definitions
|
||||
* When including 3rd party modules you also need to include the type definition for the module
|
||||
* if they don't provide one within the module. You can try to install it with typings
|
||||
typings install node --save
|
||||
* If you can't find the type definition in the registry we can make an ambient definition in
|
||||
* this file for now. For example
|
||||
declare module "my-module" {
|
||||
export function doesSomething(value: string): string;
|
||||
}
|
||||
*
|
||||
* If you're prototying and you will fix the types later you can also declare it as type any
|
||||
*
|
||||
declare var assert: any;
|
||||
*
|
||||
* If you're importing a module that uses Node.js modules which are CommonJS you need to import as
|
||||
*
|
||||
import * as _ from 'lodash'
|
||||
* You can include your type definitions in this file until you create one for the typings registry
|
||||
* see https://github.com/typings/registry
|
||||
*
|
||||
*/
|
||||
|
||||
// declare module '*'; // default type definitions for any for modules that are not found.
|
||||
// caveat: if this is enabled and you do not have the proper module there may not be an error.
|
||||
// suggestion: follow the pattern below with modern-lru which provides an alternative way to create an 'any' module.
|
||||
|
||||
// for legacy tslint etc to understand
|
||||
declare module 'modern-lru' {
|
||||
let x: any;
|
||||
export = x;
|
||||
}
|
||||
|
||||
declare var System: SystemJS;
|
||||
|
||||
interface SystemJS {
|
||||
import: (path?: string) => Promise<any>;
|
||||
}
|
||||
// Extra variables that live on Global that will be replaced by webpack DefinePlugin
|
||||
declare var ENV: string;
|
||||
declare var HMR: boolean;
|
||||
declare var Zone: {current: any};
|
||||
interface GlobalEnvironment {
|
||||
ENV;
|
||||
HMR;
|
||||
SystemJS: SystemJS;
|
||||
System: SystemJS;
|
||||
}
|
||||
|
||||
interface WebpackModule {
|
||||
hot: {
|
||||
data?: any,
|
||||
idle: any,
|
||||
accept(dependencies?: string | string[], callback?: (updatedDependencies?: any) => void): void;
|
||||
decline(dependencies?: string | string[]): void;
|
||||
dispose(callback?: (data?: any) => void): void;
|
||||
addDisposeHandler(callback?: (data?: any) => void): void;
|
||||
removeDisposeHandler(callback?: (data?: any) => void): void;
|
||||
check(autoApply?: any, callback?: (err?: Error, outdatedModules?: any[]) => void): void;
|
||||
apply(options?: any, callback?: (err?: Error, outdatedModules?: any[]) => void): void;
|
||||
status(callback?: (status?: string) => void): void | string;
|
||||
removeStatusHandler(callback?: (status?: string) => void): void;
|
||||
};
|
||||
}
|
||||
|
||||
interface WebpackRequire {
|
||||
context(file: string, flag?: boolean, exp?: RegExp): any;
|
||||
}
|
||||
|
||||
// Extend typings
|
||||
interface NodeRequire extends WebpackRequire {}
|
||||
interface NodeModule extends WebpackModule {}
|
||||
interface Global extends GlobalEnvironment {}
|
Reference in New Issue
Block a user