From bb8736383e66fb7195d92f67d3af4c3eaf409217 Mon Sep 17 00:00:00 2001 From: Mark Pieszak Date: Tue, 10 Oct 2017 19:08:58 -0400 Subject: [PATCH 1/5] feat(5.0): update engine-etc for angular 5.0-rc1 WIP - More updates to come --- ClientApp/app/app.module.ts | 6 +- ClientApp/boot.server.ts | 1 + .../polyfills/temporary-aspnetcore-engine.ts | 238 +++++++----------- package.json | 36 +-- tslint.json | 3 +- webpack.config.js | 14 +- webpack.config.vendor.js | 2 +- 7 files changed, 134 insertions(+), 166 deletions(-) diff --git a/ClientApp/app/app.module.ts b/ClientApp/app/app.module.ts index 1c492f1d..cb843d53 100644 --- a/ClientApp/app/app.module.ts +++ b/ClientApp/app/app.module.ts @@ -2,6 +2,7 @@ import { NgModule, Inject } from '@angular/core'; import { RouterModule, PreloadAllModules } from '@angular/router'; import { CommonModule, APP_BASE_HREF } from '@angular/common'; import { HttpModule, Http } from '@angular/http'; +import { HttpClientModule, HttpClient } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; import { Ng2BootstrapModule } from 'ngx-bootstrap'; @@ -26,7 +27,7 @@ import { UserService } from './shared/user.service'; import { ORIGIN_URL } from './shared/constants/baseurl.constants'; import { TransferHttpModule } from '../modules/transfer-http/transfer-http.module'; -export function createTranslateLoader(http: Http, baseHref) { +export function createTranslateLoader(http: HttpClient, baseHref) { // Temporary Azure hack if (baseHref === null && typeof window !== 'undefined') { baseHref = window.location.origin; @@ -50,6 +51,7 @@ export function createTranslateLoader(http: Http, baseHref) { imports: [ CommonModule, HttpModule, + HttpClientModule, FormsModule, Ng2BootstrapModule.forRoot(), // You could also split this up if you don't want the Entire Module imported @@ -60,7 +62,7 @@ export function createTranslateLoader(http: Http, baseHref) { loader: { provide: TranslateLoader, useFactory: (createTranslateLoader), - deps: [Http, [ORIGIN_URL]] + deps: [HttpClient, [ORIGIN_URL]] } }), diff --git a/ClientApp/boot.server.ts b/ClientApp/boot.server.ts index 86d65086..1ddac10b 100644 --- a/ClientApp/boot.server.ts +++ b/ClientApp/boot.server.ts @@ -26,6 +26,7 @@ export default createServerRenderer((params) => { }; return ngAspnetCoreEngine(setupOptions).then(response => { + // Apply your transferData to response.globals response.globals.transferData = createTransferScript({ someData: 'Transfer this to the client on the window.TRANSFER_CACHE {} object', diff --git a/ClientApp/polyfills/temporary-aspnetcore-engine.ts b/ClientApp/polyfills/temporary-aspnetcore-engine.ts index d3157a1b..df90a388 100644 --- a/ClientApp/polyfills/temporary-aspnetcore-engine.ts +++ b/ClientApp/polyfills/temporary-aspnetcore-engine.ts @@ -1,10 +1,7 @@ -/* ********* TEMPORARILY HERE ************** - * - will be on npm soon - - * import { ngAspnetCoreEngine } from `@nguniversal/aspnetcore-engine`; - */ -import { Type, NgModuleFactory, NgModuleRef, ApplicationRef, Provider, CompilerFactory, Compiler } from '@angular/core'; -import { platformServer, platformDynamicServer, PlatformState, INITIAL_CONFIG, renderModuleFactory } from '@angular/platform-server'; +import { Type, NgModuleFactory, NgModuleRef, ApplicationRef, Provider, StaticProvider, CompilerFactory, Compiler } from '@angular/core'; +import { platformServer, BEFORE_APP_SERIALIZED, platformDynamicServer, PlatformState, INITIAL_CONFIG, renderModuleFactory } from '@angular/platform-server'; import { ResourceLoader } from '@angular/compiler'; +import { DOCUMENT } from '@angular/platform-browser'; import * as fs from 'fs'; import { REQUEST } from '../app/shared/constants/request'; @@ -21,7 +18,6 @@ export class FileLoader implements ResourceLoader { if (err) { return reject(err); } - resolve(buffer.toString()); }); }); @@ -39,11 +35,67 @@ export interface IRequestParams { } export interface IEngineOptions { - appSelector: string; - request: IRequestParams; - ngModule: Type<{}> | NgModuleFactory<{}>; - providers?: Provider[]; -}; + appSelector: string; // e.g., + request: IRequestParams; // e.g., params + ngModule: Type<{}> | NgModuleFactory<{}>; // e.g., AppModule + providers?: StaticProvider[]; // StaticProvider[] +} + +/* @internal */ +export class UniversalData { + public static appNode = ''; + public static title = ''; + public static scripts = ''; + public static styles = ''; + public static meta = ''; + public static links = ''; +} + +/* @internal */ +let appSelector = 'app-root'; // default + +/* @internal */ +function beforeAppSerialized( + doc: any /* TODO: type definition for Domino - DomAPI Spec (similar to "Document") */ +) { + + return () => { + const STYLES = []; + const SCRIPTS = []; + const META = []; + const LINKS = []; + + for (let i = 0; i < doc.head.children.length; i++) { + const element = doc.head.children[i]; + const tagName = element.tagName.toUpperCase(); + + switch (tagName) { + case 'SCRIPT': + SCRIPTS.push(element.outerHTML); + break; + case 'STYLE': + STYLES.push(element.outerHTML); + break; + case 'LINK': + LINKS.push(element.outerHTML); + break; + case 'META': + META.push(element.outerHTML); + break; + default: + break; + } + } + + UniversalData.title = doc.title; + UniversalData.appNode = doc.querySelector(appSelector).outerHTML; + UniversalData.scripts = SCRIPTS.join(' '); + UniversalData.styles = STYLES.join(' '); + UniversalData.meta = META.join(' '); + UniversalData.links = LINKS.join(' '); + }; +} + export function ngAspnetCoreEngine( options: IEngineOptions @@ -51,11 +103,18 @@ export function ngAspnetCoreEngine( options.providers = options.providers || []; + if (!options.appSelector) { + throw new Error(`appSelector is required! Pass in " appSelector: '' ", for your root App component.`); + } + + // Grab the DOM "selector" from the passed in Template for example = "app-root" + appSelector = options.appSelector.substring(1, options.appSelector.indexOf('>')); + const compilerFactory: CompilerFactory = platformDynamicServer().injector.get(CompilerFactory); const compiler: Compiler = compilerFactory.createCompiler([ { providers: [ - { provide: ResourceLoader, useClass: FileLoader } + { provide: ResourceLoader, useClass: FileLoader, deps: [] } ] } ]); @@ -69,150 +128,43 @@ export function ngAspnetCoreEngine( } const extraProviders = options.providers.concat( - options.providers, - [ - { - provide: INITIAL_CONFIG, - useValue: { - document: options.appSelector, - url: options.request.url - } - }, - { + options.providers, [{ provide: ORIGIN_URL, useValue: options.request.origin }, { provide: REQUEST, useValue: options.request.data.request + }, { + provide: BEFORE_APP_SERIALIZED, + useFactory: beforeAppSerialized, multi: true, deps: [ DOCUMENT ] } ] ); - const platform = platformServer(extraProviders); - getFactory(moduleOrFactory, compiler) - .then((factory: NgModuleFactory<{}>) => { - - return platform.bootstrapModuleFactory(factory).then((moduleRef: NgModuleRef<{}>) => { - - const state: PlatformState = moduleRef.injector.get(PlatformState); - const appRef: ApplicationRef = moduleRef.injector.get(ApplicationRef); - - appRef.isStable - .filter((isStable: boolean) => isStable) - .first() - .subscribe((stable) => { - - // Fire the TransferState Cache - const bootstrap = moduleRef.instance['ngOnBootstrap']; - bootstrap && bootstrap(); - - // The parse5 Document itself - const AST_DOCUMENT = state.getDocument(); - - // Strip out the Angular application - const htmlDoc = state.renderToString(); - - const APP_HTML = htmlDoc.substring( - htmlDoc.indexOf('') + 6, - htmlDoc.indexOf('') - ); - - // Strip out Styles / Meta-tags / Title - const STYLES = []; - const SCRIPTS = []; - const META = []; - const LINKS = []; - let TITLE = ''; - - let STYLES_STRING: string = htmlDoc.indexOf('') + 8) - : null; - - const HEAD = AST_DOCUMENT.head; - - let count = 0; - - for (let i = 0; i < HEAD.children.length; i++) { - let element = HEAD.children[i]; - - if (element.name === 'title') { - TITLE = element.children[0].data; - } - - if (element.name === 'script') { - SCRIPTS.push( - `` - ); - } - - // Broken after 4.0 (worked in rc) - // if (element.name === 'style') { - // let styleTag = '`; - // STYLES.push(styleTag); - // } - - if (element.name === 'meta') { - count = count + 1; - let metaString = '\n`); - } - - if (element.name === 'link') { - let linkString = '\n`); - } - } - - resolve({ - html: APP_HTML, - globals: { - styles: STYLES_STRING, - title: TITLE, - scripts: SCRIPTS.join(' '), - meta: META.join(' '), - links: LINKS.join(' ') - } - }); - - moduleRef.destroy(); - - }, (err) => { - // isStable subscription error (Template / code error) - reject(err); - }); - - }, err => { - // bootstrapModuleFactory error - reject(err); + .then(factory => { + return renderModuleFactory(factory, { + document: options.appSelector, + url: options.request.url, + extraProviders: extraProviders }); - - }, err => { - // getFactory error + }) + .then((html: string) => { + resolve({ + html: UniversalData.appNode, + globals: { + styles: UniversalData.styles, + title: UniversalData.title, + scripts: UniversalData.scripts, + meta: UniversalData.meta, + links: UniversalData.links + } + }); + }, (err) => { reject(err); }); } catch (ex) { - // try/catch error reject(ex); } @@ -226,7 +178,7 @@ const factoryCacheMap = new Map, NgModuleFactory<{}>>(); function getFactory( moduleOrFactory: Type<{}> | NgModuleFactory<{}>, compiler: Compiler ): Promise> { - + return new Promise>((resolve, reject) => { // If module has been compiled AoT if (moduleOrFactory instanceof NgModuleFactory) { diff --git a/package.json b/package.json index 9ebbf620..9bd938e0 100644 --- a/package.json +++ b/package.json @@ -15,20 +15,20 @@ "clean": "rimraf wwwroot/dist clientapp/dist" }, "dependencies": { - "@angular/animations": "^4.3.0", - "@angular/common": "^4.3.0", - "@angular/compiler": "^4.3.0", - "@angular/compiler-cli": "^4.3.0", - "@angular/core": "^4.3.0", - "@angular/forms": "^4.3.0", - "@angular/http": "^4.3.0", - "@angular/platform-browser": "^4.3.0", - "@angular/platform-browser-dynamic": "^4.3.0", - "@angular/platform-server": "^4.3.0", - "@angular/router": "^4.3.0", - "@nguniversal/aspnetcore-engine": "^1.0.0-beta.2", - "@ngx-translate/core": "^6.0.1", - "@ngx-translate/http-loader": "0.0.3", + "@angular/animations": "^5.0.0-rc.1", + "@angular/common": "^5.0.0-rc.1", + "@angular/compiler": "^5.0.0-rc.1", + "@angular/compiler-cli": "^5.0.0-rc.1", + "@angular/core": "^5.0.0-rc.1", + "@angular/forms": "^5.0.0-rc.1", + "@angular/http": "^5.0.0-rc.1", + "@angular/platform-browser": "^5.0.0-rc.1", + "@angular/platform-browser-dynamic": "^5.0.0-rc.1", + "@angular/platform-server": "^5.0.0-rc.1", + "@angular/router": "^5.0.0-rc.1", + "@nguniversal/aspnetcore-engine": "^1.0.0-beta.3", + "@ngx-translate/core": "^8.0.0", + "@ngx-translate/http-loader": "^2.0.0", "@types/node": "^7.0.12", "angular2-router-loader": "^0.3.5", "angular2-template-loader": "^0.6.2", @@ -49,7 +49,7 @@ "jquery": "^2.2.1", "json-loader": "^0.5.4", "moment": "2.18.1", - "ngx-bootstrap": "2.0.0-beta.3", + "ngx-bootstrap": "2.0.0-beta.6", "node-sass": "^4.5.2", "preboot": "^5.0.0", "raw-loader": "^0.5.1", @@ -58,7 +58,7 @@ "sass-loader": "^6.0.6", "style-loader": "^0.18.2", "to-string-loader": "^1.1.5", - "typescript": "2.5.2", + "typescript": "2.5.3", "url-loader": "^0.5.7", "webpack": "^3.6.0", "webpack-hot-middleware": "^2.19.1", @@ -66,8 +66,8 @@ "zone.js": "^0.8.17" }, "devDependencies": { - "@angular/cli": "^1.3.2", - "@ngtools/webpack": "^1.3.0", + "@angular/cli": "^1.5.0-beta.4", + "@ngtools/webpack": "^1.5.0-beta.4", "@types/chai": "^3.4.34", "@types/jasmine": "^2.5.37", "chai": "^3.5.0", diff --git a/tslint.json b/tslint.json index 657d49b6..f318a0b8 100644 --- a/tslint.json +++ b/tslint.json @@ -1,4 +1,5 @@ { + "defaultSeverity": "warn", "rules": { "align": false, "ban": false, @@ -75,7 +76,7 @@ "no-switch-case-fall-through": true, "no-trailing-whitespace": false, "no-unused-expression": true, - "no-unused-variable": false, + "no-unused-variable": true, "no-use-before-declare": true, "no-var-keyword": true, "no-var-requires": false, diff --git a/webpack.config.js b/webpack.config.js index 02ff0a01..81ba1a9f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -78,7 +78,19 @@ module.exports = (env) => { manifest: require('./ClientApp/dist/vendor-manifest.json'), sourceType: 'commonjs2', name: './vendor' - }) + }), + new webpack.ContextReplacementPlugin( + // fixes WARNING Critical dependency: the request of a dependency is an expression + /(.+)?angular(\\|\/)core(.+)?/, + path.join(__dirname, 'src'), // location of your src + {} // a map of your routes + ), + new webpack.ContextReplacementPlugin( + // fixes WARNING Critical dependency: the request of a dependency is an expression + /(.+)?express(\\|\/)(.+)?/, + path.join(__dirname, 'src'), + {} + ) ].concat(isDevBuild ? [] : [ new webpack.optimize.UglifyJsPlugin({ compress: false, diff --git a/webpack.config.vendor.js b/webpack.config.vendor.js index 3e07b78b..6d35d3ba 100644 --- a/webpack.config.vendor.js +++ b/webpack.config.vendor.js @@ -46,7 +46,7 @@ module.exports = (env) => { plugins: [ // new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }), // Maps these identifiers to the jQuery package (because Bootstrap expects it to be a global variable) new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, './ClientApp')), // Workaround for https://github.com/angular/angular/issues/11580 - new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, path.join(__dirname, './ClientApp')), // Workaround for https://github.com/angular/angular/issues/14898 + new webpack.ContextReplacementPlugin(/(.+)?angular(\\|\/)core(.+)?/, path.join(__dirname, './ClientApp')), // Workaround for https://github.com/angular/angular/issues/14898 new webpack.IgnorePlugin(/^vertx$/) // Workaround for https://github.com/stefanpenner/es6-promise/issues/100 ] }; From 29a40aa23fd9a5bcd606eaa7dde5acdb1756286c Mon Sep 17 00:00:00 2001 From: Mark Pieszak Date: Tue, 10 Oct 2017 19:18:07 -0400 Subject: [PATCH 2/5] remove ng 4 references --- .../components/navmenu/navmenu.component.html | 2 +- .../app/containers/home/home.component.html | 12 ++---------- .../app/containers/home/home.component.ts | 2 +- README.md | 18 +++++++++--------- Startup.cs | 2 +- 5 files changed, 14 insertions(+), 22 deletions(-) diff --git a/ClientApp/app/components/navmenu/navmenu.component.html b/ClientApp/app/components/navmenu/navmenu.component.html index bd6eadda..08e1b0fc 100644 --- a/ClientApp/app/components/navmenu/navmenu.component.html +++ b/ClientApp/app/components/navmenu/navmenu.component.html @@ -8,7 +8,7 @@ - Angular 4 Universal & ASP.NET Core + Angular 5 Universal & ASP.NET Core
diff --git a/ClientApp/app/containers/home/home.component.html b/ClientApp/app/containers/home/home.component.html index 06d801e7..3a4db63b 100644 --- a/ClientApp/app/containers/home/home.component.html +++ b/ClientApp/app/containers/home/home.component.html @@ -1,7 +1,7 @@ 

{{ title }}

- Enjoy the latest features from .NET Core & Angular 4.0! + Enjoy the latest features from .NET Core & Angular 5.0!
For more info check the repo here: AspNetCore-Angular2-Universal repo

@@ -12,7 +12,7 @@

{{ 'HOME_FEATURE_LIST_TITLE' | translate }}

  • ASP.NET Core 2.0 :: ( Visual Studio 2017 )
  • - Angular 4.* front-end UI framework + Angular 5.* front-end UI framework
    • Angular **platform-server** (aka: Universal) - server-side rendering for SEO, deep-linking, and incredible performance.
    • @@ -22,17 +22,10 @@

      {{ 'HOME_FEATURE_LIST_TITLE' | translate }}

    • The latest TypeScript 2.* features -
    • Webpack
        -
      • Hot Module Reloading/Replacement for an amazing development experience.
      • Tree-shaking
      @@ -40,7 +33,6 @@

      {{ 'HOME_FEATURE_LIST_TITLE' | translate }}

    • Bootstrap (ngx-bootstrap) : Bootstrap capable of being rendered even on the server.
    • Unit testing via karma & jasmine.
    • -
    diff --git a/ClientApp/app/containers/home/home.component.ts b/ClientApp/app/containers/home/home.component.ts index 3065c0b1..ff10f2d4 100644 --- a/ClientApp/app/containers/home/home.component.ts +++ b/ClientApp/app/containers/home/home.component.ts @@ -8,7 +8,7 @@ import { TranslateService } from '@ngx-translate/core'; }) export class HomeComponent implements OnInit { - title: string = 'Angular 4.0 Universal & ASP.NET Core 2.0 advanced starter-kit'; + title: string = 'Angular 5.x Universal & ASP.NET Core 2.0 advanced starter-kit'; // Use "constructor"s only for dependency injection constructor( diff --git a/README.md b/README.md index 9e30c7a4..94194632 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ -# ASP.NET Core 2.0 & Angular 4 (+) advanced starter - with Server-side prerendering (for Angular SEO)! +# ASP.NET Core 2.0 & Angular 5 (+) advanced starter - with Server-side prerendering (for Angular SEO)!

    - ASP.NET Core 2.0 Angular 4+ Starter + ASP.NET Core 2.0 Angular 5+ Starter

    -### Harness the power of Angular 4+, ASP.NET Core 2.0, now with SEO ! +### Harness the power of Angular 5+, ASP.NET Core 2.0, now with SEO ! Angular SEO in action:

    - ASP.NET Core Angular4 SEO + ASP.NET Core Angular5 SEO

    ### What is this repo? Live Demo here: http://aspnetcore-angular2-universal.azurewebsites.net This repository is maintained by [Angular](https://github.com/angular/angular) and is meant to be an advanced starter -for both ASP.NET Core 2.0 using Angular 4.0+, not only for the client-side, but to be rendered on the server for instant +for both ASP.NET Core 2.0 using Angular 5.0+, not only for the client-side, but to be rendered on the server for instant application paints (Note: If you don't need SSR [read here](#faq) on how to disable it). This is meant to be a Feature-Rich Starter application containing all of the latest technologies, best build systems available, and include many real-world examples and libraries needed in todays Single Page Applications (SPAs). @@ -49,7 +49,7 @@ This utilizes all the latest standards, no gulp, no bower, no typings, no manual - Swagger WebAPI documentation when running in development mode - SignalR Chat demo! (Thanks to [@hakonamatata](https://github.com/hakonamatata)) -- **Angular 4.0.0** : +- **Angular 5.0.0** : - (Minimal) Angular-CLI integration - This is to be used mainly for Generating Components/Services/etc. - Usage examples: @@ -291,7 +291,7 @@ Take a look at the `_Layout.cshtml` file for example, notice how we let .NET han - @ViewData["Title"] - AspNET.Core Angular 4.0.0 (+) starter + @ViewData["Title"] - AspNET.Core Angular 5.0.0 (+) starter @@ -332,7 +332,7 @@ Well now, your Client-side Angular will take over, and you'll have a fully funct - This repository uses ASP.Net Core 2.0, which has a hard requirement on .NET Core Runtime 2.0.0 and .NET Core SDK 2.0.0. Please install these items from [here](https://github.com/dotnet/core/blob/master/release-notes/download-archives/2.0.0-download.md) -> When building components in Angular 4 there are a few things to keep in mind. +> When building components in Angular 5 there are a few things to keep in mind. - **`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: - If you need to use them, consider limiting them to only your client and wrapping them situationally. You can use the Object injected using the PLATFORM_ID token to check whether the current platform is browser or server. @@ -392,7 +392,7 @@ You'll either want to remove SSR for now, or wait as support should be coming to ### How can I use jQuery and/or some jQuery plugins with this repo? > Note: If at all possible, try to avoid using jQuery or libraries dependent on it, as there are -better, more abstract ways of dealing with the DOM in Angular (4+) such as using the Renderer, etc. +better, more abstract ways of dealing with the DOM in Angular (5+) such as using the Renderer, etc. Yes, of course but there are a few things you need to setup before doing this. First, make sure jQuery is included in webpack vendor file, and that you have a webpack Plugin setup for it. `new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' })` diff --git a/Startup.cs b/Startup.cs index 7736f9e9..a5edcc97 100644 --- a/Startup.cs +++ b/Startup.cs @@ -54,7 +54,7 @@ public void ConfigureServices(IServiceCollection services) // Register the Swagger generator, defining one or more Swagger documents services.AddSwaggerGen(c => { - c.SwaggerDoc("v1", new Info { Title = "Angular 4.0 Universal & ASP.NET Core advanced starter-kit web API", Version = "v1" }); + c.SwaggerDoc("v1", new Info { Title = "Angular 5.0 Universal & ASP.NET Core advanced starter-kit web API", Version = "v1" }); }); } From b36eca50a1194a68ae5c686faa36642253b5e09f Mon Sep 17 00:00:00 2001 From: Mark Pieszak Date: Tue, 10 Oct 2017 19:24:35 -0400 Subject: [PATCH 3/5] update source maps for faster HMR builds --- webpack.config.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 81ba1a9f..a5615cff 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -65,7 +65,8 @@ module.exports = (env) => { entryModule: path.join(__dirname, 'ClientApp/app/app.module.browser#AppModule'), exclude: ['./**/*.server.ts'] }) - ]) + ]), + devtool: isDevBuild ? 'cheap-eval-source-map' : false }); // Configuration for server-side (prerendering) bundle suitable for running in Node @@ -108,7 +109,8 @@ module.exports = (env) => { path: path.join(__dirname, './ClientApp/dist') }, target: 'node', - devtool: isDevBuild ? 'inline-source-map': false + // switch to "inline-source-map" if you want to debug the TS during SSR + devtool: isDevBuild ? 'cheap-eval-source-map' : false }); return [clientBundleConfig, serverBundleConfig]; From 0cf12a1713d27e70b5ce5a55f54886cb18dfdfff Mon Sep 17 00:00:00 2001 From: Mark Pieszak Date: Wed, 11 Oct 2017 14:05:27 -0400 Subject: [PATCH 4/5] use aspnetcore-engine & misc updates and fixes --- ClientApp/app/app.component.html | 2 +- ClientApp/app/app.component.ts | 11 +- ClientApp/app/app.module.browser.ts | 3 +- ClientApp/app/app.module.server.ts | 2 +- ClientApp/app/app.module.ts | 2 +- .../components/navmenu/navmenu.component.ts | 10 +- .../user-detail/user-detail.component.ts | 2 +- .../containers/counter/counter.component.ts | 2 +- .../app/containers/lazy/lazy.component.ts | 2 +- .../not-found/not-found.component.ts | 2 +- .../app/containers/users/users.component.html | 2 +- .../app/containers/users/users.component.ts | 2 +- .../app/shared/constants/baseurl.constants.ts | 3 - ClientApp/app/shared/constants/request.ts | 3 - ClientApp/app/shared/user.service.ts | 56 ++--- ClientApp/boot.server.ts | 17 +- .../polyfills/temporary-aspnetcore-engine.ts | 206 ------------------ README.md | 2 +- Server/Controllers/HomeController.cs | 2 +- Startup.cs | 2 +- package.json | 2 +- webpack.config.js | 5 +- 22 files changed, 67 insertions(+), 273 deletions(-) delete mode 100644 ClientApp/app/shared/constants/baseurl.constants.ts delete mode 100644 ClientApp/app/shared/constants/request.ts delete mode 100644 ClientApp/polyfills/temporary-aspnetcore-engine.ts diff --git a/ClientApp/app/app.component.html b/ClientApp/app/app.component.html index a3e3bf9b..0345c682 100644 --- a/ClientApp/app/app.component.html +++ b/ClientApp/app/app.component.html @@ -1,5 +1,5 @@
    - +
    diff --git a/ClientApp/app/app.component.ts b/ClientApp/app/app.component.ts index 02b91787..7a6a75ed 100644 --- a/ClientApp/app/app.component.ts +++ b/ClientApp/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, OnDestroy, Inject, ViewEncapsulation, RendererFactory2, PLATFORM_ID } from '@angular/core'; +import { Component, OnInit, OnDestroy, Inject, ViewEncapsulation, RendererFactory2, PLATFORM_ID, Injector } from '@angular/core'; import { Router, NavigationEnd, ActivatedRoute, PRIMARY_OUTLET } from '@angular/router'; import { Meta, Title, DOCUMENT, MetaDefinition } from '@angular/platform-browser'; import { Subscription } from 'rxjs/Subscription'; @@ -7,10 +7,10 @@ import { LinkService } from './shared/link.service'; // i18n support import { TranslateService } from '@ngx-translate/core'; -import { REQUEST } from './shared/constants/request'; +import { REQUEST } from '@nguniversal/aspnetcore-engine'; @Component({ - selector: 'app', + selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], encapsulation: ViewEncapsulation.None @@ -23,6 +23,7 @@ export class AppComponent implements OnInit, OnDestroy { private defaultPageTitle: string = 'My App'; private routerSub$: Subscription; + private request; constructor( private router: Router, @@ -31,7 +32,7 @@ export class AppComponent implements OnInit, OnDestroy { private meta: Meta, private linkService: LinkService, public translate: TranslateService, - @Inject(REQUEST) private request + private injector: Injector ) { // this language will be used as a fallback when a translation isn't found in the current language translate.setDefaultLang('en'); @@ -39,6 +40,8 @@ export class AppComponent implements OnInit, OnDestroy { // the lang to use, if the lang isn't available, it will use the current loader to get them translate.use('en'); + this.request = this.injector.get(REQUEST); + console.log(`What's our REQUEST Object look like?`); console.log(`The Request object only really exists on the Server, but on the Browser we can at least see Cookies`); console.log(this.request); diff --git a/ClientApp/app/app.module.browser.ts b/ClientApp/app/app.module.browser.ts index ac318d44..04ee48c5 100644 --- a/ClientApp/app/app.module.browser.ts +++ b/ClientApp/app/app.module.browser.ts @@ -3,10 +3,9 @@ import { BrowserModule } from '@angular/platform-browser'; import { APP_BASE_HREF } from '@angular/common'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { ORIGIN_URL } from './shared/constants/baseurl.constants'; +import { ORIGIN_URL, REQUEST } from '@nguniversal/aspnetcore-engine'; import { AppModuleShared } from './app.module'; import { AppComponent } from './app.component'; -import { REQUEST } from './shared/constants/request'; import { BrowserTransferStateModule } from '../modules/transfer-state/browser-transfer-state.module'; import { BrowserPrebootModule } from 'preboot/browser'; diff --git a/ClientApp/app/app.module.server.ts b/ClientApp/app/app.module.server.ts index 9e22b71e..48056617 100644 --- a/ClientApp/app/app.module.server.ts +++ b/ClientApp/app/app.module.server.ts @@ -17,7 +17,7 @@ import { ServerPrebootModule } from 'preboot/server'; appId: 'my-app-id' // make sure this matches with your Browser NgModule }), ServerModule, - ServerPrebootModule.recordEvents({ appRoot: 'app' }), + ServerPrebootModule.recordEvents({ appRoot: 'app-root' }), NoopAnimationsModule, ServerTransferStateModule, diff --git a/ClientApp/app/app.module.ts b/ClientApp/app/app.module.ts index cb843d53..cf7e3d97 100644 --- a/ClientApp/app/app.module.ts +++ b/ClientApp/app/app.module.ts @@ -24,7 +24,7 @@ import { NgxBootstrapComponent } from './containers/ngx-bootstrap-demo/ngx-boots import { LinkService } from './shared/link.service'; import { UserService } from './shared/user.service'; // import { ConnectionResolver } from './shared/route.resolver'; -import { ORIGIN_URL } from './shared/constants/baseurl.constants'; +import { ORIGIN_URL } from '@nguniversal/aspnetcore-engine'; import { TransferHttpModule } from '../modules/transfer-http/transfer-http.module'; export function createTranslateLoader(http: HttpClient, baseHref) { diff --git a/ClientApp/app/components/navmenu/navmenu.component.ts b/ClientApp/app/components/navmenu/navmenu.component.ts index ac36f036..fb0cf22c 100644 --- a/ClientApp/app/components/navmenu/navmenu.component.ts +++ b/ClientApp/app/components/navmenu/navmenu.component.ts @@ -1,23 +1,23 @@ import { Component } from '@angular/core'; @Component({ - selector: 'nav-menu', + selector: 'app-nav-menu', templateUrl: './navmenu.component.html', styleUrls: ['./navmenu.component.css'] }) export class NavMenuComponent { - collapse: string = "collapse"; + collapse: string = 'collapse'; collapseNavbar(): void { if (this.collapse.length > 1) { - this.collapse = ""; + this.collapse = ''; } else { - this.collapse = "collapse"; + this.collapse = 'collapse'; } } collapseMenu() { - this.collapse = "collapse" + this.collapse = 'collapse'; } } diff --git a/ClientApp/app/components/user-detail/user-detail.component.ts b/ClientApp/app/components/user-detail/user-detail.component.ts index 9db88355..d21d3415 100644 --- a/ClientApp/app/components/user-detail/user-detail.component.ts +++ b/ClientApp/app/components/user-detail/user-detail.component.ts @@ -3,7 +3,7 @@ import { IUser } from '../../models/User'; import { UserService } from '../../shared/user.service'; @Component({ - selector: 'user-detail', + selector: 'app-user-detail', templateUrl: './user-detail.component.html' }) export class UserDetailComponent { diff --git a/ClientApp/app/containers/counter/counter.component.ts b/ClientApp/app/containers/counter/counter.component.ts index 69de17d9..5adb5195 100644 --- a/ClientApp/app/containers/counter/counter.component.ts +++ b/ClientApp/app/containers/counter/counter.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; @Component({ - selector: 'counter', + selector: 'app-counter', templateUrl: './counter.component.html' }) export class CounterComponent { diff --git a/ClientApp/app/containers/lazy/lazy.component.ts b/ClientApp/app/containers/lazy/lazy.component.ts index 25d6d1a4..97d260fd 100644 --- a/ClientApp/app/containers/lazy/lazy.component.ts +++ b/ClientApp/app/containers/lazy/lazy.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; @Component({ - selector: 'lazy-view', + selector: 'app-lazy-view', template: `

    Lazy-Loaded Component!

    diff --git a/ClientApp/app/containers/not-found/not-found.component.ts b/ClientApp/app/containers/not-found/not-found.component.ts index e39faa05..c59a5f8f 100644 --- a/ClientApp/app/containers/not-found/not-found.component.ts +++ b/ClientApp/app/containers/not-found/not-found.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; @Component({ - selector: 'not-found', + selector: 'app-not-found', templateUrl: './not-found.component.html' }) export class NotFoundComponent implements OnInit { diff --git a/ClientApp/app/containers/users/users.component.html b/ClientApp/app/containers/users/users.component.html index 5bb77691..1e661476 100644 --- a/ClientApp/app/containers/users/users.component.html +++ b/ClientApp/app/containers/users/users.component.html @@ -26,4 +26,4 @@

    Users

- + diff --git a/ClientApp/app/containers/users/users.component.ts b/ClientApp/app/containers/users/users.component.ts index 9dd28781..33c87017 100644 --- a/ClientApp/app/containers/users/users.component.ts +++ b/ClientApp/app/containers/users/users.component.ts @@ -7,7 +7,7 @@ import { IUser } from '../../models/User'; import { UserService } from '../../shared/user.service'; @Component({ - selector: 'users', + selector: 'app-users', templateUrl: './users.component.html', styleUrls: ['./users.component.css'], animations: [ diff --git a/ClientApp/app/shared/constants/baseurl.constants.ts b/ClientApp/app/shared/constants/baseurl.constants.ts deleted file mode 100644 index 58807bc4..00000000 --- a/ClientApp/app/shared/constants/baseurl.constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { InjectionToken } from '@angular/core'; - -export const ORIGIN_URL = new InjectionToken('ORIGIN_URL'); diff --git a/ClientApp/app/shared/constants/request.ts b/ClientApp/app/shared/constants/request.ts deleted file mode 100644 index 4c553d8a..00000000 --- a/ClientApp/app/shared/constants/request.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { InjectionToken } from '@angular/core'; - -export const REQUEST = new InjectionToken('REQUEST'); diff --git a/ClientApp/app/shared/user.service.ts b/ClientApp/app/shared/user.service.ts index 8bcc3bb3..d3b8190a 100644 --- a/ClientApp/app/shared/user.service.ts +++ b/ClientApp/app/shared/user.service.ts @@ -1,42 +1,46 @@ -import { Injectable, Inject } from '@angular/core'; +import { Injectable, Inject, Injector } from '@angular/core'; import { Http, URLSearchParams } from '@angular/http'; import { APP_BASE_HREF } from '@angular/common'; -import { ORIGIN_URL } from './constants/baseurl.constants'; +import { ORIGIN_URL } from '@nguniversal/aspnetcore-engine'; import { IUser } from '../models/User'; import { TransferHttp } from '../../modules/transfer-http/transfer-http'; import { Observable } from 'rxjs/Observable'; @Injectable() export class UserService { - constructor( - private transferHttp: TransferHttp, // Use only for GETS that you want re-used between Server render -> Client render - private http: Http, // Use for everything else - @Inject(ORIGIN_URL) private baseUrl: string) { - } + private baseUrl: string; - getUsers(): Observable { - // ** TransferHttp example / concept ** - // - Here we make an Http call on the server, save the result on the window object and pass it down with the SSR, - // The Client then re-uses this Http result instead of hitting the server again! + constructor( + private transferHttp: TransferHttp, // Use only for GETS that you want re-used between Server render -> Client render + private http: Http, // Use for everything else + private injector: Injector + ) { + this.baseUrl = this.injector.get(ORIGIN_URL); + } - // NOTE : transferHttp also automatically does .map(res => res.json()) for you, so no need for these calls - return this.transferHttp.get(`${this.baseUrl}/api/users`); - } + getUsers(): Observable { + // ** TransferHttp example / concept ** + // - Here we make an Http call on the server, save the result on the window object and pass it down with the SSR, + // The Client then re-uses this Http result instead of hitting the server again! - getUser(user: IUser): Observable { - return this.transferHttp.get(`${this.baseUrl}/api/users/` + user.id); - } + // NOTE : transferHttp also automatically does .map(res => res.json()) for you, so no need for these calls + return this.transferHttp.get(`${this.baseUrl}/api/users`); + } - deleteUser(user: IUser): Observable { - return this.http.delete(`${this.baseUrl}/api/users/` + user.id); - } + getUser(user: IUser): Observable { + return this.transferHttp.get(`${this.baseUrl}/api/users/` + user.id); + } - updateUser(user: IUser): Observable { - return this.http.put(`${this.baseUrl}/api/users/` + user.id, user); - } + deleteUser(user: IUser): Observable { + return this.http.delete(`${this.baseUrl}/api/users/` + user.id); + } - addUser(newUserName: string): Observable { - return this.http.post(`${this.baseUrl}/api/users`, { name: newUserName }) - } + updateUser(user: IUser): Observable { + return this.http.put(`${this.baseUrl}/api/users/` + user.id, user); + } + + addUser(newUserName: string): Observable { + return this.http.post(`${this.baseUrl}/api/users`, { name: newUserName }); + } } diff --git a/ClientApp/boot.server.ts b/ClientApp/boot.server.ts index 1ddac10b..e1311816 100644 --- a/ClientApp/boot.server.ts +++ b/ClientApp/boot.server.ts @@ -1,15 +1,11 @@ import 'zone.js/dist/zone-node'; import './polyfills/server.polyfills'; import { enableProdMode } from '@angular/core'; -import { INITIAL_CONFIG } from '@angular/platform-server'; -import { APP_BASE_HREF } from '@angular/common'; -import { createServerRenderer, RenderResult } from 'aspnet-prerendering'; +import { createServerRenderer } from 'aspnet-prerendering'; -import { ORIGIN_URL } from './app/shared/constants/baseurl.constants'; // Grab the (Node) server-specific NgModule import { AppModule } from './app/app.module.server'; -// Temporary * the engine will be on npm soon (`@universal/ng-aspnetcore-engine`) -import { ngAspnetCoreEngine, IEngineOptions, createTransferScript } from './polyfills/temporary-aspnetcore-engine'; +import { ngAspnetCoreEngine, IEngineOptions, createTransferScript } from '@nguniversal/aspnetcore-engine'; enableProdMode(); @@ -17,11 +13,12 @@ export default createServerRenderer((params) => { // Platform-server provider configuration const setupOptions: IEngineOptions = { - appSelector: '', + appSelector: '', ngModule: AppModule, request: params, providers: [ - // Optional - Any other Server providers you want to pass (remember you'll have to provide them for the Browser as well) + // Optional - Any other Server providers you want to pass + // (remember you'll have to provide them for the Browser as well) ] }; @@ -34,8 +31,8 @@ export default createServerRenderer((params) => { }); return ({ - html: response.html, - globals: response.globals + html: response.html, // our serialized + globals: response.globals // all of our styles/scripts/meta-tags/link-tags for aspnet to serve up }); }); }); diff --git a/ClientApp/polyfills/temporary-aspnetcore-engine.ts b/ClientApp/polyfills/temporary-aspnetcore-engine.ts deleted file mode 100644 index df90a388..00000000 --- a/ClientApp/polyfills/temporary-aspnetcore-engine.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { Type, NgModuleFactory, NgModuleRef, ApplicationRef, Provider, StaticProvider, CompilerFactory, Compiler } from '@angular/core'; -import { platformServer, BEFORE_APP_SERIALIZED, platformDynamicServer, PlatformState, INITIAL_CONFIG, renderModuleFactory } from '@angular/platform-server'; -import { ResourceLoader } from '@angular/compiler'; -import { DOCUMENT } from '@angular/platform-browser'; -import * as fs from 'fs'; - -import { REQUEST } from '../app/shared/constants/request'; -import { ORIGIN_URL } from '../app/shared/constants/baseurl.constants'; - -export function createTransferScript(transferData: Object): string { - return ``; -} - -export class FileLoader implements ResourceLoader { - get(url: string): Promise { - return new Promise((resolve, reject) => { - fs.readFile(url, (err: NodeJS.ErrnoException, buffer: Buffer) => { - if (err) { - return reject(err); - } - resolve(buffer.toString()); - }); - }); - } -} - -export interface IRequestParams { - location: any; // e.g., Location object containing information '/some/path' - origin: string; // e.g., 'https://example.com:1234' - url: string; // e.g., '/some/path' - baseUrl: string; // e.g., '' or '/myVirtualDir' - absoluteUrl: string; // e.g., 'https://example.com:1234/some/path' - domainTasks: Promise; - data: any; // any custom object passed through from .NET -} - -export interface IEngineOptions { - appSelector: string; // e.g., - request: IRequestParams; // e.g., params - ngModule: Type<{}> | NgModuleFactory<{}>; // e.g., AppModule - providers?: StaticProvider[]; // StaticProvider[] -} - -/* @internal */ -export class UniversalData { - public static appNode = ''; - public static title = ''; - public static scripts = ''; - public static styles = ''; - public static meta = ''; - public static links = ''; -} - -/* @internal */ -let appSelector = 'app-root'; // default - -/* @internal */ -function beforeAppSerialized( - doc: any /* TODO: type definition for Domino - DomAPI Spec (similar to "Document") */ -) { - - return () => { - const STYLES = []; - const SCRIPTS = []; - const META = []; - const LINKS = []; - - for (let i = 0; i < doc.head.children.length; i++) { - const element = doc.head.children[i]; - const tagName = element.tagName.toUpperCase(); - - switch (tagName) { - case 'SCRIPT': - SCRIPTS.push(element.outerHTML); - break; - case 'STYLE': - STYLES.push(element.outerHTML); - break; - case 'LINK': - LINKS.push(element.outerHTML); - break; - case 'META': - META.push(element.outerHTML); - break; - default: - break; - } - } - - UniversalData.title = doc.title; - UniversalData.appNode = doc.querySelector(appSelector).outerHTML; - UniversalData.scripts = SCRIPTS.join(' '); - UniversalData.styles = STYLES.join(' '); - UniversalData.meta = META.join(' '); - UniversalData.links = LINKS.join(' '); - }; -} - - -export function ngAspnetCoreEngine( - options: IEngineOptions -): Promise<{ html: string, globals: { styles: string, title: string, meta: string, transferData?: {}, [key: string]: any } }> { - - options.providers = options.providers || []; - - if (!options.appSelector) { - throw new Error(`appSelector is required! Pass in " appSelector: '' ", for your root App component.`); - } - - // Grab the DOM "selector" from the passed in Template for example = "app-root" - appSelector = options.appSelector.substring(1, options.appSelector.indexOf('>')); - - const compilerFactory: CompilerFactory = platformDynamicServer().injector.get(CompilerFactory); - const compiler: Compiler = compilerFactory.createCompiler([ - { - providers: [ - { provide: ResourceLoader, useClass: FileLoader, deps: [] } - ] - } - ]); - - return new Promise((resolve, reject) => { - - try { - const moduleOrFactory = options.ngModule; - if (!moduleOrFactory) { - throw new Error('You must pass in a NgModule or NgModuleFactory to be bootstrapped'); - } - - const extraProviders = options.providers.concat( - options.providers, [{ - provide: ORIGIN_URL, - useValue: options.request.origin - }, { - provide: REQUEST, - useValue: options.request.data.request - }, { - provide: BEFORE_APP_SERIALIZED, - useFactory: beforeAppSerialized, multi: true, deps: [ DOCUMENT ] - } - ] - ); - - getFactory(moduleOrFactory, compiler) - .then(factory => { - return renderModuleFactory(factory, { - document: options.appSelector, - url: options.request.url, - extraProviders: extraProviders - }); - }) - .then((html: string) => { - resolve({ - html: UniversalData.appNode, - globals: { - styles: UniversalData.styles, - title: UniversalData.title, - scripts: UniversalData.scripts, - meta: UniversalData.meta, - links: UniversalData.links - } - }); - }, (err) => { - reject(err); - }); - - } catch (ex) { - reject(ex); - } - - }); - -} - -/* ********************** Private / Internal ****************** */ - -const factoryCacheMap = new Map, NgModuleFactory<{}>>(); -function getFactory( - moduleOrFactory: Type<{}> | NgModuleFactory<{}>, compiler: Compiler -): Promise> { - - return new Promise>((resolve, reject) => { - // If module has been compiled AoT - if (moduleOrFactory instanceof NgModuleFactory) { - resolve(moduleOrFactory); - return; - } else { - let moduleFactory = factoryCacheMap.get(moduleOrFactory); - - // If module factory is cached - if (moduleFactory) { - resolve(moduleFactory); - return; - } - - // Compile the module and cache it - compiler.compileModuleAsync(moduleOrFactory) - .then((factory) => { - factoryCacheMap.set(moduleOrFactory, factory); - resolve(factory); - }, (err => { - reject(err); - })); - } - }); -} diff --git a/README.md b/README.md index 94194632..968be430 100644 --- a/README.md +++ b/README.md @@ -377,7 +377,7 @@ constructor(element: ElementRef, renderer: Renderer2) { ### How can I disable SSR (Server-side rendering)? Simply comment out the logic within HomeController, and replace `@Html.Raw(ViewData["SpaHtml"])` with just your applications root -AppComponent tag ("app" in our case): ``. +AppComponent tag ("app" in our case): ``. > You could also remove any `isPlatformBrowser/etc` logic, and delete the boot.server, app.module.browser & app.module.server files, just make sure your `boot.browser` file points to `app.module`. diff --git a/Server/Controllers/HomeController.cs b/Server/Controllers/HomeController.cs index 8a5a77f6..0a674a60 100644 --- a/Server/Controllers/HomeController.cs +++ b/Server/Controllers/HomeController.cs @@ -21,7 +21,7 @@ public async Task Index() { var prerenderResult = await Request.BuildPrerender(); - ViewData["SpaHtml"] = prerenderResult.Html; // our from Angular + ViewData["SpaHtml"] = prerenderResult.Html; // our from Angular ViewData["Title"] = prerenderResult.Globals["title"]; // set our from Angular ViewData["Styles"] = prerenderResult.Globals["styles"]; // put styles in the correct place ViewData["Scripts"] = prerenderResult.Globals["scripts"]; // scripts (that were in our header) diff --git a/Startup.cs b/Startup.cs index a5edcc97..b7773ac0 100644 --- a/Startup.cs +++ b/Startup.cs @@ -74,7 +74,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { HotModuleReplacement = true, - HotModuleReplacementEndpoint = "/dist/__webpack_hmr" + HotModuleReplacementEndpoint = "/dist/" }); app.UseSwagger(); app.UseSwaggerUI(c => diff --git a/package.json b/package.json index 9bd938e0..fdf5e46d 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@angular/platform-browser-dynamic": "^5.0.0-rc.1", "@angular/platform-server": "^5.0.0-rc.1", "@angular/router": "^5.0.0-rc.1", - "@nguniversal/aspnetcore-engine": "^1.0.0-beta.3", + "@nguniversal/aspnetcore-engine": "^5.0.0-beta.1", "@ngx-translate/core": "^8.0.0", "@ngx-translate/http-loader": "^2.0.0", "@types/node": "^7.0.12", diff --git a/webpack.config.js b/webpack.config.js index a5615cff..eb440b9d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -66,7 +66,10 @@ module.exports = (env) => { exclude: ['./**/*.server.ts'] }) ]), - devtool: isDevBuild ? 'cheap-eval-source-map' : false + devtool: isDevBuild ? 'cheap-eval-source-map' : false, + node: { + fs: "empty" + } }); // Configuration for server-side (prerendering) bundle suitable for running in Node From 8a32e0beedfd72616fd5717c00c9ebea2d06a584 Mon Sep 17 00:00:00 2001 From: Mark Pieszak <mpieszak84@gmail.com> Date: Thu, 2 Nov 2017 10:15:37 -0400 Subject: [PATCH 5/5] update to 5.0 official --- ClientApp/app/app.module.browser.ts | 7 +- ClientApp/app/app.module.server.ts | 23 ++- ClientApp/app/app.module.ts | 20 +-- .../app/containers/users/users.component.ts | 19 +-- ClientApp/app/shared/user.service.ts | 31 ++-- ClientApp/boot.browser.ts | 2 - ClientApp/boot.server.ts | 7 +- .../transfer-http/transfer-http.module.ts | 10 -- .../modules/transfer-http/transfer-http.ts | 152 ------------------ .../browser-transfer-state.module.ts | 20 --- .../server-transfer-state.module.ts | 12 -- .../transfer-state/server-transfer-state.ts | 36 ----- .../modules/transfer-state/transfer-state.ts | 40 ----- README.md | 2 +- package.json | 29 ++-- 15 files changed, 64 insertions(+), 346 deletions(-) delete mode 100644 ClientApp/modules/transfer-http/transfer-http.module.ts delete mode 100644 ClientApp/modules/transfer-http/transfer-http.ts delete mode 100644 ClientApp/modules/transfer-state/browser-transfer-state.module.ts delete mode 100644 ClientApp/modules/transfer-state/server-transfer-state.module.ts delete mode 100644 ClientApp/modules/transfer-state/server-transfer-state.ts delete mode 100644 ClientApp/modules/transfer-state/transfer-state.ts diff --git a/ClientApp/app/app.module.browser.ts b/ClientApp/app/app.module.browser.ts index 04ee48c5..56d1c20c 100644 --- a/ClientApp/app/app.module.browser.ts +++ b/ClientApp/app/app.module.browser.ts @@ -6,8 +6,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ORIGIN_URL, REQUEST } from '@nguniversal/aspnetcore-engine'; import { AppModuleShared } from './app.module'; import { AppComponent } from './app.component'; -import { BrowserTransferStateModule } from '../modules/transfer-state/browser-transfer-state.module'; - +import { BrowserTransferStateModule } from '@angular/platform-browser'; import { BrowserPrebootModule } from 'preboot/browser'; export function getOriginUrl() { @@ -22,12 +21,8 @@ export function getRequest() { @NgModule({ bootstrap: [AppComponent], imports: [ - BrowserModule.withServerTransition({ - appId: 'my-app-id' // make sure this matches with your Server NgModule - }), BrowserPrebootModule.replayEvents(), BrowserAnimationsModule, - BrowserTransferStateModule, // Our Common AppModule AppModuleShared diff --git a/ClientApp/app/app.module.server.ts b/ClientApp/app/app.module.server.ts index 48056617..25711851 100644 --- a/ClientApp/app/app.module.server.ts +++ b/ClientApp/app/app.module.server.ts @@ -5,33 +5,28 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { AppModuleShared } from './app.module'; import { AppComponent } from './app.component'; -import { ServerTransferStateModule } from '../modules/transfer-state/server-transfer-state.module'; -import { TransferState } from '../modules/transfer-state/transfer-state'; +import { ServerTransferStateModule } from '@angular/platform-server'; import { ServerPrebootModule } from 'preboot/server'; @NgModule({ bootstrap: [AppComponent], imports: [ - BrowserModule.withServerTransition({ - appId: 'my-app-id' // make sure this matches with your Browser NgModule - }), + // Our Common AppModule + AppModuleShared, + ServerModule, ServerPrebootModule.recordEvents({ appRoot: 'app-root' }), NoopAnimationsModule, - ServerTransferStateModule, - - // Our Common AppModule - AppModuleShared + // HttpTransferCacheModule still needs fixes for 5.0 + // Leave this commented out for now, as it breaks Server-renders + // Looking into fixes for this! - @MarkPieszak + // ServerTransferStateModule // <-- broken for the time-being with ASP.NET ] }) export class AppModule { - constructor(private transferState: TransferState) { } + constructor() { } - // Gotcha (needs to be an arrow function) - ngOnBootstrap = () => { - this.transferState.inject(); - } } diff --git a/ClientApp/app/app.module.ts b/ClientApp/app/app.module.ts index cf7e3d97..d31250c4 100644 --- a/ClientApp/app/app.module.ts +++ b/ClientApp/app/app.module.ts @@ -4,6 +4,8 @@ import { CommonModule, APP_BASE_HREF } from '@angular/common'; import { HttpModule, Http } from '@angular/http'; import { HttpClientModule, HttpClient } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; +import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser'; +import { TransferHttpCacheModule } from '@nguniversal/common'; import { Ng2BootstrapModule } from 'ngx-bootstrap'; @@ -17,15 +19,12 @@ import { HomeComponent } from './containers/home/home.component'; import { UsersComponent } from './containers/users/users.component'; import { UserDetailComponent } from './components/user-detail/user-detail.component'; import { CounterComponent } from './containers/counter/counter.component'; -// import { ChatComponent } from './containers/chat/chat.component'; import { NotFoundComponent } from './containers/not-found/not-found.component'; import { NgxBootstrapComponent } from './containers/ngx-bootstrap-demo/ngx-bootstrap.component'; import { LinkService } from './shared/link.service'; import { UserService } from './shared/user.service'; -// import { ConnectionResolver } from './shared/route.resolver'; import { ORIGIN_URL } from '@nguniversal/aspnetcore-engine'; -import { TransferHttpModule } from '../modules/transfer-http/transfer-http.module'; export function createTranslateLoader(http: HttpClient, baseHref) { // Temporary Azure hack @@ -44,19 +43,22 @@ export function createTranslateLoader(http: HttpClient, baseHref) { UsersComponent, UserDetailComponent, HomeComponent, - // ChatComponent, NotFoundComponent, NgxBootstrapComponent ], imports: [ CommonModule, - HttpModule, + BrowserModule.withServerTransition({ + appId: 'my-app-id' // make sure this matches with your Server NgModule + }), HttpClientModule, + TransferHttpCacheModule, + BrowserTransferStateModule, + + FormsModule, Ng2BootstrapModule.forRoot(), // You could also split this up if you don't want the Entire Module imported - TransferHttpModule, // Our Http TransferData method - // i18n support TranslateModule.forRoot({ loader: { @@ -147,9 +149,9 @@ export function createTranslateLoader(http: HttpClient, baseHref) { providers: [ LinkService, UserService, - // ConnectionResolver, TranslateModule - ] + ], + bootstrap: [AppComponent] }) export class AppModuleShared { } diff --git a/ClientApp/app/containers/users/users.component.ts b/ClientApp/app/containers/users/users.component.ts index 33c87017..35cc8517 100644 --- a/ClientApp/app/containers/users/users.component.ts +++ b/ClientApp/app/containers/users/users.component.ts @@ -31,15 +31,16 @@ export class UsersComponent implements OnInit { selectedUser: IUser; // Use "constructor"s only for dependency injection - constructor(private userService: UserService) { } + constructor( + private userService: UserService + ) { } // Here you want to handle anything with @Input()'s @Output()'s // Data retrieval / etc - this is when the Component is "ready" and wired up ngOnInit() { this.userService.getUsers().subscribe(result => { - console.log('Get user result: ', result); - console.log('TransferHttp [GET] /api/users/allresult', result); - this.users = result as IUser[]; + console.log('HttpClient [GET] /api/users/allresult', result); + this.users = result; }); } @@ -50,10 +51,8 @@ export class UsersComponent implements OnInit { deleteUser(user) { this.userService.deleteUser(user).subscribe(result => { console.log('Delete user result: ', result); - if (result.ok) { - let position = this.users.indexOf(user); - this.users.splice(position, 1); - } + let position = this.users.indexOf(user); + this.users.splice(position, 1); }, error => { console.log(`There was an issue. ${error._body}.`); }); @@ -62,9 +61,7 @@ export class UsersComponent implements OnInit { addUser(newUserName) { this.userService.addUser(newUserName).subscribe(result => { console.log('Post user result: ', result); - if (result.ok) { - this.users.push(result.json()); - } + this.users.push(result); }, error => { console.log(`There was an issue. ${error._body}.`); }); diff --git a/ClientApp/app/shared/user.service.ts b/ClientApp/app/shared/user.service.ts index d3b8190a..9489e32f 100644 --- a/ClientApp/app/shared/user.service.ts +++ b/ClientApp/app/shared/user.service.ts @@ -1,46 +1,41 @@ import { Injectable, Inject, Injector } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; import { Http, URLSearchParams } from '@angular/http'; import { APP_BASE_HREF } from '@angular/common'; import { ORIGIN_URL } from '@nguniversal/aspnetcore-engine'; import { IUser } from '../models/User'; -import { TransferHttp } from '../../modules/transfer-http/transfer-http'; import { Observable } from 'rxjs/Observable'; + @Injectable() export class UserService { private baseUrl: string; constructor( - private transferHttp: TransferHttp, // Use only for GETS that you want re-used between Server render -> Client render - private http: Http, // Use for everything else + private http: HttpClient, private injector: Injector ) { this.baseUrl = this.injector.get(ORIGIN_URL); } - getUsers(): Observable<IUser[]> { - // ** TransferHttp example / concept ** - // - Here we make an Http call on the server, save the result on the window object and pass it down with the SSR, - // The Client then re-uses this Http result instead of hitting the server again! - - // NOTE : transferHttp also automatically does .map(res => res.json()) for you, so no need for these calls - return this.transferHttp.get(`${this.baseUrl}/api/users`); + getUsers() { + return this.http.get<IUser[]>(`${this.baseUrl}/api/users`); } - getUser(user: IUser): Observable<IUser> { - return this.transferHttp.get(`${this.baseUrl}/api/users/` + user.id); + getUser(user: IUser) { + return this.http.get<IUser>(`${this.baseUrl}/api/users/` + user.id); } - deleteUser(user: IUser): Observable<any> { - return this.http.delete(`${this.baseUrl}/api/users/` + user.id); + deleteUser(user: IUser) { + return this.http.delete<IUser>(`${this.baseUrl}/api/users/` + user.id); } - updateUser(user: IUser): Observable<any> { - return this.http.put(`${this.baseUrl}/api/users/` + user.id, user); + updateUser(user: IUser){ + return this.http.put<IUser>(`${this.baseUrl}/api/users/` + user.id, user); } - addUser(newUserName: string): Observable<any> { - return this.http.post(`${this.baseUrl}/api/users`, { name: newUserName }); + addUser(newUserName: string) { + return this.http.post<IUser>(`${this.baseUrl}/api/users`, { name: newUserName }); } } diff --git a/ClientApp/boot.browser.ts b/ClientApp/boot.browser.ts index a7830543..329344f7 100644 --- a/ClientApp/boot.browser.ts +++ b/ClientApp/boot.browser.ts @@ -3,8 +3,6 @@ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module.browser'; -const rootElemTagName = 'app'; // Update this if you change your root component selector - // // Enable either Hot Module Reloading or production mode if (module['hot']) { module['hot'].accept(); diff --git a/ClientApp/boot.server.ts b/ClientApp/boot.server.ts index e1311816..a7c2f9f5 100644 --- a/ClientApp/boot.server.ts +++ b/ClientApp/boot.server.ts @@ -11,6 +11,8 @@ enableProdMode(); export default createServerRenderer((params) => { + console.log('server ?'); + // Platform-server provider configuration const setupOptions: IEngineOptions = { appSelector: '<app-root></app-root>', @@ -24,6 +26,9 @@ export default createServerRenderer((params) => { return ngAspnetCoreEngine(setupOptions).then(response => { + // console.log('\n\n\naspnet-engine!!!\n\n\n') + console.log(response) + // Apply your transferData to response.globals response.globals.transferData = createTransferScript({ someData: 'Transfer this to the client on the window.TRANSFER_CACHE {} object', @@ -31,7 +36,7 @@ export default createServerRenderer((params) => { }); return ({ - html: response.html, // our <app> serialized + html: response.html, // our <app-root> serialized globals: response.globals // all of our styles/scripts/meta-tags/link-tags for aspnet to serve up }); }); diff --git a/ClientApp/modules/transfer-http/transfer-http.module.ts b/ClientApp/modules/transfer-http/transfer-http.module.ts deleted file mode 100644 index c2875b33..00000000 --- a/ClientApp/modules/transfer-http/transfer-http.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NgModule } from '@angular/core'; -import { Http, HttpModule } from '@angular/http'; -import { TransferHttp } from './transfer-http'; - -@NgModule({ - providers: [ - TransferHttp - ] -}) -export class TransferHttpModule {} diff --git a/ClientApp/modules/transfer-http/transfer-http.ts b/ClientApp/modules/transfer-http/transfer-http.ts deleted file mode 100644 index 3f9b5d84..00000000 --- a/ClientApp/modules/transfer-http/transfer-http.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { Injectable, Inject, PLATFORM_ID } from '@angular/core'; -import { ConnectionBackend, Http, Request, RequestOptions, RequestOptionsArgs, Response } from '@angular/http'; -import { Observable } from 'rxjs/Observable'; -import { Subject } from 'rxjs/Subject'; -import { TransferState } from '../transfer-state/transfer-state'; -import { isPlatformServer } from '@angular/common'; - -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/do'; -import 'rxjs/add/observable/fromPromise'; - -@Injectable() -export class TransferHttp { - - private isServer = isPlatformServer(this.platformId); - - constructor( - @Inject(PLATFORM_ID) private platformId, - private http: Http, - protected transferState: TransferState - ) { } - - request(uri: string | Request, options?: RequestOptionsArgs): Observable<any> { - return this.getData(uri, options, (url: string, options: RequestOptionsArgs) => { - return this.http.request(url, options); - }); - } - /** - * Performs a request with `get` http method. - */ - get(url: string, options?: RequestOptionsArgs): Observable<any> { - return this.getData(url, options, (url: string, options: RequestOptionsArgs) => { - return this.http.get(url, options); - }); - } - /** - * Performs a request with `post` http method. - */ - post(url: string, body: any, options?: RequestOptionsArgs): Observable<any> { - return this.getPostData(url, body, options, (url: string, body: any, options: RequestOptionsArgs) => { - return this.http.post(url, body, options); - }); - } - /** - * Performs a request with `put` http method. - */ - put(url: string, body: any, options?: RequestOptionsArgs): Observable<any> { - - return this.getPostData(url, body, options, (url: string, body: any, options: RequestOptionsArgs) => { - return this.http.put(url, body, options); - }); - } - /** - * Performs a request with `delete` http method. - */ - delete(url: string, options?: RequestOptionsArgs): Observable<any> { - return this.getData(url, options, (url: string, options: RequestOptionsArgs) => { - return this.http.delete(url, options); - }); - } - /** - * Performs a request with `patch` http method. - */ - patch(url: string, body: any, options?: RequestOptionsArgs): Observable<any> { - return this.getPostData(url, body, options, (url: string, body: any, options: RequestOptionsArgs) => { - return this.http.patch(url, body.options); - }); - } - /** - * Performs a request with `head` http method. - */ - head(url: string, options?: RequestOptionsArgs): Observable<any> { - return this.getData(url, options, (url: string, options: RequestOptionsArgs) => { - return this.http.head(url, options); - }); - } - /** - * Performs a request with `options` http method. - */ - options(url: string, options?: RequestOptionsArgs): Observable<any> { - return this.getData(url, options, (url: string, options: RequestOptionsArgs) => { - return this.http.options(url, options); - }); - } - - private getData(uri: string | Request, options: RequestOptionsArgs, callback: (uri: string | Request, options?: RequestOptionsArgs) => Observable<Response>) { - - let url = uri; - - if (typeof uri !== 'string') { - url = uri.url; - } - - const key = url + JSON.stringify(options); - - try { - return this.resolveData(key); - - } catch (e) { - return callback(url, options) - .map(res => res.json()) - .do(data => { - if (this.isServer) { - this.setCache(key, data); - } - }); - } - } - - private getPostData(uri: string | Request, body: any, options: RequestOptionsArgs, callback: (uri: string | Request, body: any, options?: RequestOptionsArgs) => Observable<Response>) { - - let url = uri; - - if (typeof uri !== 'string') { - url = uri.url; - } - - const key = url + JSON.stringify(body); - - try { - - return this.resolveData(key); - - } catch (e) { - return callback(uri, body, options) - .map(res => res.json()) - .do(data => { - if (this.isServer) { - this.setCache(key, data); - } - }); - } - } - - private resolveData(key: string) { - const data = this.getFromCache(key); - - if (!data) { - throw new Error(); - } - - return Observable.fromPromise(Promise.resolve(data)); - } - - private setCache(key, data) { - return this.transferState.set(key, data); - } - - private getFromCache(key): any { - return this.transferState.get(key); - } -} diff --git a/ClientApp/modules/transfer-state/browser-transfer-state.module.ts b/ClientApp/modules/transfer-state/browser-transfer-state.module.ts deleted file mode 100644 index 20e11421..00000000 --- a/ClientApp/modules/transfer-state/browser-transfer-state.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NgModule, PLATFORM_ID } from '@angular/core'; -import { TransferState } from './transfer-state'; - -export function getTransferState(): TransferState { - const transferState = new TransferState(); - transferState.initialize(window['TRANSFER_STATE'] || {}); - return transferState; -} - -@NgModule({ - providers: [ - { - provide: TransferState, - useFactory: getTransferState - } - ] -}) -export class BrowserTransferStateModule { - -} diff --git a/ClientApp/modules/transfer-state/server-transfer-state.module.ts b/ClientApp/modules/transfer-state/server-transfer-state.module.ts deleted file mode 100644 index 1a77f653..00000000 --- a/ClientApp/modules/transfer-state/server-transfer-state.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule } from '@angular/core'; -import { ServerTransferState } from './server-transfer-state'; -import { TransferState } from './transfer-state'; - -@NgModule({ - providers: [ - { provide: TransferState, useClass: ServerTransferState } - ] -}) -export class ServerTransferStateModule { - -} diff --git a/ClientApp/modules/transfer-state/server-transfer-state.ts b/ClientApp/modules/transfer-state/server-transfer-state.ts deleted file mode 100644 index b2890b26..00000000 --- a/ClientApp/modules/transfer-state/server-transfer-state.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Injectable, Optional, RendererFactory2, ViewEncapsulation, Inject, PLATFORM_ID } from '@angular/core'; -import { TransferState } from './transfer-state'; -import { PlatformState } from '@angular/platform-server'; -@Injectable() -export class ServerTransferState extends TransferState { - constructor(private state: PlatformState, private rendererFactory: RendererFactory2) { - super(); - } - - /** - * Inject the State into the bottom of the <head> - */ - inject() { - try { - const document: any = this.state.getDocument(); - const transferStateString = JSON.stringify(this.toJson()); - const renderer = this.rendererFactory.createRenderer(document, { - id: '-1', - encapsulation: ViewEncapsulation.None, - styles: [], - data: {} - }); - - const body = document.body; - - const script = renderer.createElement('script'); - renderer.setValue(script, `window['TRANSFER_STATE'] = ${transferStateString}`); - renderer.appendChild(body, script); - } catch (e) { - console.log('Failed to append TRANSFER_STATE to body'); - console.error(e); - } - } - - -} diff --git a/ClientApp/modules/transfer-state/transfer-state.ts b/ClientApp/modules/transfer-state/transfer-state.ts deleted file mode 100644 index cc963b8f..00000000 --- a/ClientApp/modules/transfer-state/transfer-state.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Injectable, Inject, PLATFORM_ID } from '@angular/core'; - -@Injectable() -export class TransferState { - private _map = new Map<string, any>(); - - constructor() { } - - keys() { - return this._map.keys(); - } - - get(key: string): any { - const cachedValue = this._map.get(key); - this._map.delete(key); - return cachedValue; - } - - set(key: string, value: any): Map<string, any> { - return this._map.set(key, value); - } - - toJson(): any { - const obj = {}; - Array.from(this.keys()) - .forEach(key => { - obj[key] = this.get(key); - }); - return obj; - } - - initialize(obj: any): void { - Object.keys(obj) - .forEach(key => { - this.set(key, obj[key]); - }); - } - - inject(): void { } -} diff --git a/README.md b/README.md index 968be430..bfbcecc6 100644 --- a/README.md +++ b/README.md @@ -377,7 +377,7 @@ constructor(element: ElementRef, renderer: Renderer2) { ### How can I disable SSR (Server-side rendering)? Simply comment out the logic within HomeController, and replace `@Html.Raw(ViewData["SpaHtml"])` with just your applications root -AppComponent tag ("app" in our case): `<app-root></app-root>`. +AppComponent tag ("app-root" in our case): `<app-root></app-root>`. > You could also remove any `isPlatformBrowser/etc` logic, and delete the boot.server, app.module.browser & app.module.server files, just make sure your `boot.browser` file points to `app.module`. diff --git a/package.json b/package.json index fdf5e46d..583b77d0 100644 --- a/package.json +++ b/package.json @@ -15,18 +15,19 @@ "clean": "rimraf wwwroot/dist clientapp/dist" }, "dependencies": { - "@angular/animations": "^5.0.0-rc.1", - "@angular/common": "^5.0.0-rc.1", - "@angular/compiler": "^5.0.0-rc.1", - "@angular/compiler-cli": "^5.0.0-rc.1", - "@angular/core": "^5.0.0-rc.1", - "@angular/forms": "^5.0.0-rc.1", - "@angular/http": "^5.0.0-rc.1", - "@angular/platform-browser": "^5.0.0-rc.1", - "@angular/platform-browser-dynamic": "^5.0.0-rc.1", - "@angular/platform-server": "^5.0.0-rc.1", - "@angular/router": "^5.0.0-rc.1", - "@nguniversal/aspnetcore-engine": "^5.0.0-beta.1", + "@angular/animations": "^5.0.0", + "@angular/common": "^5.0.0", + "@angular/compiler": "^5.0.0", + "@angular/compiler-cli": "^5.0.0", + "@angular/core": "^5.0.0", + "@angular/forms": "^5.0.0", + "@angular/http": "^5.0.0", + "@angular/platform-browser": "^5.0.0", + "@angular/platform-browser-dynamic": "^5.0.0", + "@angular/platform-server": "^5.0.0", + "@angular/router": "^5.0.0", + "@nguniversal/aspnetcore-engine": "^5.0.0-beta.5", + "@nguniversal/common": "^5.0.0-beta.5", "@ngx-translate/core": "^8.0.0", "@ngx-translate/http-loader": "^2.0.0", "@types/node": "^7.0.12", @@ -66,8 +67,8 @@ "zone.js": "^0.8.17" }, "devDependencies": { - "@angular/cli": "^1.5.0-beta.4", - "@ngtools/webpack": "^1.5.0-beta.4", + "@angular/cli": "^1.5.0", + "@ngtools/webpack": "^1.8.0", "@types/chai": "^3.4.34", "@types/jasmine": "^2.5.37", "chai": "^3.5.0",