New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding TransferState #136

Merged
merged 1 commit into from Mar 20, 2018
Jump to file or symbol
Failed to load files and symbols.
+205 −7
Diff settings

Always

Just for now

@@ -10,6 +10,7 @@ header {
text-align: center;
position: fixed;
width: 100%;
z-index: 100;
}
h2 {
View
@@ -11,12 +11,24 @@ import { NotificationService } from './services/notification.service';
import { Meta } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
import { DonorsComponent } from './donors/donors.component';
import { ExampleApiService } from './services/exampleApi.service';
import { WithTransferStateComponent } from './transferState/withTransferState.component';
import { PingWithTransferStateResolver } from './services/resolvers/pingWithTransferState.resolver';
import { TransferStateComponent } from './transferState/transferState.component';
import { WithoutTransferStateComponent } from './transferState/withoutTransferState.component';
import { PingWithoutTransferStateResolver } from './services/resolvers/pingWithoutTransferState.resolver';
import { CountResolver } from './services/resolvers/count.resolver';
const routes: any[] = [
{ path: '', component: HomeComponent, data: {title: 'Home', description: 'Home.'}},
{ path: 'donors', component: DonorsComponent, data: {title: 'Donors', description: 'List of donations.'}},
{ path: 'lazy', loadChildren: './lazy/lazy.module#LazyModule', data: {title: 'Lazy module', description: 'Lazy module example.'}},
{ path: 'external', loadChildren: '@angular-universal-serverless/external-module/release#ExternalModule', data: {title: 'External module', description: 'External module example.'}}
{ path: 'external', loadChildren: '@angular-universal-serverless/external-module/release#ExternalModule', data: {title: 'External module', description: 'External module example.'}},
{ path: 'transferState', children: [
{ path: '', component: TransferStateComponent, data: {title: 'Transfer state (API)', description: 'Angular TransferState example'}},
{ path: 'with', component: WithTransferStateComponent, data: {title: 'Transfer state (API)', description: 'Angular TransferState example'}, resolve: {ping: PingWithTransferStateResolver, count: CountResolver}},
{ path: 'without', component: WithoutTransferStateComponent, data: {title: 'Transfer state (API)', description: 'Angular TransferState example'}, resolve: {ping: PingWithoutTransferStateResolver, count: CountResolver}}
]}
];
@NgModule({
@@ -28,12 +40,16 @@ const routes: any[] = [
RouterModule.forRoot(routes),
TranslateModule.forChild()
],
declarations: [ AppComponent, HomeComponent, MenuComponent, DonorsComponent ],
declarations: [ AppComponent, HomeComponent, MenuComponent, DonorsComponent, WithTransferStateComponent, WithoutTransferStateComponent, TransferStateComponent ],
exports: [ AppComponent ],
providers: [
WindowRef,
SnackBarService,
NotificationService
NotificationService,
ExampleApiService,
PingWithTransferStateResolver,
PingWithoutTransferStateResolver,
CountResolver
]
})
export class AppModule {
@@ -1,5 +1,5 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@@ -24,7 +24,8 @@ export function HttpLoaderFactory(http: HttpClient) {
HttpClientModule,
TranslateModule.forRoot({
loader: {provide: TranslateLoader, useFactory: HttpLoaderFactory, deps: [HttpClient]}
})
}),
BrowserTransferStateModule
]
})
export class BrowserAppModule {}
@@ -24,6 +24,8 @@ <h3>Serverless support</h3>
<li>Azure Functions</li>
<li>Webtasks</li>
</ul>
<h3>TransferState</h3>
<p>Decrease number of requests done by back-end and front-end. Read more on the <a routerLink="/transferState">TransferState page</a>.</p>
<h3>Donation</h3>
<p>This project is my 'after-hours' work. I am devoting my private time and resources for its <strong><i>continuous</i></strong> evolution. If you like it, I would appreciate if you donor it!</p>
<p>List of donors can be found on the <a routerLink="donors">donors page</a></p>
@@ -18,6 +18,7 @@ export class MenuComponent implements OnInit {
{link: '/', icon: 'home', text: 'Home'},
{link: '/lazy', icon: 'free_breakfast', text: 'Lazy module'},
{link: '/external', icon: 'call_merge', text: 'External module'},
{link: '/transferState', icon: 'call_merge', text: 'TransferState (API calls)'},
{link: 'https://github.com/maciejtreder/angular-universal-pwa', icon: 'code', text: 'Fork on github'},
];
@Input('contextual')
@@ -1,5 +1,5 @@
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { BrowserModule } from '@angular/platform-browser';
@@ -33,7 +33,8 @@ export function universalLoader(): TranslateLoader {
ServiceWorkerModuleMock,
TranslateModule.forRoot({
loader: {provide: TranslateLoader, useFactory: universalLoader}
})
}),
ServerTransferStateModule
]
})
export class ServerAppModule {}
@@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class ExampleApiService {
private host: string = 'https://www.angular-universal-pwa.maciejtreder.com';
constructor(private http: HttpClient) {}
public ping(): Observable<string> {
return this.http.get(this.host + '/ping', {responseType: 'text'});
}
public count(): Observable<string> {
return this.http.get(this.host + '/count', {responseType: 'text'});
}
}
@@ -0,0 +1,13 @@
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Injectable } from '@angular/core';
import { ExampleApiService } from '../exampleApi.service';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class CountResolver implements Resolve<string> {
constructor(private api: ExampleApiService) {}
public resolve(snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> {
return this.api.count();
}
}
@@ -0,0 +1,28 @@
import 'rxjs/add/operator/do';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { ExampleApiService } from '../exampleApi.service';
import { Observable } from 'rxjs/Observable';
import { makeStateKey, StateKey, TransferState } from '@angular/platform-browser';
import { isPlatformServer } from '@angular/common';
@Injectable()
export class PingWithTransferStateResolver implements Resolve<string> {
private key: StateKey<string> = makeStateKey<string>('response');
constructor(private api: ExampleApiService, private transferState: TransferState, @Inject(PLATFORM_ID) private platformId: any) {}
public resolve(snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> {
if (!this.transferState.hasKey(this.key)) {
return this.api.ping().do((response: string) => {
if (isPlatformServer(this.platformId)) {
this.transferState.set(this.key, response);
}
});
} else {
const value: string = this.transferState.get(this.key, 'error');
this.transferState.remove(this.key);
return Observable.of(value);
}
}
}
@@ -0,0 +1,13 @@
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Injectable } from '@angular/core';
import { ExampleApiService } from '../exampleApi.service';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class PingWithoutTransferStateResolver implements Resolve<string> {
constructor(private api: ExampleApiService) {}
public resolve(snapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> {
return this.api.ping();
}
}
@@ -0,0 +1,13 @@
div {
padding: 10px 5px;
border: 1px dashed gray;
}
div div {
border: 0;
justify-content: center;
display: flex;
}
div a {
margin: 0 10px;
}
@@ -0,0 +1,35 @@
<h1>TransferState</h1>
<p>Most of server-side rendered sites have one big problem. Back-end is requested twice in case of initial load. Why? This is how user action flow looks like:</p>
<ol>
<li>Request page; ie: https://www.angular-universal-pwa.maciejtreder.com/transferState</li>
<li>Request reach back-end:
<ol>
<li>Back-end launches Angular and starts rendering the view</li>
<li><strong>Back-end performs request to the API to fetch data for component (https://www.angular-universal-pwa.maciejtreder.com/ping)</strong></li>
</ol>
</li>
<li>HTML is send back to the user</li>
<li>User browser renders view from the given html and css
<ol>
<li>User browser launches JavaScript -> Angular comes into the game</li>
<li><strong>Browser performs request to the API to fetch data for component (https://www.angular-universal-pwa.maciejtreder.com/ping)</strong></li>
</ol>
</li>
</ol>
<p>
To avoid such repetitive requests, Angular 5 comes with <strong>TransferState</strong>.
This mechanism give front-end and back-end a possiblity to "communicate".
In other words, front-end retrieves from the back-end collection of key -> value sets with the data which back-end already retrieved from external services.
</p>
<div>
<p>Response from /ping: <strong>{{responsePing}}</strong></p>
<p>Response from /requestCount: <strong>{{responseCount}}</strong></p>
<i>To see <strong>factual</strong> number of requests, you need to get rid of PWA functionality - hard refresh this page (ctrl + shift + f5) or disable Service Worker in developer tools.</i>
<div>
<a mat-raised-button routerLink="/transferState/with">Try with TransferState</a>
<a mat-raised-button routerLink="/transferState/without">Try without TransferState</a>
</div>
</div>
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
templateUrl: './transferState.component.html',
styleUrls: ['./transferState.component.css']
})
export class TransferStateComponent {
public responsePing: string;
public responseCount: number;
}
@@ -0,0 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
templateUrl: './transferState.component.html',
styleUrls: ['./transferState.component.css']
})
export class WithTransferStateComponent implements OnInit {
public responsePing: string;
public responseCount: number;
constructor(private route: ActivatedRoute) {}
public ngOnInit() {
this.responsePing = this.route.snapshot.data.ping;
this.responseCount = this.route.snapshot.data.count;
}
}
@@ -0,0 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
templateUrl: './transferState.component.html',
styleUrls: ['./transferState.component.css']
})
export class WithoutTransferStateComponent implements OnInit {
public responsePing: string;
public responseCount: number;
constructor(private route: ActivatedRoute) {}
public ngOnInit() {
this.responsePing = this.route.snapshot.data.ping;
this.responseCount = this.route.snapshot.data.count;
}
}
View
@@ -33,6 +33,16 @@ app.set('views', 'dist');
app.use('/', express.static('dist', { index: false }));
let pingCount = 0;
app.get('/ping', (req, res) => {
pingCount++;
res.send('pong');
});
app.get('/count', (req, res) => {
res.send(pingCount.toString());
});
app.get('/**', (req, res) => {
if (req.headers.host.indexOf('angular-universal-pwa.maciejtreder.com') > -1 && req.headers.host !== 'www.angular-universal-pwa.maciejtreder.com') {
res.writeHead (301, {Location: 'https://www.angular-universal-pwa.maciejtreder.com'});
ProTip! Use n and p to navigate between commits in a pull request.