Skip to content

Commit

Permalink
Angular universal integration (#224)
Browse files Browse the repository at this point in the history
Why?
In order to support SEO, we should have some provision to allow google crawl the webpages with all the relevant information. This needed a requirement for server side rendered version of this application.
This change addresses the need by:
Adds Angular universal support.
Updated the code using window, document, local storage with Browser platform checks
Updated meta information for main app component.
[delivers #159558419]
  • Loading branch information
pkrawat1 committed Aug 21, 2018
1 parent a6ec2da commit f945098
Show file tree
Hide file tree
Showing 33 changed files with 1,172 additions and 645 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
"*.csproj.user": true,
"*.suo": true,
".docker": false,
"docs": false
"docs": true
}
}
52 changes: 51 additions & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"outputPath": "dist/browser",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
Expand Down Expand Up @@ -106,6 +106,56 @@
}
}
},
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/server",
"main": "src/main.server.ts",
"tsConfig": "src/tsconfig.server.json"
},
"configurations": {
"mock-ng-spree": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.mock-ng-spree.ts"
}
]
},
"dev-ng-spree": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev-ng-spree.ts"
}
]
},
"prod-ng-spree": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod-ng-spree.ts"
}
]
},
"dev-custom": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev-custom.ts"
}
]
},
"prod-custom": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod-custom.ts"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
Expand Down
2 changes: 1 addition & 1 deletion firebase.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"hosting": {
"public": "dist",
"public": "dist/browser",
"rewrites": [
{
"source": "**",
Expand Down
17 changes: 14 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@
"compodoc": "./node_modules/.bin/compodoc -p src/tsconfig.app.json -d docs/",
"sw": "sw-precache --root=dist --config=sw-precache-config.js",
"bundle-report": "webpack-bundle-analyzer dist/stats.json",
"static-serve": "cd dist && live-server --port=4200 --host=localhost --entry-file=/index.html"
"static-serve": "cd dist/browser && live-server --port=4200 --host=localhost --entry-file=/index.html",
"start:ssr:prod-custom": "npm run build:client-and-server-bundles:prod-custom && npm run webpack:server && node dist/server",
"start:ssr:prod-ng-spree": "npm run build:client-and-server-bundles:prod-ng-spree && npm run webpack:server && node dist/server",
"build:client-and-server-bundles:prod-custom": "npm run build:prod-custom && ng run angularspree:server:prod-custom",
"build:client-and-server-bundles:prod-ng-spree": "npm run build:prod-ng-spree && ng run angularspree:server:prod-ng-spree",
"webpack:server": "webpack --config webpack.server.config.js --progress --colors"
},
"private": true,
"dependencies": {
Expand All @@ -32,6 +37,7 @@
"@angular/http": "^6.1.1",
"@angular/platform-browser": "^6.1.1",
"@angular/platform-browser-dynamic": "^6.1.1",
"@angular/platform-server": "^6.1.1",
"@angular/pwa": "^0.7.2",
"@angular/router": "^6.1.1",
"@angular/service-worker": "^6.1.1",
Expand All @@ -41,6 +47,9 @@
"@ngrx/router-store": "^6.1.0",
"@ngrx/store": "^6.1.0",
"@ngu/carousel": "^1.4.8",
"@nguniversal/common": "^6.0.0",
"@nguniversal/express-engine": "^6.0.0",
"@nguniversal/module-map-ngfactory-loader": "^6.0.0",
"@ngx-lite/input-star-rating": "^0.1.5",
"@ngx-lite/json-ld": "^0.4.2",
"@ngx-progressbar/core": "^5.0.1",
Expand All @@ -60,6 +69,7 @@
"reselect": "^3.0.1",
"rxjs": "^6.2.2",
"rxjs-compat": "^6.2.2",
"ts-loader": "^4.4.2",
"web-animations-js": "^2.3.1",
"webpack-bundle-analyzer": "^2.13.1",
"zone.js": "^0.8.26"
Expand Down Expand Up @@ -96,10 +106,11 @@
"sw-precache": "^5.2.1",
"ts-node": "~7.0.0",
"tslint": "~5.11.0",
"typescript": "~2.9.0"
"typescript": "~2.9.0",
"webpack-cli": "^3.1.0"
},
"description": "Spree for Angular2",
"main": "index.js",
"repository": "git@github.com:aviabird/angularspree.git",
"author": "Pankaj Rawat <pankajrawat19sept@gmail.com>"
}
}
53 changes: 53 additions & 0 deletions server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';

import { enableProdMode } from '@angular/core';

import * as express from 'express';
import { join } from 'path';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
const app = express();

const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');

// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';

app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));

app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));

// TODO: implement data requests securely
app.get('/api/*', (req, res) => {
res.status(404).send('data requests are not supported');
});

// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});

// Start up the Node server
app.listen(PORT, () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});
40 changes: 24 additions & 16 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { AppState } from './interfaces';
import { Store } from '@ngrx/store';
import { Subscription, Observable } from 'rxjs';
import { CheckoutService } from './core/services/checkout.service';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Component, OnInit, OnDestroy, Inject, PLATFORM_ID } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { Title, Meta } from '@angular/platform-browser';
import { isPlatformBrowser } from '../../node_modules/@angular/common';

