feat(Http): add Http class

Fixes #2530
This commit is contained in:
Jeff Cross 2015-06-13 15:49:05 -07:00
parent 93596dff3f
commit b68e561c0f
8 changed files with 266 additions and 55 deletions

View File

@ -2,10 +2,14 @@ import {bind, Binding} from 'angular2/di';
import {Http, HttpFactory} from './src/http/http';
import {XHRBackend} from 'angular2/src/http/backends/xhr_backend';
import {BrowserXHR} from 'angular2/src/http/backends/browser_xhr';
import {BaseRequestOptions} from 'angular2/src/http/base_request_options';
export {Http};
export var httpInjectables: List<any> = [
bind(BrowserXHR)
.toValue(BrowserXHR),
XHRBackend,
bind(BrowserXHR).toValue(BrowserXHR),
bind(Http).toFactory(HttpFactory, [XHRBackend])
BaseRequestOptions,
bind(HttpFactory).toFactory(HttpFactory, [XHRBackend, BaseRequestOptions]),
Http
];

View File

@ -6,10 +6,10 @@ import {RequestOptions} from './interfaces';
import {Injectable} from 'angular2/di';
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
export class RequestOptionsClass {
export class RequestOptionsClass implements RequestOptions {
method: RequestMethods = RequestMethods.GET;
headers: Headers;
body: URLSearchParams | FormData | string;
body: URLSearchParams | FormData | Blob | string;
mode: RequestModesOpts = RequestModesOpts.Cors;
credentials: RequestCredentialsOpts;
cache: RequestCacheOpts;

View File

@ -5,7 +5,7 @@ export enum RequestCacheOpts {Default, NoStore, Reload, NoCache, ForceCache, Onl
export enum RequestCredentialsOpts {Omit, SameOrigin, Include};
export enum RequestMethods {GET, POST, PUT, DELETE, OPTIONS, HEAD};
export enum RequestMethods {GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH};
export enum ReadyStates {UNSENT, OPEN, HEADERS_RECEIVED, LOADING, DONE, CANCELLED};

View File

@ -6,6 +6,8 @@ import {Request} from './static_request';
import {Response} from './static_response';
import {XHRBackend} from './backends/xhr_backend';
import {BaseRequestOptions} from './base_request_options';
import {RequestMethods} from './enums';
import {URLSearchParams} from './url_search_params';
import * as Rx from 'rx';
/**
@ -43,9 +45,59 @@ import * as Rx from 'rx';
*
**/
function httpRequest(backend: XHRBackend, request: Request) {
return <Rx.Observable<Response>>(Observable.create(observer => {
var connection: Connection = backend.createConnection(request);
var internalSubscription = connection.response.subscribe(observer);
return () => {
internalSubscription.dispose();
connection.dispose();
}
}))
}
// Abstract
@Injectable()
export class Http {
constructor(private backend: XHRBackend, private defaultOptions: BaseRequestOptions) {}
request(url: string, options?: RequestOptions): Rx.Observable<Response> {
return httpRequest(this.backend, new Request(url, this.defaultOptions.merge(options)));
}
get(url: string, options?: RequestOptions) {
return httpRequest(this.backend, new Request(url, this.defaultOptions.merge(options)
.merge({method: RequestMethods.GET})));
}
post(url: string, body: URLSearchParams | FormData | Blob | string, options?: RequestOptions) {
return httpRequest(this.backend,
new Request(url, this.defaultOptions.merge(options)
.merge({body: body, method: RequestMethods.POST})));
}
put(url: string, body: URLSearchParams | FormData | Blob | string, options?: RequestOptions) {
return httpRequest(this.backend,
new Request(url, this.defaultOptions.merge(options)
.merge({body: body, method: RequestMethods.PUT})));
}
delete (url: string, options?: RequestOptions) {
return httpRequest(this.backend, new Request(url, this.defaultOptions.merge(options)
.merge({method: RequestMethods.DELETE})));
}
patch(url: string, body: URLSearchParams | FormData | Blob | string, options?: RequestOptions) {
return httpRequest(this.backend,
new Request(url, this.defaultOptions.merge(options)
.merge({body: body, method: RequestMethods.PATCH})));
}
head(url: string, options?: RequestOptions) {
return httpRequest(this.backend, new Request(url, this.defaultOptions.merge(options)
.merge({method: RequestMethods.HEAD})));
}
}
var Observable;
@ -56,13 +108,6 @@ if (Rx.hasOwnProperty('default')) {
}
export function HttpFactory(backend: XHRBackend, defaultOptions: BaseRequestOptions) {
return function(url: string, options?: RequestOptions) {
return <Rx.Observable<Response>>(Observable.create(observer => {
var connection: Connection = backend.createConnection(new Request(url, options));
var internalSubscription = connection.response.subscribe(observer);
return () => {
internalSubscription.dispose();
connection.dispose();
}
}))
return httpRequest(backend, new Request(url, defaultOptions.merge(options)));
}
}

View File

@ -14,7 +14,7 @@ import {URLSearchParams} from './url_search_params';
export interface RequestOptions {
method?: RequestMethods;
headers?: Headers;
body?: URLSearchParams | FormData | string;
body?: URLSearchParams | FormData | Blob | string;
mode?: RequestModesOpts;
credentials?: RequestCredentialsOpts;
cache?: RequestCacheOpts;

View File

@ -24,8 +24,8 @@ export function main() {
it('should retain previously merged values when merging again', () => {
var options1 = new BaseRequestOptions();
var options2 = options1.merge({method: RequestMethods.DELETE});
var options3 = options2.merge({mode: RequestModesOpts.NoCors}) expect(options3.mode)
.toBe(RequestModesOpts.NoCors);
var options3 = options2.merge({mode: RequestModesOpts.NoCors});
expect(options3.mode).toBe(RequestModesOpts.NoCors);
expect(options3.method).toBe(RequestMethods.DELETE);
});
});

View File

@ -12,11 +12,11 @@ import {
} from 'angular2/test_lib';
import {Http, HttpFactory} from 'angular2/src/http/http';
import {XHRBackend} from 'angular2/src/http/backends/xhr_backend';
import {httpInjectables} from 'angular2/http';
import {Injector, bind} from 'angular2/di';
import {MockBackend} from 'angular2/src/http/backends/mock_backend';
import {Response} from 'angular2/src/http/static_response';
import {ReadyStates} from 'angular2/src/http/enums';
import {RequestMethods} from 'angular2/src/http/enums';
import {BaseRequestOptions} from 'angular2/src/http/base_request_options';
class SpyObserver extends SpyObject {
onNext: Function;
@ -38,20 +38,31 @@ export function main() {
var backend: MockBackend;
var baseResponse;
var sampleObserver;
var httpFactory;
beforeEach(() => {
injector = Injector.resolveAndCreate(
[MockBackend, bind(Http).toFactory(HttpFactory, [MockBackend])]);
injector = Injector.resolveAndCreate([
BaseRequestOptions,
MockBackend,
bind(XHRBackend).toClass(MockBackend),
bind(HttpFactory).toFactory(HttpFactory, [MockBackend, BaseRequestOptions]),
bind(Http).toFactory(
function(backend: XHRBackend, defaultOptions: BaseRequestOptions) {
return new Http(backend, defaultOptions);
},
[MockBackend, BaseRequestOptions])
]);
http = injector.get(Http);
httpFactory = injector.get(HttpFactory);
backend = injector.get(MockBackend);
baseResponse = new Response('base response');
sampleObserver = new SpyObserver();
});
afterEach(() => {/*backend.verifyNoPendingRequests();*/});
afterEach(() => backend.verifyNoPendingRequests());
describe('HttpFactory', () => {
it('should return an Observable', () => {
expect(typeof http(url).subscribe).toBe('function');
expect(typeof httpFactory(url).subscribe).toBe('function');
backend.resolveAllConnections();
});
@ -60,7 +71,7 @@ export function main() {
inject([AsyncTestCompleter], (async) => {
var connection;
backend.connections.subscribe((c) => connection = c);
var subscription = http('http://basic.connection')
var subscription = httpFactory('http://basic.connection')
.subscribe(res => {
expect(res.text()).toBe('base response');
async.done();
@ -73,7 +84,7 @@ export function main() {
inject([AsyncTestCompleter], async => {
var connection;
backend.connections.subscribe((c) => connection = c);
http('http://basic.connection', {method: ReadyStates.UNSENT})
httpFactory('http://basic.connection', {method: RequestMethods.GET})
.subscribe(res => {
expect(res.text()).toBe('base response');
async.done();
@ -86,7 +97,7 @@ export function main() {
inject([AsyncTestCompleter], async => {
var connection;
backend.connections.subscribe((c) => connection = c);
http(url, {method: ReadyStates.UNSENT})
httpFactory(url, {method: RequestMethods.GET})
.subscribe(res => {
expect(res.text()).toBe('base response');
async.done();
@ -94,4 +105,155 @@ export function main() {
connection.mockRespond(baseResponse)
}));
});
describe('Http', () => {
it('should return an Observable', () => {
expect(typeof http.request(url).subscribe).toBe('function');
backend.resolveAllConnections();
});
it('should perform a get request for given url if only passed a string',
inject([AsyncTestCompleter], (async) => {
var connection;
backend.connections.subscribe((c) => connection = c);
var subscription = http.request('http://basic.connection')
.subscribe(res => {
expect(res.text()).toBe('base response');
async.done();
});
connection.mockRespond(baseResponse)
}));
it('should perform a get request for given url if passed a ConnectionConfig instance',
inject([AsyncTestCompleter], async => {
var connection;
backend.connections.subscribe((c) => connection = c);
http.request('http://basic.connection', {method: RequestMethods.GET})
.subscribe(res => {
expect(res.text()).toBe('base response');
async.done();
});
connection.mockRespond(baseResponse);
}));
it('should perform a get request for given url if passed a dictionary',
inject([AsyncTestCompleter], async => {
var connection;
backend.connections.subscribe((c) => connection = c);
http.request(url, {method: RequestMethods.GET})
.subscribe(res => {
expect(res.text()).toBe('base response');
async.done();
});
connection.mockRespond(baseResponse);
}));
describe('.get()', () => {
it('should perform a get request for given url', inject([AsyncTestCompleter], async => {
backend.connections.subscribe((c) => {
expect(c.request.method).toBe(RequestMethods.GET);
backend.resolveAllConnections();
async.done();
});
http.get(url).subscribe(res => {});
}));
});
describe('.post()', () => {
it('should perform a post request for given url', inject([AsyncTestCompleter], async => {
backend.connections.subscribe((c) => {
expect(c.request.method).toBe(RequestMethods.POST);
backend.resolveAllConnections();
async.done();
});
http.post(url).subscribe(res => {});
}));
it('should attach the provided body to the request', inject([AsyncTestCompleter], async => {
var body = 'this is my put body';
backend.connections.subscribe((c) => {
expect(c.request.text()).toBe(body);
backend.resolveAllConnections();
async.done();
});
http.post(url, body).subscribe(res => {});
}));
});
describe('.put()', () => {
it('should perform a put request for given url', inject([AsyncTestCompleter], async => {
backend.connections.subscribe((c) => {
expect(c.request.method).toBe(RequestMethods.PUT);
backend.resolveAllConnections();
async.done();
});
http.put(url).subscribe(res => {});
}));
it('should attach the provided body to the request', inject([AsyncTestCompleter], async => {
var body = 'this is my put body';
backend.connections.subscribe((c) => {
expect(c.request.text()).toBe(body);
backend.resolveAllConnections();
async.done();
});
http.put(url, body).subscribe(res => {});
}));
});
describe('.delete()', () => {
it('should perform a delete request for given url', inject([AsyncTestCompleter], async => {
backend.connections.subscribe((c) => {
expect(c.request.method).toBe(RequestMethods.DELETE);
backend.resolveAllConnections();
async.done();
});
http.delete(url).subscribe(res => {});
}));
});
describe('.patch()', () => {
it('should perform a patch request for given url', inject([AsyncTestCompleter], async => {
backend.connections.subscribe((c) => {
expect(c.request.method).toBe(RequestMethods.PATCH);
backend.resolveAllConnections();
async.done();
});
http.patch(url).subscribe(res => {});
}));
it('should attach the provided body to the request', inject([AsyncTestCompleter], async => {
var body = 'this is my put body';
backend.connections.subscribe((c) => {
expect(c.request.text()).toBe(body);
backend.resolveAllConnections();
async.done();
});
http.patch(url, body).subscribe(res => {});
}));
});
describe('.head()', () => {
it('should perform a head request for given url', inject([AsyncTestCompleter], async => {
backend.connections.subscribe((c) => {
expect(c.request.method).toBe(RequestMethods.HEAD);
backend.resolveAllConnections();
async.done();
});
http.head(url).subscribe(res => {});
}));
});
});
});
}

View File

@ -1,6 +1,6 @@
import {bootstrap, Component, View, NgFor, NgIf, Inject} from 'angular2/angular2';
import {httpInjectables} from 'angular2/http';
import {Http} from 'angular2/src/http/http';
import {HttpFactory} from 'angular2/src/http/http';
import {IHttp} from 'angular2/src/http/interfaces';
import {Response} from 'angular2/src/http/static_response';
import {LocalVariable} from './assign_local_directive';
@ -24,7 +24,7 @@ import {LocalVariable} from './assign_local_directive';
})
export class HttpCmp {
people: Rx.Observable<Object>;
constructor(@Inject(Http) http: IHttp) {
constructor(@Inject(HttpFactory) http: IHttp) {
this.people = http('./people.json').map(res => res.json());
}
}