Skip to content

Commit 01fb06a

Browse files
robwormaldjeffbcross
authored andcommitted
chore(http): Use Observables in Http
- Remove ObservableWrapper/EventEmitter from Http. - Temporarily use complete Rx build w/ all operators.
1 parent c9901c5 commit 01fb06a

File tree

9 files changed

+223
-95
lines changed

9 files changed

+223
-95
lines changed

karma-js.conf.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ module.exports = function(config) {
2424
// Including systemjs because it defines `__eval`, which produces correct stack traces.
2525
'modules/angular2/src/test_lib/shims_for_IE.js',
2626
'node_modules/systemjs/dist/system.src.js',
27-
{pattern: 'node_modules/@reactivex/rxjs/dist/cjs/**', included: false, watched: false, served: true},
27+
{pattern: 'node_modules/@reactivex/rxjs/**', included: false, watched: false, served: true},
2828
'node_modules/reflect-metadata/Reflect.js',
2929
'tools/build/file2modulename.js',
3030
'test-main.js',

modules/angular2/src/http/backends/jsonp_backend.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import {BrowserJsonp} from './browser_jsonp';
88
import {EventEmitter, ObservableWrapper} from 'angular2/src/core/facade/async';
99
import {makeTypeError} from 'angular2/src/core/facade/exceptions';
1010
import {StringWrapper, isPresent} from 'angular2/src/core/facade/lang';
11-
var Observable = require('@reactivex/rxjs/dist/cjs/Observable');
12-
11+
// todo(robwormald): temporary until https://github.com/angular/angular/issues/4390 decided
12+
var Rx = require('@reactivex/rxjs/dist/cjs/Rx');
13+
var {Observable} = Rx;
1314
export class JSONPConnection implements Connection {
1415
readyState: ReadyStates;
1516
request: Request;

modules/angular2/src/http/backends/mock_backend.ts

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import {Request} from '../static_request';
33
import {Response} from '../static_response';
44
import {ReadyStates} from '../enums';
55
import {Connection, ConnectionBackend} from '../interfaces';
6-
import {ObservableWrapper, EventEmitter} from 'angular2/src/core/facade/async';
76
import {isPresent} from 'angular2/src/core/facade/lang';
87
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
8+
var Rx = require('@reactivex/rxjs/dist/cjs/Rx');
9+
let{Subject, ReplaySubject} = Rx;
910

1011
/**
1112
*
@@ -30,23 +31,14 @@ export class MockConnection implements Connection {
3031
* {@link EventEmitter} of {@link Response}. Can be subscribed to in order to be notified when a
3132
* response is available.
3233
*/
33-
response: EventEmitter;
34+
response: any; // Subject<Response>
3435

3536
constructor(req: Request) {
36-
this.response = new EventEmitter();
37+
this.response = new ReplaySubject(1).take(1);
3738
this.readyState = ReadyStates.Open;
3839
this.request = req;
3940
}
4041

41-
/**
42-
* Changes the `readyState` of the connection to a custom state of 5 (cancelled).
43-
*/
44-
dispose() {
45-
if (this.readyState !== ReadyStates.Done) {
46-
this.readyState = ReadyStates.Cancelled;
47-
}
48-
}
49-
5042
/**
5143
* Sends a mock response to the connection. This response is the value that is emitted to the
5244
* {@link EventEmitter} returned by {@link Http}.
@@ -66,8 +58,8 @@ export class MockConnection implements Connection {
6658
throw new BaseException('Connection has already been resolved');
6759
}
6860
this.readyState = ReadyStates.Done;
69-
ObservableWrapper.callNext(this.response, res);
70-
ObservableWrapper.callReturn(this.response);
61+
this.response.next(res);
62+
this.response.complete();
7163
}
7264

7365
/**
@@ -92,8 +84,7 @@ export class MockConnection implements Connection {
9284
mockError(err?: Error) {
9385
// Matches XHR semantics
9486
this.readyState = ReadyStates.Done;
95-
ObservableWrapper.callThrow(this.response, err);
96-
ObservableWrapper.callReturn(this.response);
87+
this.response.error(err);
9788
}
9889
}
9990

@@ -162,7 +153,7 @@ export class MockBackend implements ConnectionBackend {
162153
*
163154
* This property only exists in the mock implementation, not in real Backends.
164155
*/
165-
connections: EventEmitter; //<MockConnection>
156+
connections: any; //<MockConnection>
166157

167158
/**
168159
* An array representation of `connections`. This array will be updated with each connection that
@@ -179,13 +170,12 @@ export class MockBackend implements ConnectionBackend {
179170
*
180171
* This property only exists in the mock implementation, not in real Backends.
181172
*/
182-
pendingConnections: EventEmitter; //<MockConnection>
173+
pendingConnections: any; // Subject<MockConnection>
183174
constructor() {
184175
this.connectionsArray = [];
185-
this.connections = new EventEmitter();
186-
ObservableWrapper.subscribe<MockConnection>(
187-
this.connections, connection => this.connectionsArray.push(connection));
188-
this.pendingConnections = new EventEmitter();
176+
this.connections = new Subject();
177+
this.connections.subscribe(connection => this.connectionsArray.push(connection));
178+
this.pendingConnections = new Subject();
189179
}
190180

191181
/**
@@ -195,7 +185,7 @@ export class MockBackend implements ConnectionBackend {
195185
*/
196186
verifyNoPendingRequests() {
197187
let pending = 0;
198-
ObservableWrapper.subscribe(this.pendingConnections, c => pending++);
188+
this.pendingConnections.subscribe(c => pending++);
199189
if (pending > 0) throw new BaseException(`${pending} pending connections to be resolved`);
200190
}
201191

@@ -205,9 +195,7 @@ export class MockBackend implements ConnectionBackend {
205195
*
206196
* This method only exists in the mock implementation, not in real Backends.
207197
*/
208-
resolveAllConnections() {
209-
ObservableWrapper.subscribe<MockConnection>(this.connections, c => c.readyState = 4);
210-
}
198+
resolveAllConnections() { this.connections.subscribe(c => c.readyState = 4); }
211199

212200
/**
213201
* Creates a new {@link MockConnection}. This is equivalent to calling `new
@@ -220,7 +208,7 @@ export class MockBackend implements ConnectionBackend {
220208
throw new BaseException(`createConnection requires an instance of Request, got ${req}`);
221209
}
222210
let connection = new MockConnection(req);
223-
ObservableWrapper.callNext(this.connections, connection);
211+
this.connections.next(connection);
224212
return connection;
225213
}
226214
}

modules/angular2/src/http/backends/xhr_backend.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import {ResponseOptions, BaseResponseOptions} from '../base_response_options';
66
import {Injectable} from 'angular2/src/core/di';
77
import {BrowserXhr} from './browser_xhr';
88
import {isPresent} from 'angular2/src/core/facade/lang';
9-
var Observable = require('@reactivex/rxjs/dist/cjs/Observable');
9+
// todo(robwormald): temporary until https://github.com/angular/angular/issues/4390 decided
10+
var Rx = require('@reactivex/rxjs/dist/cjs/Rx');
11+
var {Observable} = Rx;
1012
/**
1113
* Creates connections using `XMLHttpRequest`. Given a fully-qualified
1214
* request, an `XHRConnection` will immediately create an `XMLHttpRequest` object and send the

modules/angular2/src/http/http.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import {RequestOptionsArgs, Connection, ConnectionBackend} from './interfaces';
55
import {Request} from './static_request';
66
import {BaseRequestOptions, RequestOptions} from './base_request_options';
77
import {RequestMethods} from './enums';
8-
import {EventEmitter} from 'angular2/src/core/facade/async';
98

10-
function httpRequest(backend: ConnectionBackend, request: Request): EventEmitter {
9+
function httpRequest(backend: ConnectionBackend, request: Request): any {
1110
return backend.createConnection(request).response;
1211
}
1312

@@ -94,8 +93,8 @@ export class Http {
9493
* object can be provided as the 2nd argument. The options object will be merged with the values
9594
* of {@link BaseRequestOptions} before performing the request.
9695
*/
97-
request(url: string | Request, options?: RequestOptionsArgs): EventEmitter {
98-
var responseObservable: EventEmitter;
96+
request(url: string | Request, options?: RequestOptionsArgs): any {
97+
var responseObservable: any;
9998
if (isString(url)) {
10099
responseObservable = httpRequest(
101100
this._backend,
@@ -111,15 +110,15 @@ export class Http {
111110
/**
112111
* Performs a request with `get` http method.
113112
*/
114-
get(url: string, options?: RequestOptionsArgs): EventEmitter {
113+
get(url: string, options?: RequestOptionsArgs): any {
115114
return httpRequest(this._backend, new Request(mergeOptions(this._defaultOptions, options,
116115
RequestMethods.Get, url)));
117116
}
118117

119118
/**
120119
* Performs a request with `post` http method.
121120
*/
122-
post(url: string, body: string, options?: RequestOptionsArgs): EventEmitter {
121+
post(url: string, body: string, options?: RequestOptionsArgs): any {
123122
return httpRequest(
124123
this._backend,
125124
new Request(mergeOptions(this._defaultOptions.merge(new RequestOptions({body: body})),
@@ -129,7 +128,7 @@ export class Http {
129128
/**
130129
* Performs a request with `put` http method.
131130
*/
132-
put(url: string, body: string, options?: RequestOptionsArgs): EventEmitter {
131+
put(url: string, body: string, options?: RequestOptionsArgs): any {
133132
return httpRequest(
134133
this._backend,
135134
new Request(mergeOptions(this._defaultOptions.merge(new RequestOptions({body: body})),
@@ -139,15 +138,15 @@ export class Http {
139138
/**
140139
* Performs a request with `delete` http method.
141140
*/
142-
delete (url: string, options?: RequestOptionsArgs): EventEmitter {
141+
delete (url: string, options?: RequestOptionsArgs): any {
143142
return httpRequest(this._backend, new Request(mergeOptions(this._defaultOptions, options,
144143
RequestMethods.Delete, url)));
145144
}
146145

147146
/**
148147
* Performs a request with `patch` http method.
149148
*/
150-
patch(url: string, body: string, options?: RequestOptionsArgs): EventEmitter {
149+
patch(url: string, body: string, options?: RequestOptionsArgs): any {
151150
return httpRequest(
152151
this._backend,
153152
new Request(mergeOptions(this._defaultOptions.merge(new RequestOptions({body: body})),
@@ -157,7 +156,7 @@ export class Http {
157156
/**
158157
* Performs a request with `head` http method.
159158
*/
160-
head(url: string, options?: RequestOptionsArgs): EventEmitter {
159+
head(url: string, options?: RequestOptionsArgs): any {
161160
return httpRequest(this._backend, new Request(mergeOptions(this._defaultOptions, options,
162161
RequestMethods.Head, url)));
163162
}
@@ -175,8 +174,8 @@ export class Jsonp extends Http {
175174
* object can be provided as the 2nd argument. The options object will be merged with the values
176175
* of {@link BaseRequestOptions} before performing the request.
177176
*/
178-
request(url: string | Request, options?: RequestOptionsArgs): EventEmitter {
179-
var responseObservable: EventEmitter;
177+
request(url: string | Request, options?: RequestOptionsArgs): any {
178+
var responseObservable: any;
180179
if (isString(url)) {
181180
url = new Request(mergeOptions(this._defaultOptions, options, RequestMethods.Get, url));
182181
}

modules/angular2/src/http/interfaces.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export abstract class ConnectionBackend {
2222
export abstract class Connection {
2323
readyState: ReadyStates;
2424
request: Request;
25-
response: EventEmitter; // TODO: generic of <Response>;
25+
response: any; // TODO: generic of <Response>;
2626
}
2727

2828
/**
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import {
2+
AsyncTestCompleter,
3+
afterEach,
4+
beforeEach,
5+
ddescribe,
6+
describe,
7+
expect,
8+
iit,
9+
inject,
10+
it,
11+
xit,
12+
SpyObject
13+
} from 'angular2/test_lib';
14+
import {ObservableWrapper} from 'angular2/src/core/facade/async';
15+
import {BrowserXhr} from 'angular2/src/http/backends/browser_xhr';
16+
import {MockConnection, MockBackend} from 'angular2/src/http/backends/mock_backend';
17+
import {bind, Injector} from 'angular2/core';
18+
import {Request} from 'angular2/src/http/static_request';
19+
import {Response} from 'angular2/src/http/static_response';
20+
import {Headers} from 'angular2/src/http/headers';
21+
import {Map} from 'angular2/src/core/facade/collection';
22+
import {RequestOptions, BaseRequestOptions} from 'angular2/src/http/base_request_options';
23+
import {BaseResponseOptions, ResponseOptions} from 'angular2/src/http/base_response_options';
24+
import {ResponseTypes} from 'angular2/src/http/enums';
25+
26+
export function main() {
27+
describe('MockBackend', () => {
28+
29+
var backend;
30+
var sampleRequest1;
31+
var sampleResponse1;
32+
var sampleRequest2;
33+
var sampleResponse2;
34+
var connection;
35+
36+
beforeEach(() => {
37+
var injector = Injector.resolveAndCreate(
38+
[bind(ResponseOptions).toClass(BaseResponseOptions), MockBackend]);
39+
backend = injector.get(MockBackend);
40+
var base = new BaseRequestOptions();
41+
sampleRequest1 = new Request(base.merge(new RequestOptions({url: 'https://google.com'})));
42+
sampleResponse1 = new Response(new ResponseOptions({body: 'response1'}));
43+
sampleRequest2 = new Request(base.merge(new RequestOptions({url: 'https://google.com'})));
44+
sampleResponse2 = new Response(new ResponseOptions({body: 'response2'}));
45+
});
46+
47+
it('should create a new MockBackend', () => {expect(backend).toBeAnInstanceOf(MockBackend)});
48+
49+
it('should create a new MockConnection',
50+
() => {expect(backend.createConnection(sampleRequest1)).toBeAnInstanceOf(MockConnection)});
51+
52+
it('should create a new connection and allow subscription', () => {
53+
let connection = backend.createConnection(sampleRequest1);
54+
connection.response.subscribe(() => {});
55+
});
56+
57+
it('should allow responding after subscription', inject([AsyncTestCompleter], async => {
58+
let connection = backend.createConnection(sampleRequest1);
59+
connection.response.subscribe((res) => { async.done(); });
60+
connection.mockRespond(sampleResponse1);
61+
}));
62+
63+
it('should allow subscribing after responding', inject([AsyncTestCompleter], async => {
64+
let connection = backend.createConnection(sampleRequest1);
65+
connection.mockRespond(sampleResponse1);
66+
connection.response.subscribe((res) => { async.done(); });
67+
}));
68+
69+
it('should allow responding after subscription with an error',
70+
inject([AsyncTestCompleter], async => {
71+
let connection = backend.createConnection(sampleRequest1);
72+
connection.response.subscribe(null, () => { async.done(); });
73+
connection.mockError(new Error('nope'));
74+
}));
75+
76+
it('should not throw when there are no unresolved requests',
77+
inject([AsyncTestCompleter], async => {
78+
let connection = backend.createConnection(sampleRequest1);
79+
connection.response.subscribe(() => { async.done(); });
80+
connection.mockRespond(sampleResponse1);
81+
backend.verifyNoPendingRequests();
82+
}));
83+
84+
xit('should throw when there are unresolved requests', inject([AsyncTestCompleter], async => {
85+
let connection = backend.createConnection(sampleRequest1);
86+
connection.response.subscribe(() => { async.done(); });
87+
backend.verifyNoPendingRequests();
88+
}));
89+
90+
it('should work when requests are resolved out of order',
91+
inject([AsyncTestCompleter], async => {
92+
let connection1 = backend.createConnection(sampleRequest1);
93+
let connection2 = backend.createConnection(sampleRequest1);
94+
connection1.response.subscribe(() => { async.done(); });
95+
connection2.response.subscribe(() => {});
96+
connection2.mockRespond(sampleResponse1);
97+
connection1.mockRespond(sampleResponse1);
98+
backend.verifyNoPendingRequests();
99+
}));
100+
101+
xit('should allow double subscribing', inject([AsyncTestCompleter], async => {
102+
let responses = [sampleResponse1, sampleResponse2];
103+
backend.connections.subscribe(c => c.mockRespond(responses.shift()));
104+
let responseObservable = backend.createConnection(sampleRequest1).response;
105+
responseObservable.subscribe(res => expect(res.text()).toBe('response1'));
106+
responseObservable.subscribe(res => expect(res.text()).toBe('response2'), null,
107+
async.done);
108+
}));
109+
110+
// TODO(robwormald): readyStates are leaving?
111+
it('should allow resolution of requests manually', () => {
112+
let connection1: MockConnection = backend.createConnection(sampleRequest1);
113+
let connection2: MockConnection = backend.createConnection(sampleRequest1);
114+
connection1.response.subscribe(() => {});
115+
connection2.response.subscribe(() => {});
116+
backend.resolveAllConnections();
117+
backend.verifyNoPendingRequests();
118+
});
119+
});
120+
}

0 commit comments

Comments
 (0)