@Component({
selector: 'app-root',
Expand All @@ -22,28 +23,33 @@ export class AppComponent implements OnInit, OnDestroy {
currentStep: string;
checkoutUrls = ['/checkout/cart', '/checkout/address', '/checkout/payment'];
layoutState$: Observable<LayoutState>;
schema = {
'@context': 'https://schema.org',
'@type': 'Organization',
'name': environment.appName,
'url': location.origin
};
schema = {};

constructor(
private router: Router,
private checkoutService: CheckoutService,
private store: Store<AppState>,
private metaTitle: Title,
private meta: Meta
private meta: Meta,
@Inject(PLATFORM_ID) private platformId: any
) {
this.router.events
.pipe(filter(e => e instanceof NavigationEnd))
.subscribe((e: NavigationEnd) => {
this.currentUrl = e.url;
this.findCurrentStep(this.currentUrl);
window.scrollTo(0, 0);
if (isPlatformBrowser(this.platformId)) {
window.scrollTo(0, 0);
}
this.addMetaInfo();
});

this.schema = {
'@context': 'https://schema.org',
'@type': 'Organization',
'name': environment.appName,
'url': isPlatformBrowser(this.platformId) ? location.origin : ''
};
}

ngOnInit() {
Expand All @@ -58,13 +64,15 @@ export class AppComponent implements OnInit, OnDestroy {
}

addFaviconIcon() {
const link =
document.querySelector(`link[rel*='icon']`) ||
(document.createElement('link') as any);
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = environment.config.fevicon;
document.getElementsByTagName('head')[0].appendChild(link);
if (isPlatformBrowser(this.platformId)) {
const link =
document.querySelector(`link[rel*='icon']`) ||
(document.createElement('link') as any);
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
link.href = environment.config.fevicon;
document.getElementsByTagName('head')[0].appendChild(link);
}
}

isCheckoutRoute() {
Expand Down
7 changes: 5 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { AppPreloadingStrategy } from './app_preloading_strategy';
import { myAuthConfig } from './oauth_config';
import { Ng2UiAuthModule } from 'ng2-ui-auth';
import { EffectsModule } from '@ngrx/effects';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
import {TransferHttpCacheModule} from '@nguniversal/common';

// Components
import { AppComponent } from './app.component';
Expand Down Expand Up @@ -60,7 +61,9 @@ import { ToastrModule } from 'ngx-toastr';
*/
EffectsModule.forRoot([]),
BrowserAnimationsModule,
BrowserModule,
BrowserModule.withServerTransition({ appId: 'ng-spree' }),
BrowserTransferStateModule,
TransferHttpCacheModule,
FormsModule,
HttpModule,
HomeModule,
Expand Down
20 changes: 20 additions & 0 deletions src/app/app.server.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NgModule } from '@angular/core';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';

import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule,
ServerTransferStateModule
],
providers: [
// Add universal-only providers here
],
bootstrap: [ AppComponent ],
})
export class AppServerModule {}
9 changes: 6 additions & 3 deletions src/app/checkout/cart/cart.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { getTotalCartValue, getTotalCartItems, getItemTotal } from './../reducer
import { Observable } from 'rxjs';
import { AppState } from './../../interfaces';
import { Store } from '@ngrx/store';
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, PLATFORM_ID, Inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@Component({
selector: 'app-cart',
Expand All @@ -17,14 +18,16 @@ export class CartComponent implements OnInit {
shipTotal$: Observable<number>;
itemTotal$: Observable<number>;

constructor(private store: Store<AppState>) {
constructor(private store: Store<AppState>, @Inject(PLATFORM_ID) private platformId: any) {
this.totalCartValue$ = this.store.select(getTotalCartValue);
this.totalCartItems$ = this.store.select(getTotalCartItems);
this.itemTotal$ = this.store.select(getItemTotal);
}

ngOnInit() {
this.screenwidth = window.innerWidth;
if (isPlatformBrowser(this.platformId)) {
this.screenwidth = window.innerWidth;
}
this.calculateInnerWidth();
}
calculateInnerWidth() {
Expand Down
13 changes: 9 additions & 4 deletions src/app/checkout/order-failed/order-failed.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { LineItem } from './../../core/models/line_item';
import { Order } from './../../core/models/order';
import { UserService } from './../../user/services/user.service';
import { ActivatedRoute, Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, PLATFORM_ID, Inject } from '@angular/core';
import { CheckoutService } from '../../core/services/checkout.service';
import { isPlatformBrowser } from '../../../../node_modules/@angular/common';

@Component({
selector: 'app-order-failed',
Expand All @@ -20,7 +21,9 @@ export class OrderFailedComponent implements OnInit {
private userService: UserService,
private activatedRouter: ActivatedRoute,
private route: Router,
private checkoutService: CheckoutService) {
private checkoutService: CheckoutService,
@Inject(PLATFORM_ID) private platformId: any
) {
this.activatedRouter.queryParams
.subscribe(params => {
this.queryParams = params
Expand All @@ -47,8 +50,10 @@ export class OrderFailedComponent implements OnInit {
retryPayment(order: Order) {
this.checkoutService.makePayment(+order.total, order.bill_address, order.number)
.subscribe((response: any) => {
response = response
window.open(response.url, '_self');
response = response;
if (isPlatformBrowser(this.platformId)) {
window.open(response.url, '_self');
}
});
}

Expand Down
Loading

0 comments on commit f945098

Please sign in to comment.