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

feature: for v.1.0.6 enable parseUrl override and show how to use overrides #49

Merged
merged 1 commit into from
Oct 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ src/*.d.ts
src/*.js
src/*.js.map
src/*.metadata.json
examples/*.d.ts
examples/*.js
examples/*.js.map
examples/*.metadata.json
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
aot
examples
in-memory-web-api
src
gulpfile.js
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
# "angular-in-memory-web-api" versions
<a name="0.1.6"></a>
## 0.1.6 (2016-10-09)
* Do not add delay to observable if delay value === 0 (issue #47)
* Can override `parseUrl` method in your db service class (issue #46, #35)
* README.md explains `parseUrl` override.
* Exports functions helpful for custom HTTP Method Interceptors
* `createErrorResponse`
* `createObservableResponse`
* `setStatusText`
* Added `examples/hero-data.service.ts` to show overrides (issue #44)

<a name="0.1.5"></a>
## 0.1.5 (2016-10-03)
* project.json license changed again to match angular.io package.json
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ If an existing, running remote server should handle requests for collections
that are not in the in-memory database, set `Config.passThruUnknownUrl: true`.
This service will forward unrecognized requests via a base version of the Angular XHRBackend.

## _parseUrl_ override

The `parseUrl` method breaks down the request URL into a `ParsedUrl` object.
`ParsedUrl` is a public interface whose properties guide the in-memory web api
as it processes the request.

Request URLs for your api may not match the api imagined by the default `parseUrl` and may even cause it to throw an error.
You can override the default by implementing a `parseUrl` method in your `InMemoryDbService`.
Such a method must take the incoming request URL string and return a `ParsedUrl` object.

## HTTP method interceptors

If you make requests this service can't handle but still want an in-memory database to hold values,
Expand All @@ -123,6 +133,25 @@ db: Object; // the current in-mem database collections
config: InMemoryBackendConfigArgs; // the current config
passThruBackend: ConnectionBackend; // pass through backend, if it exists
```
## Examples

The file `examples/hero-data.service.ts` is an example of a Hero-oriented `InMemoryDbService`,
derived from the [HTTP Client](https://angular.io/docs/ts/latest/guide/server-communication.html)
sample in the Angular documentation.

Add the following line to `AppModule.imports`
```
InMemoryWebApiModule.forRoot(HeroDataService)
```

That file also has a `HeroDataOverrideService` derived class that demonstrates overriding
the `parseUrl` method and an HTTP GET interceptor.

Add the following line to `AppModule.imports` to see it in action:
```
InMemoryWebApiModule.forRoot(HeroDataOverrideService)
```

# To Do
* add tests (shameful omission!)

Expand Down
131 changes: 131 additions & 0 deletions examples/hero-data.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* This is an example of a Hero-oriented InMemoryDbService.
*
* Add the following line to `AppModule.imports`
* InMemoryWebApiModule.forRoot(HeroDataService) // or HeroDataOverrideService
*/
import {
InMemoryDbService,
createErrorResponse, createObservableResponse, HttpMethodInterceptorArgs, ParsedUrl, STATUS
} from 'angular-in-memory-web-api';

import { ResponseOptions, URLSearchParams } from '@angular/http';

// For AoT compile
/* tslint:disable:no-unused-variable */
import { Observable } from 'rxjs/Observable';
import { Response } from '@angular/http';
/* tslint:enable:no-unused-variable */

export class HeroDataService implements InMemoryDbService {
createDb() {
let heroes = [
{ id: '1', name: 'Windstorm' },
{ id: '2', name: 'Bombasto' },
{ id: '3', name: 'Magneta' },
{ id: '4', name: 'Tornado' }
];
return {heroes};
}
}

