Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Commit c83605b

Browse files
Add aspnet-angular NPM package containing HttpWithStateTransfer utility
1 parent fc12d72 commit c83605b

File tree

7 files changed

+161
-0
lines changed

7 files changed

+161
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/node_modules/
2+
**/*.js
3+
**/*.d.ts
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
!/*.js
2+
!/*.d.ts
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Copyright (c) .NET Foundation. All rights reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4+
these files except in compliance with the License. You may obtain a copy of the
5+
License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software distributed
10+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
12+
specific language governing permissions and limitations under the License.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "aspnet-angular",
3+
"version": "0.1.0",
4+
"description": "Helpers for using Angular in ASP.NET Core projects",
5+
"main": "index.js",
6+
"scripts": {
7+
"prepublish": "rimraf *.d.ts && tsc && echo 'Finished building NPM package \"aspnet-angular\"'",
8+
"test": "echo \"Error: no test specified\" && exit 1"
9+
},
10+
"repository": {
11+
"type": "git",
12+
"url": "https://github.com/aspnet/JavaScriptServices.git"
13+
},
14+
"author": "Microsoft",
15+
"license": "Apache-2.0",
16+
"bugs": {
17+
"url": "https://github.com/aspnet/JavaScriptServices/issues"
18+
},
19+
"devDependencies": {
20+
"@angular/common": "^4.3.2",
21+
"@angular/core": "^4.3.2",
22+
"@angular/http": "^4.3.2",
23+
"@angular/platform-browser": "^4.3.2",
24+
"rimraf": "^2.6.1",
25+
"typescript": "^2.4.2",
26+
"rxjs": "^5.4.2",
27+
"zone.js": "^0.8.16"
28+
},
29+
"peerDependencies": {
30+
"@angular/core": "^4.2.5 || ^5.0.0-beta"
31+
}
32+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Provider, NgModule, Inject } from '@angular/core';
2+
import { Headers, Http, ResponseOptions, RequestOptionsArgs, Response } from '@angular/http';
3+
import { Observable } from 'rxjs/Observable';
4+
import 'rxjs/add/observable/of';
5+
import 'rxjs/add/operator/map';
6+
const globalSerializedStateKey = 'HTTP_STATE_TRANSFER';
7+
const backingStoreDIToken = 'HTTP_STATE_BACKING_STORE';
8+
9+
export interface CacheOptions {
10+
permanent: boolean;
11+
}
12+
13+
export interface CachedHttpResponse {
14+
headers: { [name: string]: any } | null;
15+
status: number;
16+
statusText: string | null;
17+
text: string;
18+
url: string;
19+
}
20+
21+
export type BackingStore = { [key: string]: CachedHttpResponse };
22+
23+
export class HttpWithStateTransfer {
24+
private backingStore: BackingStore;
25+
private http: Http;
26+
27+
constructor(@Inject(Http) http: Http, @Inject(backingStoreDIToken) backingStore: BackingStore) {
28+
this.http = http;
29+
this.backingStore = backingStore;
30+
}
31+
32+
public stateForTransfer(): any {
33+
return { [globalSerializedStateKey]: this.backingStore };
34+
}
35+
36+
public get(url: string, options?: CacheOptions, requestOptions?: RequestOptionsArgs): Observable<Response> {
37+
return this.getCachedResponse(/* cacheKey */ url, () => this.http.get(url, requestOptions), options);
38+
}
39+
40+
private getCachedResponse(cacheKey: string, provider: () => Observable<Response>, options?: CacheOptions): Observable<Response> {
41+
// By default, the cache is only used for the *first* client-side read. So, we're only performing
42+
// a one-time transfer of server-side response to the client. If you want to keep and reuse cached
43+
// responses continually during server-side and client-side execution, set 'permanent' to 'true.
44+
const isClient = typeof window !== 'undefined';
45+
const isPermanent = options && options.permanent;
46+
47+
const allowReadFromCache = isClient || isPermanent;
48+
if (allowReadFromCache && this.backingStore.hasOwnProperty(cacheKey)) {
49+
const cachedValue = this.backingStore[cacheKey];
50+
if (!isPermanent) {
51+
delete this.backingStore[cacheKey];
52+
}
53+
return Observable.of(new Response(new ResponseOptions({
54+
body: cachedValue.text,
55+
headers: new Headers(cachedValue.headers),
56+
status: cachedValue.status,
57+
url: cachedValue.url
58+
})));
59+
}
60+
61+
return provider()
62+
.map(response => {
63+
const allowWriteToCache = !isClient || isPermanent;
64+
if (allowWriteToCache) {
65+
this.backingStore[cacheKey] = {
66+
headers: response.headers ? response.headers.toJSON() : null,
67+
status: response.status,
68+
statusText: response.statusText,
69+
text: response.text(),
70+
url: response.url
71+
};
72+
}
73+
74+
return response;
75+
});
76+
}
77+
}
78+
79+
export function defaultBackingStoreFactory() {
80+
const transferredData = typeof window !== 'undefined' ? (window as any)[globalSerializedStateKey] : null;
81+
return transferredData || {};
82+
}
83+
84+
@NgModule({
85+
providers: [
86+
// The backing store is a separate DI service so you could override exactly how it gets
87+
// transferred from server to client
88+
{ provide: backingStoreDIToken, useFactory: defaultBackingStoreFactory },
89+
90+
{ provide: HttpWithStateTransfer, useClass: HttpWithStateTransfer },
91+
]
92+
})
93+
export class HttpWithStateTransferModule {
94+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './HttpWithStateTransfer';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"compilerOptions": {
3+
"experimentalDecorators": true,
4+
"moduleResolution": "node",
5+
"module": "commonjs",
6+
"target": "es5",
7+
"declaration": true,
8+
"outDir": ".",
9+
"lib": ["es2015", "dom"]
10+
},
11+
"files": [
12+
"src/index.ts"
13+
],
14+
"exclude": [
15+
"node_modules"
16+
]
17+
}

0 commit comments

Comments
 (0)