Skip to content

Commit 2156810

Browse files
committed
feat(http): add basic http service
This implementation only works in JavaScript, while the Observable transpilation story gets worked out. Right now, the service just makes a simple request, and returns an Observable of Response. Additional functionality will be captured in separate issues. Fixes #2028
1 parent 363b9ba commit 2156810

35 files changed

+1054
-2
lines changed

gulpfile.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,16 @@ gulp.task('!build/change_detect.dart', function(done) {
845845
proc.stdout.pipe(dartStream);
846846
});
847847

848+
// ------------
849+
// additional tasks for building examples
850+
gulp.task('build.http.example', function() {
851+
//Copy over people.json used in http example
852+
return gulp.src('modules/examples/src/http/people.json')
853+
.pipe(gulp.dest(CONFIG.dest.js.prod.es5 + '/examples/src/http/'))
854+
.pipe(gulp.dest(CONFIG.dest.js.dev.es5 + '/examples/src/http/'))
855+
.pipe(gulp.dest(CONFIG.dest.js.dart2js + '/examples/src/http/'));
856+
});
857+
848858
// ------------
849859
// angular material testing rules
850860
gulp.task('build.css.material', function() {

modules/angular2/http.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {bind, Binding} from 'angular2/di';
2+
import {Http, HttpFactory} from './src/http/http';
3+
import {XHRBackend} from 'angular2/src/http/backends/xhr_backend';
4+
import {BrowserXHR} from 'angular2/src/http/backends/browser_xhr';
5+
6+
export {Http};
7+
export var httpInjectables: List<any> = [
8+
XHRBackend,
9+
bind(BrowserXHR).toValue(BrowserXHR),
10+
bind(Http).toFactory(HttpFactory, [XHRBackend])
11+
];

modules/angular2/src/facade/async.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,5 @@ export class EventEmitter extends Observable {
107107

108108
throw(error) { this._subject.onError(error); }
109109

110-
return (value) { this._subject.onCompleted(); }
110+
return (value?) { this._subject.onCompleted(); }
111111
}

modules/angular2/src/facade/collection.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ export class ListWrapper {
238238
l.sort();
239239
}
240240
}
241+
static toString<T>(l: List<T>): string { return l.toString(); }
241242
}
242243