/**
* This is an example of a Hero-oriented InMemoryDbService with method overrides.
*/
export class HeroDataOverrideService extends HeroDataService {
// parseUrl override
parseUrl(url: string): ParsedUrl {
try {
const loc = this.getLocation(url);
let drop = 0;
let urlRoot = '';
if (loc.host !== undefined) {
// url for a server on a different host!
// assume it's collection is actually here too.
drop = 1; // the leading slash
urlRoot = loc.protocol + '//' + loc.host + '/';
}
const path = loc.pathname.substring(drop);
let [base, collectionName, id] = path.split('/');
const resourceUrl = urlRoot + base + '/' + collectionName + '/';
[collectionName] = collectionName.split('.'); // ignore anything after the '.', e.g., '.json'
const query = loc.search && new URLSearchParams(loc.search.substr(1));

const result = { base, collectionName, id, query, resourceUrl };
console.log('override parseUrl:');
console.log(result);
return result;
} catch (err) {
const msg = `unable to parse url '${url}'; original error: ${err.message}`;
throw new Error(msg);
}
}

// HTTP GET interceptor
protected get(interceptorArgs: HttpMethodInterceptorArgs) {

console.log('HTTP GET override');
let resp: ResponseOptions;

const {id, query, collection, collectionName, headers} = interceptorArgs.requestInfo;
let data = collection;

if (id) {
data = this.findById(collection, id);
} else if (query) {
data = this.applyQuery(collection, query);
}

if (data) {
resp = new ResponseOptions({
body: { data: this.clone(data) },
headers: headers,
status: STATUS.OK
});
} else {
resp = createErrorResponse(STATUS.NOT_FOUND,
`'${collectionName}' with id='${id}' not found`);
}

return createObservableResponse(resp);
}

/////////// private ///////////////
private applyQuery(collection: any[], query: URLSearchParams) {
// extract filtering conditions - {propertyName, RegExps) - from query/search parameters
const conditions: {name: string, rx: RegExp}[] = [];
const caseSensitive = 'i';
query.paramsMap.forEach((value: string[], name: string) => {
value.forEach(v => conditions.push({name, rx: new RegExp(decodeURI(v), caseSensitive)}));
});

const len = conditions.length;
if (!len) { return collection; }

// AND the RegExp conditions
return collection.filter(row => {
let ok = true;
let i = len;
while (ok && i) {
i -= 1;
const cond = conditions[i];
ok = cond.rx.test(row[cond.name]);
}
return ok;
});
}

private clone(data: any) {
return JSON.parse(JSON.stringify(data));
}

private findById(collection: any[], id: number | string) {
return collection.find((item: any) => item.id === id);
}

private getLocation(href: string) {
const l = document.createElement('a');
l.href = href;
return l;
};
}
59 changes: 41 additions & 18 deletions in-memory-backend.service.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@ import { Injector } from '@angular/core';
import { Connection, ConnectionBackend, Headers, Request, Response, ResponseOptions, URLSearchParams } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/delay';
/**
* Create an error Response from an HTTP status code and error message
*/
export declare function createErrorResponse(status: number, message: string): ResponseOptions;
/**
* Create an Observable response from response options:
*/
export declare function createObservableResponse(resOptions: ResponseOptions): Observable<Response>;
/**
* Interface for object passed to an HTTP method override method
*/
export interface HttpMethodInterceptorArgs {
requestInfo: RequestInfo;
db: Object;
config: InMemoryBackendConfigArgs;
passThruBackend: ConnectionBackend;
}
/**
* Interface for a class that creates an in-memory database
*
Expand Down Expand Up @@ -68,6 +85,10 @@ export interface InMemoryBackendConfigArgs {
export declare class InMemoryBackendConfig implements InMemoryBackendConfigArgs {
constructor(config?: InMemoryBackendConfigArgs);
}
/**
* Returns true if the the Http Status Code is 200-299 (success)
*/
export declare function isSuccess(status: number): boolean;
/**
* Interface for object w/ info about the current request url
* extracted from an Http Request
Expand All @@ -83,15 +104,26 @@ export interface RequestInfo {
resourceUrl: string;
}
/**
* Interface for object passed to an HTTP method override method
*/
export interface HttpMethodInterceptorArgs {
requestInfo: RequestInfo;
db: Object;
config: InMemoryBackendConfigArgs;
passThruBackend: ConnectionBackend;
* Set the status text in a response:
*/
export declare function setStatusText(options: ResponseOptions): ResponseOptions;
/**
*
* Interface for the result of the parseUrl method:
* Given URL "http://localhost:8080/api/characters/42?foo=1 the default implementation returns
* base: 'api'
* collectionName: 'characters'
* id: '42'
* query: new URLSearchParams('foo=1')
* resourceUrl: 'api/characters/42?foo=1'
*/
export interface ParsedUrl {
base: string;
collectionName: string;
id: string;
query: URLSearchParams;
resourceUrl: string;
}
export declare const isSuccess: (status: number) => boolean;
/**
* Simulate the behavior of a RESTy web api
* backed by the simple in-memory data store provided by the injected InMemoryDataService service.
Expand Down Expand Up @@ -172,8 +204,6 @@ export declare class InMemoryBackendService {
* http.post('commands/config', '{"delay":1000}');
*/
protected commands(reqInfo: RequestInfo): Observable<Response>;
protected createErrorResponse(status: number, message: string): ResponseOptions;
protected createObservableResponse(resOptions: ResponseOptions): Observable<Response>;
protected delete({id, collection, collectionName, headers}: RequestInfo): ResponseOptions;
protected findById(collection: any[], id: number | string): any;
protected genId(collection: any): any;
Expand All @@ -183,13 +213,7 @@ export declare class InMemoryBackendService {
protected parseId(collection: {
id: any;
}[], id: string): any;
protected parseUrl(url: string): {
base: string;
id: string;
collectionName: string;
resourceUrl: string;
query: URLSearchParams;
};
protected parseUrl(url: string): ParsedUrl;
protected post({collection, headers, id, req, resourceUrl}: RequestInfo): ResponseOptions;
protected put({id, collection, collectionName, headers, req}: RequestInfo): ResponseOptions;
protected removeById(collection: any[], id: number): boolean;
Expand All @@ -198,5 +222,4 @@ export declare class InMemoryBackendService {
*/
protected resetDb(): void;
protected setPassThruBackend(): void;
protected setStatusText(options: ResponseOptions): ResponseOptions;
}
Loading