Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

Commit c3d8433

Browse files
authored
feature: for v.1.0.6 enable parseUrl override and show how to use overrides (#49)
1 parent 1bc89a1 commit c3d8433

11 files changed

+396
-138
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ src/*.d.ts
66
src/*.js
77
src/*.js.map
88
src/*.metadata.json
9+
examples/*.d.ts
10+
examples/*.js
11+
examples/*.js.map
12+
examples/*.metadata.json

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
aot
2+
examples
23
in-memory-web-api
34
src
45
gulpfile.js

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
# "angular-in-memory-web-api" versions
2+
<a name="0.1.6"></a>
3+
## 0.1.6 (2016-10-09)
4+
* Do not add delay to observable if delay value === 0 (issue #47)
5+
* Can override `parseUrl` method in your db service class (issue #46, #35)
6+
* README.md explains `parseUrl` override.
7+
* Exports functions helpful for custom HTTP Method Interceptors
8+
* `createErrorResponse`
9+
* `createObservableResponse`
10+
* `setStatusText`
11+
* Added `examples/hero-data.service.ts` to show overrides (issue #44)
12+
213
<a name="0.1.5"></a>
314
## 0.1.5 (2016-10-03)
415
* project.json license changed again to match angular.io package.json

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,16 @@ If an existing, running remote server should handle requests for collections
104104
that are not in the in-memory database, set `Config.passThruUnknownUrl: true`.
105105
This service will forward unrecognized requests via a base version of the Angular XHRBackend.
106106

107+
## _parseUrl_ override
108+
109+
The `parseUrl` method breaks down the request URL into a `ParsedUrl` object.
110+
`ParsedUrl` is a public interface whose properties guide the in-memory web api
111+
as it processes the request.
112+
113+
Request URLs for your api may not match the api imagined by the default `parseUrl` and may even cause it to throw an error.
114+
You can override the default by implementing a `parseUrl` method in your `InMemoryDbService`.
115+
Such a method must take the incoming request URL string and return a `ParsedUrl` object.
116+
107117
## HTTP method interceptors
108118

109119
If you make requests this service can't handle but still want an in-memory database to hold values,
@@ -123,6 +133,25 @@ db: Object; // the current in-mem database collections
123133
config: InMemoryBackendConfigArgs; // the current config
124134
passThruBackend: ConnectionBackend; // pass through backend, if it exists
125135
```
136+
## Examples
137+
138+
The file `examples/hero-data.service.ts` is an example of a Hero-oriented `InMemoryDbService`,
139+
derived from the [HTTP Client](https://angular.io/docs/ts/latest/guide/server-communication.html)
140+
sample in the Angular documentation.
141+
142+
Add the following line to `AppModule.imports`
143+
```
144+
InMemoryWebApiModule.forRoot(HeroDataService)
145+
```
146+
147+
That file also has a `HeroDataOverrideService` derived class that demonstrates overriding
148+
the `parseUrl` method and an HTTP GET interceptor.
149+
150+
Add the following line to `AppModule.imports` to see it in action:
151+
```
152+
InMemoryWebApiModule.forRoot(HeroDataOverrideService)
153+
```
154+
126155
# To Do
127156
* add tests (shameful omission!)
128157

examples/hero-data.service.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/**
2+
* This is an example of a Hero-oriented InMemoryDbService.
3+
*
4+
* Add the following line to `AppModule.imports`
5+
* InMemoryWebApiModule.forRoot(HeroDataService) // or HeroDataOverrideService
6+
*/
7+
import {
8+
InMemoryDbService,
9+
createErrorResponse, createObservableResponse, HttpMethodInterceptorArgs, ParsedUrl, STATUS
10+
} from 'angular-in-memory-web-api';
11+
12+
import { ResponseOptions, URLSearchParams } from '@angular/http';
13+
14+
// For AoT compile
15+
/* tslint:disable:no-unused-variable */
16+
import { Observable } from 'rxjs/Observable';
17+
import { Response } from '@angular/http';
18+
/* tslint:enable:no-unused-variable */
19+
20+
export class HeroDataService implements InMemoryDbService {
21+
createDb() {
22+
let heroes = [
23+
{ id: '1', name: 'Windstorm' },
24+
{ id: '2', name: 'Bombasto' },
25+
{ id: '3', name: 'Magneta' },
26+
{ id: '4', name: 'Tornado' }
27+
];
28+
return {heroes};
29+
}
30+
}
31+
32+
/**
33+
* This is an example of a Hero-oriented InMemoryDbService with method overrides.
34+
*/
35+
export class HeroDataOverrideService extends HeroDataService {
36+
// parseUrl override
37+
parseUrl(url: string): ParsedUrl {
38+
try {
39+
const loc = this.getLocation(url);
40+
let drop = 0;
41+
let urlRoot = '';
42+
if (loc.host !== undefined) {
43+
// url for a server on a different host!
44+
// assume it's collection is actually here too.
45+
drop = 1; // the leading slash
46+
urlRoot = loc.protocol + '//' + loc.host + '/';
47+
}
48+
const path = loc.pathname.substring(drop);
49+
let [base, collectionName, id] = path.split('/');
50+
const resourceUrl = urlRoot + base + '/' + collectionName + '/';
51+
[collectionName] = collectionName.split('.'); // ignore anything after the '.', e.g., '.json'
52+
const query = loc.search && new URLSearchParams(loc.search.substr(1));
53+
54+
const result = { base, collectionName, id, query, resourceUrl };
55+
console.log('override parseUrl:');
56+
console.log(result);
57+
return result;
58+
} catch (err) {
59+
const msg = `unable to parse url '${url}'; original error: ${err.message}`;
60+
throw new Error(msg);
61+
}
62+
}
63+
64+
// HTTP GET interceptor
65+
protected get(interceptorArgs: HttpMethodInterceptorArgs) {
66+
67+
console.log('HTTP GET override');
68+
let resp: ResponseOptions;
69+
70+
const {id, query, collection, collectionName, headers} = interceptorArgs.requestInfo;
71+
let data = collection;
72+
73+
if (id) {
74+
data = this.findById(collection, id);
75+
} else if (query) {
76+
data = this.applyQuery(collection, query);
77+
}
78+
79+
if (data) {
80+
resp = new ResponseOptions({
81+
body: { data: this.clone(data) },
82+
headers: headers,
83+
status: STATUS.OK
84+
});
85+
} else {
86+
resp = createErrorResponse(STATUS.NOT_FOUND,
87+
`'${collectionName}' with id='${id}' not found`);
88+
}
89+
90+
return createObservableResponse(resp);
91+
}
92+
93+
/////////// private ///////////////
94+
private applyQuery(collection: any[], query: URLSearchParams) {
95+
// extract filtering conditions - {propertyName, RegExps) - from query/search parameters
96+
const conditions: {name: string, rx: RegExp}[] = [];
97+
const caseSensitive = 'i';
98+
query.paramsMap.forEach((value: string[], name: string) => {
99+
value.forEach(v => conditions.push({name, rx: new RegExp(decodeURI(v), caseSensitive)}));
100+
});
101+
102+
const len = conditions.length;
103+
if (!len) { return collection; }
104+
105+
// AND the RegExp conditions
106+
return collection.filter(row => {
107+
let ok = true;
108+
let i = len;
109+
while (ok && i) {
110+
i -= 1;
111+
const cond = conditions[i];
112+
ok = cond.rx.test(row[cond.name]);
113+
}
114+
return ok;
115+
});
116+
}
117+
118+
private clone(data: any) {
119+
return JSON.parse(JSON.stringify(data));
120+
}
121+
122+
private findById(collection: any[], id: number | string) {
123+
return collection.find((item: any) => item.id === id);
124+
}
125+
126+
private getLocation(href: string) {
127+
const l = document.createElement('a');
128+
l.href = href;
129+
return l;
130+
};
131+
}

in-memory-backend.service.d.ts

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@ import { Injector } from '@angular/core';
22
import { Connection, ConnectionBackend, Headers, Request, Response, ResponseOptions, URLSearchParams } from '@angular/http';
33
import { Observable } from 'rxjs/Observable';
44
import 'rxjs/add/operator/delay';
5+
/**
6+
* Create an error Response from an HTTP status code and error message
7+
*/
8+
export declare function createErrorResponse(status: number, message: string): ResponseOptions;
9+
/**
10+
* Create an Observable response from response options:
11+
*/
12+
export declare function createObservableResponse(resOptions: ResponseOptions): Observable<Response>;
13+
/**
14+
* Interface for object passed to an HTTP method override method
15+
*/
16+
export interface HttpMethodInterceptorArgs {
17+
requestInfo: RequestInfo;
18+
db: Object;
19+
config: InMemoryBackendConfigArgs;
20+
passThruBackend: ConnectionBackend;
21+
}
522
/**
623
* Interface for a class that creates an in-memory database
724
*
@@ -68,6 +85,10 @@ export interface InMemoryBackendConfigArgs {
6885
export declare class InMemoryBackendConfig implements InMemoryBackendConfigArgs {
6986
constructor(config?: InMemoryBackendConfigArgs);
7087
}
88+
/**
89+
* Returns true if the the Http Status Code is 200-299 (success)
90+
*/
91+
export declare function isSuccess(status: number): boolean;
7192
/**
7293
* Interface for object w/ info about the current request url
7394
* extracted from an Http Request
@@ -83,15 +104,26 @@ export interface RequestInfo {
83104
resourceUrl: string;
84105
}
85106
/**
86-
* Interface for object passed to an HTTP method override method
87-
*/
88-
export interface HttpMethodInterceptorArgs {
89-
requestInfo: RequestInfo;
90-
db: Object;
91-
config: InMemoryBackendConfigArgs;
92-
passThruBackend: ConnectionBackend;
107+
* Set the status text in a response:
108+
*/
109+
export declare function setStatusText(options: ResponseOptions): ResponseOptions;
110+
/**
111+
*
112+
* Interface for the result of the parseUrl method:
113+
* Given URL "http://localhost:8080/api/characters/42?foo=1 the default implementation returns
114+
* base: 'api'
115+
* collectionName: 'characters'
116+
* id: '42'
117+
* query: new URLSearchParams('foo=1')
118+
* resourceUrl: 'api/characters/42?foo=1'
119+
*/
120+
export interface ParsedUrl {
121+
base: string;
122+
collectionName: string;
123+
id: string;
124+
query: URLSearchParams;
125+
resourceUrl: string;
93126
}
94-
export declare const isSuccess: (status: number) => boolean;
95127
/**
96128
* Simulate the behavior of a RESTy web api
97129
* backed by the simple in-memory data store provided by the injected InMemoryDataService service.
@@ -172,8 +204,6 @@ export declare class InMemoryBackendService {
172204
* http.post('commands/config', '{"delay":1000}');
173205
*/
174206
protected commands(reqInfo: RequestInfo): Observable<Response>;
175-
protected createErrorResponse(status: number, message: string): ResponseOptions;
176-
protected createObservableResponse(resOptions: ResponseOptions): Observable<Response>;
177207
protected delete({id, collection, collectionName, headers}: RequestInfo): ResponseOptions;
178208
protected findById(collection: any[], id: number | string): any;
179209
protected genId(collection: any): any;
@@ -183,13 +213,7 @@ export declare class InMemoryBackendService {
183213
protected parseId(collection: {
184214
id: any;
185215
}[], id: string): any;
186-
protected parseUrl(url: string): {
187-
base: string;
188-
id: string;
189-
collectionName: string;
190-
resourceUrl: string;
191-
query: URLSearchParams;
192-
};
216+
protected parseUrl(url: string): ParsedUrl;
193217
protected post({collection, headers, id, req, resourceUrl}: RequestInfo): ResponseOptions;
194218
protected put({id, collection, collectionName, headers, req}: RequestInfo): ResponseOptions;
195219
protected removeById(collection: any[], id: number): boolean;
@@ -198,5 +222,4 @@ export declare class InMemoryBackendService {
198222
*/
199223
protected resetDb(): void;
200224
protected setPassThruBackend(): void;
201-
protected setStatusText(options: ResponseOptions): ResponseOptions;
202225
}

0 commit comments

Comments
 (0)