243244
export function isListLikeIterable(obj): boolean {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
library angular2.src.http.backends.browser_xhr;
2+
3+
/// import 'dart:html' show HttpRequest;
4+
/// import 'package:angular2/di.dart';
5+
6+
/// @Injectable()
7+
/// class BrowserXHR {
8+
/// factory BrowserXHR() => new HttpRequest();
9+
/// }
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
declare var window;
2+
3+
import {Injectable} from 'angular2/di';
4+
5+
// Make sure not to evaluate this in a non-browser environment!
6+
@Injectable()
7+
export class BrowserXHR {
8+
constructor() { return <any>(new window.XMLHttpRequest()); }
9+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import {Injectable} from 'angular2/di';
2+
import {Request} from 'angular2/src/http/static_request';
3+
import {Response} from 'angular2/src/http/static_response';
4+
import {ReadyStates} from 'angular2/src/http/enums';
5+
import * as Rx from 'rx';
6+
7+
/**
8+
* Connection represents a request and response for an underlying transport, like XHR or mock.
9+
* The mock implementation contains helper methods to respond to connections within tests.
10+
* API subject to change and expand.
11+
**/
12+
export class Connection {
13+
/**
14+
* Observer to call on download progress, if provided in config.
15+
**/
16+
downloadObserver: Rx.Observer<Response>;
17+
18+
/**
19+
* TODO
20+
* Name `readyState` should change to be more generic, and states could be made to be more
21+
* descriptive than XHR states.
22+
**/
23+
24+
readyState: ReadyStates;
25+
request: Request;
26+
response: Rx.Subject<Response>;
27+
28+
constructor(req: Request) {
29+
// State
30+
if (Rx.hasOwnProperty('default')) {
31+
this.response = new ((<any>Rx).default.Rx.Subject)();
32+
} else {
33+
this.response = new Rx.Subject<Response>();
34+
}
35+
36+
this.readyState = ReadyStates.OPEN;
37+
this.request = req;
38+
this.dispose = this.dispose.bind(this);
39+
}
40+
41+
dispose() {
42+
if (this.readyState !== ReadyStates.DONE) {
43+
this.readyState = ReadyStates.CANCELLED;
44+
}
45+
}
46+
47+
/**
48+
* Called after a connection has been established.
49+
**/
50+
mockRespond(res: Response) {
51+
if (this.readyState >= ReadyStates.DONE) {
52+
throw new Error('Connection has already been resolved');
53+
}
54+
this.readyState = ReadyStates.DONE;
55+
this.response.onNext(res);
56+
this.response.onCompleted();
57+
}
58+
59+
mockDownload(res: Response) {
60+
this.downloadObserver.onNext(res);
61+
if (res.bytesLoaded === res.totalBytes) {
62+
this.downloadObserver.onCompleted();
63+
}
64+
}
65+
66+
mockError(err?) {
67+
// Matches XHR semantics
68+
this.readyState = ReadyStates.DONE;
69+
this.response.onError(err);
70+
this.response.onCompleted();
71+
}
72+
}
73+
74+
@Injectable()
75+
export class MockBackend {
76+
connections: Rx.Subject<Connection>;
77+
connectionsArray: Array<Connection>;
78+
pendingConnections: Rx.Observable<Connection>;
79+
constructor() {
80+
this.connectionsArray = [];
81+
if (Rx.hasOwnProperty('default')) {
82+
this.connections = new (<any>Rx).default.Rx.Subject();
83+
} else {
84+
this.connections = new Rx.Subject<Connection>();
85+
}
86+
this.connections.subscribe(connection => this.connectionsArray.push(connection));
87+
this.pendingConnections = this.connections.filter((c) => c.readyState < ReadyStates.DONE);
88+
}
89+
90+
verifyNoPendingRequests() {
91+
let pending = 0;
92+
this.pendingConnections.subscribe((c) => pending++);
93+
if (pending > 0) throw new Error(`${pending} pending connections to be resolved`);
94+
}
95+
96+
resolveAllConnections() { this.connections.subscribe((c) => c.readyState = 4); }
97+
98+
createConnection(req: Request) {
99+
if (!req || !(req instanceof Request)) {
100+
throw new Error(`createConnection requires an instance of Request, got ${req}`);
101+
}
102+
let connection = new Connection(req);
103+
this.connections.onNext(connection);
104+
return connection;
105+
}
106+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {ConnectionBackend, Connection} from '../interfaces';
2+
import {ReadyStates, RequestMethods} from '../enums';
3+
import {Request} from '../static_request';
4+
import {Response} from '../static_response';
5+
import {Inject} from 'angular2/di';
6+
import {Injectable} from 'angular2/di';
7+
import {BrowserXHR} from './browser_xhr';
8+
import * as Rx from 'rx';
9+
10+
export class XHRConnection implements Connection {
11+
request: Request;
12+
response: Rx.Subject<Response>;
13+
readyState: ReadyStates;
14+
private _xhr;
15+
constructor(req: Request, NativeConstruct: any) {
16+
this.request = req;
17+
if (Rx.hasOwnProperty('default')) {
18+
this.response = new (<any>Rx).default.Rx.Subject();
19+
} else {
20+
this.response = new Rx.Subject<Response>();
21+
}
22+
this._xhr = new NativeConstruct();
23+
this._xhr.open(RequestMethods[req.method], req.url);
24+
this._xhr.addEventListener(
25+
'load',
26+
() => {this.response.onNext(new Response(this._xhr.response || this._xhr.responseText))});
27+
// TODO(jeffbcross): make this more dynamic based on body type
28+
this._xhr.send(this.request.text());
29+
}
30+
31+
dispose(): void { this._xhr.abort(); }
32+
}
33+
34+
@Injectable()
35+
export class XHRBackend implements ConnectionBackend {
36+
constructor(private _NativeConstruct: BrowserXHR) {}
37+
createConnection(request: Request): XHRConnection {
38+
return new XHRConnection(request, this._NativeConstruct);
39+
}
40+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {CONST_EXPR, CONST} from 'angular2/src/facade/lang';
2+
import {Headers} from './headers';
3+
import {URLSearchParams} from './url_search_params';
4+
import {RequestModesOpts, RequestMethods, RequestCacheOpts, RequestCredentialsOpts} from './enums';
5+
import {RequestOptions} from './interfaces';
6+
import {Injectable} from 'angular2/di';
7+
8+
@Injectable()
9+
export class BaseRequestOptions implements RequestOptions {
10+
method: RequestMethods;
11+
headers: Headers;
12+
body: URLSearchParams | FormData | string;
13+
mode: RequestModesOpts;
14+
credentials: RequestCredentialsOpts;
15+
cache: RequestCacheOpts;
16+
17+
constructor() {
18+
this.method = RequestMethods.GET;
19+
this.mode = RequestModesOpts.Cors;
20+
}
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {Headers} from './headers';
2+
import {ResponseTypes} from './enums';
3+
import {ResponseOptions} from './interfaces';
4+
5+
export class BaseResponseOptions implements ResponseOptions {
6+
status: number;
7+
headers: Headers | Object;
8+
statusText: string;
9+
type: ResponseTypes;
10+
url: string;
11+
12+
constructor({status = 200, statusText = 'Ok', type = ResponseTypes.Default,
13+
headers = new Headers(), url = ''}: ResponseOptions = {}) {
14+
this.status = status;
15+
this.statusText = statusText;
16+
this.type = type;
17+
this.headers = headers;
18+
this.url = url;
19+
}
20+
}
21+
;
22+
23+
export var baseResponseOptions = Object.freeze(new BaseResponseOptions());

0 commit comments

Comments
 (0)