parent
e68e69e7e5
commit
5b5ffe75d0
|
@ -10,8 +10,9 @@ module.exports = new Package('angular-public', [basePackage])
|
|||
'angular2/core.ts',
|
||||
'angular2/di.ts',
|
||||
'angular2/directives.ts',
|
||||
'angular2/http.ts',
|
||||
'angular2/forms.ts',
|
||||
'angular2/router.js',
|
||||
'angular2/router.ts',
|
||||
'angular2/test.ts',
|
||||
'angular2/pipes.ts'
|
||||
];
|
||||
|
|
|
@ -4,6 +4,7 @@ export * from './annotations';
|
|||
export * from './directives';
|
||||
export * from './forms';
|
||||
export * from './di';
|
||||
export * from './http';
|
||||
export {Observable, EventEmitter} from 'angular2/src/facade/async';
|
||||
export * from 'angular2/src/render/api';
|
||||
export {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
library angular2.http;
|
|
@ -1,10 +1,41 @@
|
|||
/**
|
||||
* @module
|
||||
* @public
|
||||
* @description
|
||||
* The http module provides services to perform http requests. To get started, see the {@link Http}
|
||||
* class.
|
||||
*/
|
||||
import {bind, Binding} from 'angular2/di';
|
||||
import {Http, HttpFactory} from './src/http/http';
|
||||
import {XHRBackend} from 'angular2/src/http/backends/xhr_backend';
|
||||
import {XHRBackend, XHRConnection} 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';
|
||||
import {BaseRequestOptions, RequestOptions} from 'angular2/src/http/base_request_options';
|
||||
|
||||
export {Http};
|
||||
export {MockConnection, MockBackend} from 'angular2/src/http/backends/mock_backend';
|
||||
export {Request} from 'angular2/src/http/static_request';
|
||||
export {Response} from 'angular2/src/http/static_response';
|
||||
|
||||
export {Http, XHRBackend, XHRConnection, BaseRequestOptions, RequestOptions, HttpFactory};
|
||||
export {IHttp} from 'angular2/src/http/interfaces';
|
||||
export {Headers} from 'angular2/src/http/headers';
|
||||
|
||||
/**
|
||||
* Provides a basic set of injectables to use the {@link Http} service in any application.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {httpInjectables, Http} from 'angular2/http';
|
||||
* @Component({selector: 'http-app', appInjector: [httpInjectables]})
|
||||
* @View({template: '{{data}}'})
|
||||
* class MyApp {
|
||||
* constructor(http:Http) {
|
||||
* http.request('data.txt').subscribe(res => this.data = res.text());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
export var httpInjectables: List<any> = [
|
||||
bind(BrowserXHR)
|
||||
.toValue(BrowserXHR),
|
||||
|
|
|
@ -2,31 +2,40 @@ import {Injectable} from 'angular2/di';
|
|||
import {Request} from 'angular2/src/http/static_request';
|
||||
import {Response} from 'angular2/src/http/static_response';
|
||||
import {ReadyStates} from 'angular2/src/http/enums';
|
||||
import {Connection, ConnectionBackend} from 'angular2/src/http/interfaces';
|
||||
import * as Rx from 'rx';
|
||||
|
||||
/**
|
||||
* Connection represents a request and response for an underlying transport, like XHR or mock.
|
||||
* The mock implementation contains helper methods to respond to connections within tests.
|
||||
* API subject to change and expand.
|
||||
*
|
||||
* Connection class used by MockBackend
|
||||
*
|
||||
* This class is typically not instantiated directly, but instances can be retrieved by subscribing
|
||||
*to the `connections` Observable of
|
||||
* {@link MockBackend} in order to mock responses to requests.
|
||||
*
|
||||
**/
|
||||
export class Connection {
|
||||
export class MockConnection implements Connection {
|
||||
// TODO Name `readyState` should change to be more generic, and states could be made to be more
|
||||
// descriptive than XHR states.
|
||||
/**
|
||||
* Observer to call on download progress, if provided in config.
|
||||
**/
|
||||
downloadObserver: Rx.Observer<Response>;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Name `readyState` should change to be more generic, and states could be made to be more
|
||||
* descriptive than XHR states.
|
||||
**/
|
||||
|
||||
* Describes the state of the connection, based on `XMLHttpRequest.readyState`, but with
|
||||
* additional states. For example, state 5 indicates an aborted connection.
|
||||
*/
|
||||
readyState: ReadyStates;
|
||||
|
||||
/**
|
||||
* {@link Request} instance used to create the connection.
|
||||
*/
|
||||
request: Request;
|
||||
|
||||
/**
|
||||
* [RxJS
|
||||
* Observable](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md)
|
||||
* of {@link Response}. Can be subscribed to in order to be notified when a response is available.
|
||||
*/
|
||||
response: Rx.Subject<Response>;
|
||||
|
||||
constructor(req: Request) {
|
||||
// State
|
||||
if (Rx.hasOwnProperty('default')) {
|
||||
this.response = new ((<any>Rx).default.Rx.Subject)();
|
||||
} else {
|
||||
|
@ -38,6 +47,9 @@ export class Connection {
|
|||
this.dispose = this.dispose.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the `readyState` of the connection to a custom state of 5 (cancelled).
|
||||
*/
|
||||
dispose() {
|
||||
if (this.readyState !== ReadyStates.DONE) {
|
||||
this.readyState = ReadyStates.CANCELLED;
|
||||
|
@ -45,8 +57,19 @@ export class Connection {
|
|||
}
|
||||
|
||||
/**
|
||||
* Called after a connection has been established.
|
||||
**/
|
||||
* Sends a mock response to the connection. This response is the value that is emitted to the
|
||||
* `Observable` returned by {@link Http}.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* var connection;
|
||||
* backend.connections.subscribe(c => connection = c);
|
||||
* http.request('data.json').subscribe(res => console.log(res.text()));
|
||||
* connection.mockRespond(new Response('fake response')); //logs 'fake response'
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
mockRespond(res: Response) {
|
||||
if (this.readyState >= ReadyStates.DONE) {
|
||||
throw new Error('Connection has already been resolved');
|
||||
|
@ -56,13 +79,24 @@ export class Connection {
|
|||
this.response.onCompleted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Not yet implemented!
|
||||
*
|
||||
* Sends the provided {@link Response} to the `downloadObserver` of the `Request`
|
||||
* associated with this connection.
|
||||
*/
|
||||
mockDownload(res: Response) {
|
||||
this.downloadObserver.onNext(res);
|
||||
if (res.bytesLoaded === res.totalBytes) {
|
||||
this.downloadObserver.onCompleted();
|
||||
}
|
||||
// this.request.downloadObserver.onNext(res);
|
||||
// if (res.bytesLoaded === res.totalBytes) {
|
||||
// this.request.downloadObserver.onCompleted();
|
||||
// }
|
||||
}
|
||||
|
||||
// TODO(jeffbcross): consider using Response type
|
||||
/**
|
||||
* Emits the provided error object as an error to the {@link Response} observable returned
|
||||
* from {@link Http}.
|
||||
*/
|
||||
mockError(err?) {
|
||||
// Matches XHR semantics
|
||||
this.readyState = ReadyStates.DONE;
|
||||
|
@ -71,35 +105,135 @@ export class Connection {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A mock backend for testing the {@link Http} service.
|
||||
*
|
||||
* This class can be injected in tests, and should be used to override bindings
|
||||
* to other backends, such as {@link XHRBackend}.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {MockBackend, DefaultOptions, Http} from 'angular2/http';
|
||||
* it('should get some data', inject([AsyncTestCompleter], (async) => {
|
||||
* var connection;
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* MockBackend,
|
||||
* bind(Http).toFactory((backend, defaultOptions) => {
|
||||
* return new Http(backend, defaultOptions)
|
||||
* }, [MockBackend, DefaultOptions])]);
|
||||
* var http = injector.get(Http);
|
||||
* var backend = injector.get(MockBackend);
|
||||
* //Assign any newly-created connection to local variable
|
||||
* backend.connections.subscribe(c => connection = c);
|
||||
* http.request('data.json').subscribe((res) => {
|
||||
* expect(res.text()).toBe('awesome');
|
||||
* async.done();
|
||||
* });
|
||||
* connection.mockRespond(new Response('awesome'));
|
||||
* }));
|
||||
* ```
|
||||
*
|
||||
* This method only exists in the mock implementation, not in real Backends.
|
||||
**/
|
||||
@Injectable()
|
||||
export class MockBackend {
|
||||
connections: Rx.Subject<Connection>;
|
||||
connectionsArray: Array<Connection>;
|
||||
pendingConnections: Rx.Observable<Connection>;
|
||||
export class MockBackend implements ConnectionBackend {
|
||||
/**
|
||||
* [RxJS
|
||||
* Subject](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/subjects/subject.md)
|
||||
* of {@link MockConnection} instances that have been created by this backend. Can be subscribed
|
||||
* to in order to respond to connections.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {MockBackend, Http, BaseRequestOptions} from 'angular2/http';
|
||||
* import {Injector} from 'angular2/di';
|
||||
*
|
||||
* it('should get a response', () => {
|
||||
* var connection; //this will be set when a new connection is emitted from the backend.
|
||||
* var text; //this will be set from mock response
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* MockBackend,
|
||||
* bind(Http).toFactory(backend, options) {
|
||||
* return new Http(backend, options);
|
||||
* }, [MockBackend, BaseRequestOptions]]);
|
||||
* var backend = injector.get(MockBackend);
|
||||
* var http = injector.get(Http);
|
||||
* backend.connections.subscribe(c => connection = c);
|
||||
* http.request('something.json').subscribe(res => {
|
||||
* text = res.text();
|
||||
* });
|
||||
* connection.mockRespond(new Response('Something'));
|
||||
* expect(text).toBe('Something');
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* This property only exists in the mock implementation, not in real Backends.
|
||||
*/
|
||||
connections: Rx.Subject<MockConnection>;
|
||||
|
||||
/**
|
||||
* An array representation of `connections`. This array will be updated with each connection that
|
||||
* is created by this backend.
|
||||
*
|
||||
* This property only exists in the mock implementation, not in real Backends.
|
||||
*/
|
||||
connectionsArray: Array<MockConnection>;
|
||||
/**
|
||||
* [Observable](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md)
|
||||
* of {@link MockConnection} instances that haven't yet been resolved (i.e. with a `readyState`
|
||||
* less than 4). Used internally to verify that no connections are pending via the
|
||||
* `verifyNoPendingRequests` method.
|
||||
*
|
||||
* This property only exists in the mock implementation, not in real Backends.
|
||||
*/
|
||||
pendingConnections: Rx.Observable<MockConnection>;
|
||||
constructor() {
|
||||
var Observable;
|
||||
this.connectionsArray = [];
|
||||
if (Rx.hasOwnProperty('default')) {
|
||||
this.connections = new (<any>Rx).default.Rx.Subject();
|
||||
Observable = (<any>Rx).default.Rx.Observable;
|
||||
} else {
|
||||
this.connections = new Rx.Subject<Connection>();
|
||||
this.connections = new Rx.Subject<MockConnection>();
|
||||
Observable = Rx.Observable;
|
||||
}
|
||||
this.connections.subscribe(connection => this.connectionsArray.push(connection));
|
||||
this.pendingConnections = this.connections.filter((c) => c.readyState < ReadyStates.DONE);
|
||||
this.pendingConnections =
|
||||
Observable.fromArray(this.connectionsArray).filter((c) => c.readyState < ReadyStates.DONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks all connections, and raises an exception if any connection has not received a response.
|
||||
*
|
||||
* This method only exists in the mock implementation, not in real Backends.
|
||||
*/
|
||||
verifyNoPendingRequests() {
|
||||
let pending = 0;
|
||||
this.pendingConnections.subscribe((c) => pending++);
|
||||
if (pending > 0) throw new Error(`${pending} pending connections to be resolved`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used in conjunction with `verifyNoPendingRequests` to resolve any not-yet-resolve
|
||||
* connections, if it's expected that there are connections that have not yet received a response.
|
||||
*
|
||||
* This method only exists in the mock implementation, not in real Backends.
|
||||
*/
|
||||
resolveAllConnections() { this.connections.subscribe((c) => c.readyState = 4); }
|
||||
|
||||
/**
|
||||
* Creates a new {@link MockConnection}. This is equivalent to calling `new
|
||||
* MockConnection()`, except that it also will emit the new `Connection` to the `connections`
|
||||
* observable of this `MockBackend` instance. This method will usually only be used by tests
|
||||
* against the framework itself, not by end-users.
|
||||
*/
|
||||
createConnection(req: Request) {
|
||||
if (!req || !(req instanceof Request)) {
|
||||
throw new Error(`createConnection requires an instance of Request, got ${req}`);
|
||||
}
|
||||
let connection = new Connection(req);
|
||||
let connection = new MockConnection(req);
|
||||
this.connections.onNext(connection);
|
||||
return connection;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,21 @@ import {Injectable} from 'angular2/di';
|
|||
import {BrowserXHR} from './browser_xhr';
|
||||
import * as Rx from 'rx';
|
||||
|
||||
/**
|
||||
* Creates connections using `XMLHttpRequest`. Given a fully-qualified
|
||||
* request, an `XHRConnection` will immediately create an `XMLHttpRequest` object and send the
|
||||
* request.
|
||||
*
|
||||
* This class would typically not be created or interacted with directly inside applications, though
|
||||
* the {@link MockConnection} may be interacted with in tests.
|
||||
*/
|
||||
export class XHRConnection implements Connection {
|
||||
request: Request;
|
||||
/**
|
||||
* Response
|
||||
* [Subject](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/subjects/subject.md)
|
||||
* which emits a single {@link Response} value on load event of `XMLHttpRequest`.
|
||||
*/
|
||||
response: Rx.Subject<Response>;
|
||||
readyState: ReadyStates;
|
||||
private _xhr;
|
||||
|
@ -20,6 +33,7 @@ export class XHRConnection implements Connection {
|
|||
this.response = new Rx.Subject<Response>();
|
||||
}
|
||||
this._xhr = new NativeConstruct();
|
||||
// TODO(jeffbcross): implement error listening/propagation
|
||||
this._xhr.open(RequestMethods[req.method], req.url);
|
||||
this._xhr.addEventListener(
|
||||
'load',
|
||||
|
@ -28,9 +42,38 @@ export class XHRConnection implements Connection {
|
|||
this._xhr.send(this.request.text());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls abort on the underlying XMLHttpRequest.
|
||||
*/
|
||||
dispose(): void { this._xhr.abort(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link XHRConnection} instances.
|
||||
*
|
||||
* This class would typically not be used by end users, but could be
|
||||
* overridden if a different backend implementation should be used,
|
||||
* such as in a node backend.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {Http, MyNodeBackend, httpInjectables, BaseRequestOptions} from 'angular2/http';
|
||||
* @Component({
|
||||
* appInjector: [
|
||||
* httpInjectables,
|
||||
* bind(Http).toFactory((backend, options) => {
|
||||
* return new Http(backend, options);
|
||||
* }, [MyNodeBackend, BaseRequestOptions])]
|
||||
* })
|
||||
* class MyComponent {
|
||||
* constructor(http:Http) {
|
||||
* http('people.json').subscribe(res => this.people = res.json());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
**/
|
||||
@Injectable()
|
||||
export class XHRBackend implements ConnectionBackend {
|
||||
constructor(private _NativeConstruct: BrowserXHR) {}
|
||||
|
|
|
@ -6,9 +6,28 @@ import {IRequestOptions} from './interfaces';
|
|||
import {Injectable} from 'angular2/di';
|
||||
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
/**
|
||||
* Creates a request options object with default properties as described in the [Fetch
|
||||
* Spec](https://fetch.spec.whatwg.org/#requestinit) to be optionally provided when instantiating a
|
||||
* {@link Request}. This class is used implicitly by {@link Http} to merge in provided request
|
||||
* options with the default options specified here. These same default options are injectable via
|
||||
* the {@link BaseRequestOptions} class.
|
||||
*/
|
||||
export class RequestOptions implements IRequestOptions {
|
||||
/**
|
||||
* Http method with which to execute the request.
|
||||
*
|
||||
* Defaults to "GET".
|
||||
*/
|
||||
method: RequestMethods = RequestMethods.GET;
|
||||
/**
|
||||
* Headers object based on the `Headers` class in the [Fetch
|
||||
* Spec](https://fetch.spec.whatwg.org/#headers-class).
|
||||
*/
|
||||
headers: Headers;
|
||||
/**
|
||||
* Body to be used when creating the request.
|
||||
*/
|
||||
body: URLSearchParams | FormData | Blob | string;
|
||||
mode: RequestModesOpts = RequestModesOpts.Cors;
|
||||
credentials: RequestCredentialsOpts;
|
||||
|
@ -25,11 +44,33 @@ export class RequestOptions implements IRequestOptions {
|
|||
this.cache = cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the `RequestOptions` instance, using the optional input as values to override
|
||||
* existing values.
|
||||
*/
|
||||
merge(opts: IRequestOptions = {}): RequestOptions {
|
||||
return new RequestOptions(StringMapWrapper.merge(this, opts));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injectable version of {@link RequestOptions}.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {Http, BaseRequestOptions, Request} from 'angular2/http';
|
||||
* ...
|
||||
* class MyComponent {
|
||||
* constructor(baseRequestOptions:BaseRequestOptions, http:Http) {
|
||||
* var options = baseRequestOptions.merge({body: 'foobar'});
|
||||
* var request = new Request('https://foo', options);
|
||||
* http.request(request).subscribe(res => this.bars = res.json());
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@Injectable()
|
||||
export class BaseRequestOptions extends RequestOptions {
|
||||
constructor() { super(); }
|
||||
|
|
|
@ -14,9 +14,11 @@ import {
|
|||
ListWrapper
|
||||
} from 'angular2/src/facade/collection';
|
||||
|
||||
// (@jeffbcross): This is implemented mostly to spec, except that the entries method has been
|
||||
// removed because it doesn't exist in dart, and it doesn't seem worth adding it to the facade.
|
||||
|
||||
/**
|
||||
* Polyfill for [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers/Headers), as
|
||||
* specified in the [Fetch Spec](https://fetch.spec.whatwg.org/#headers-class). The only known
|
||||
* difference from the spec is the lack of an `entries` method.
|
||||
*/
|
||||
export class Headers {
|
||||
_headersMap: Map<string, List<string>>;
|
||||
constructor(headers?: Headers | Object) {
|
||||
|
|
|
@ -10,41 +10,6 @@ import {RequestMethods} from './enums';
|
|||
import {URLSearchParams} from './url_search_params';
|
||||
import * as Rx from 'rx';
|
||||
|
||||
/**
|
||||
* A function to perform http requests over XMLHttpRequest.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* @Component({
|
||||
* appInjector: [httpBindings]
|
||||
* })
|
||||
* @View({
|
||||
* directives: [NgFor],
|
||||
* template: `
|
||||
* <ul>
|
||||
* <li *ng-for="#person of people">
|
||||
* hello, {{person.name}}
|
||||
* </li>
|
||||
* </ul>
|
||||
* `
|
||||
* })
|
||||
* class MyComponent {
|
||||
* constructor(http:Http) {
|
||||
* http('people.json').subscribe(res => this.people = res.json());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* This function is bound to a single underlying connection mechanism, such as XHR, which could be
|
||||
* mocked with dependency injection by replacing the `Backend` binding. For other transports, like
|
||||
* JSONP or Node, a separate http function would be created, such as httpJSONP.
|
||||
*
|
||||
* @exportedAs angular2/http
|
||||
*
|
||||
**/
|
||||
|
||||
function httpRequest(backend: XHRBackend, request: Request) {
|
||||
return <Rx.Observable<Response>>(Observable.create(observer => {
|
||||
var connection: Connection = backend.createConnection(request);
|
||||
|
@ -56,51 +21,123 @@ function httpRequest(backend: XHRBackend, request: Request) {
|
|||
}))
|
||||
}
|
||||
|
||||
// Abstract
|
||||
/**
|
||||
* Performs http requests using `XMLHttpRequest` as the default backend.
|
||||
*
|
||||
* `Http` is available as an injectable class, with methods to perform http requests. Calling
|
||||
* `request` returns an
|
||||
* [Observable](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md),
|
||||
* which will emit a single {@link Response} when a response is
|
||||
* received.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {Http, httpInjectables} from 'angular2/http';
|
||||
* @Component({selector: 'http-app', appInjector: [httpInjectables]})
|
||||
* @View({templateUrl: 'people.html'})
|
||||
* class PeopleComponent {
|
||||
* constructor(http: Http) {
|
||||
* http('people.json')
|
||||
* // Call map on the response observable to get the parsed people object
|
||||
* .map(res => res.json())
|
||||
* // Subscribe to the observable to get the parsed people object and attach it to the
|
||||
* // component
|
||||
* .subscribe(people => this.people = people);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The default construct used to perform requests, `XMLHttpRequest`, is abstracted as a "Backend" (
|
||||
* {@link XHRBackend} in this case), which could be mocked with dependency injection by replacing
|
||||
* the {@link XHRBackend} binding, as in the following example:
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {MockBackend, BaseRequestOptions, Http} from 'angular2/http';
|
||||
* var injector = Injector.resolveAndCreate([
|
||||
* BaseRequestOptions,
|
||||
* MockBackend,
|
||||
* bind(Http).toFactory(
|
||||
* function(backend, defaultOptions) {
|
||||
* return new Http(backend, defaultOptions);
|
||||
* },
|
||||
* [MockBackend, BaseRequestOptions])
|
||||
* ]);
|
||||
* var http = injector.get(Http);
|
||||
* http.get('request-from-mock-backend.json').subscribe((res:Response) => doSomething(res));
|
||||
* ```
|
||||
*
|
||||
**/
|
||||
@Injectable()
|
||||
export class Http {
|
||||
constructor(private backend: XHRBackend, private defaultOptions: BaseRequestOptions) {}
|
||||
constructor(private _backend: XHRBackend, private _defaultOptions: BaseRequestOptions) {}
|
||||
|
||||
/**
|
||||
* Performs any type of http request. First argument is required, and can either be a url or
|
||||
* a {@link Request} instance. If the first argument is a url, an optional {@link RequestOptions}
|
||||
* object can be provided as the 2nd argument. The options object will be merged with the values
|
||||
* of {@link BaseRequestOptions} before performing the request.
|
||||
*/
|
||||
request(url: string | Request, options?: IRequestOptions): Rx.Observable<Response> {
|
||||
if (typeof url === 'string') {
|
||||
return httpRequest(this.backend, new Request(url, this.defaultOptions.merge(options)));
|
||||
return httpRequest(this._backend, new Request(url, this._defaultOptions.merge(options)));
|
||||
} else if (url instanceof Request) {
|
||||
return httpRequest(this.backend, url);
|
||||
return httpRequest(this._backend, url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request with `get` http method.
|
||||
*/
|
||||
get(url: string, options?: IRequestOptions) {
|
||||
return httpRequest(this.backend, new Request(url, this.defaultOptions.merge(options)
|
||||
.merge({method: RequestMethods.GET})));
|
||||
return httpRequest(this._backend, new Request(url, this._defaultOptions.merge(options)
|
||||
.merge({method: RequestMethods.GET})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request with `post` http method.
|
||||
*/
|
||||
post(url: string, body: URLSearchParams | FormData | Blob | string, options?: IRequestOptions) {
|
||||
return httpRequest(this.backend,
|
||||
new Request(url, this.defaultOptions.merge(options)
|
||||
return httpRequest(this._backend,
|
||||
new Request(url, this._defaultOptions.merge(options)
|
||||
|
||||
.merge({body: body, method: RequestMethods.POST})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request with `put` http method.
|
||||
*/
|
||||
put(url: string, body: URLSearchParams | FormData | Blob | string, options?: IRequestOptions) {
|
||||
return httpRequest(this.backend,
|
||||
new Request(url, this.defaultOptions.merge(options)
|
||||
return httpRequest(this._backend,
|
||||
new Request(url, this._defaultOptions.merge(options)
|
||||
.merge({body: body, method: RequestMethods.PUT})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request with `delete` http method.
|
||||
*/
|
||||
delete (url: string, options?: IRequestOptions) {
|
||||
return httpRequest(this.backend, new Request(url, this.defaultOptions.merge(options)
|
||||
.merge({method: RequestMethods.DELETE})));
|
||||
return httpRequest(this._backend, new Request(url, this._defaultOptions.merge(options).merge(
|
||||
{method: RequestMethods.DELETE})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request with `patch` http method.
|
||||
*/
|
||||
patch(url: string, body: URLSearchParams | FormData | Blob | string, options?: IRequestOptions) {
|
||||
return httpRequest(this.backend,
|
||||
new Request(url, this.defaultOptions.merge(options)
|
||||
return httpRequest(this._backend,
|
||||
new Request(url, this._defaultOptions.merge(options)
|
||||
.merge({body: body, method: RequestMethods.PATCH})));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a request with `head` http method.
|
||||
*/
|
||||
head(url: string, options?: IRequestOptions) {
|
||||
return httpRequest(this.backend, new Request(url, this.defaultOptions.merge(options)
|
||||
.merge({method: RequestMethods.HEAD})));
|
||||
return httpRequest(this._backend, new Request(url, this._defaultOptions.merge(options)
|
||||
.merge({method: RequestMethods.HEAD})));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,6 +147,30 @@ if (Rx.hasOwnProperty('default')) {
|
|||
} else {
|
||||
Observable = Rx.Observable;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Alias to the `request` method of {@link Http}, for those who'd prefer a simple function instead
|
||||
* of an object. In order to get TypeScript type information about the `HttpFactory`, the {@link
|
||||
* IHttp} interface can be used as shown in the following example.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* import {httpInjectables, HttpFactory, IHttp} from 'angular2/http';
|
||||
* @Component({
|
||||
* appInjector: [httpInjectables]
|
||||
* })
|
||||
* @View({
|
||||
* templateUrl: 'people.html'
|
||||
* })
|
||||
* class MyComponent {
|
||||
* constructor(@Inject(HttpFactory) http:IHttp) {
|
||||
* http('people.json').subscribe(res => this.people = res.json());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
**/
|
||||
export function HttpFactory(backend: XHRBackend, defaultOptions: BaseRequestOptions) {
|
||||
return function(url: string | Request, options?: IRequestOptions) {
|
||||
if (typeof url === 'string') {
|
||||
|
|
|
@ -58,6 +58,27 @@ export interface Connection {
|
|||
dispose(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an interface to provide type information for {@link HttpFactory} when injecting.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* * import {httpInjectables, HttpFactory, IHttp} from 'angular2/http';
|
||||
* @Component({
|
||||
* appInjector: [httpInjectables]
|
||||
* })
|
||||
* @View({
|
||||
* templateUrl: 'people.html'
|
||||
* })
|
||||
* class MyComponent {
|
||||
* constructor(@Inject(HttpFactory) http:IHttp) {
|
||||
* http('people.json').subscribe(res => this.people = res.json());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
// Prefixed as IHttp because used in conjunction with Http class, but interface is callable
|
||||
// constructor(@Inject(Http) http:IHttp)
|
||||
export interface IHttp { (url: string, options?: IRequestOptions): Rx.Observable<Response> }
|
||||
|
|
|
@ -4,26 +4,37 @@ import {IRequestOptions, Request as IRequest} from './interfaces';
|
|||
import {Headers} from './headers';
|
||||
import {BaseException, RegExpWrapper} from 'angular2/src/facade/lang';
|
||||
|
||||
// TODO(jeffbcross): implement body accessors
|
||||
// TODO(jeffbcross): properly implement body accessors
|
||||
/**
|
||||
* Creates `Request` instances with default values.
|
||||
*
|
||||
* The Request's interface is inspired by the Request constructor defined in the [Fetch
|
||||
* Spec](https://fetch.spec.whatwg.org/#request-class),
|
||||
* but is considered a static value whose body can be accessed many times. There are other
|
||||
* differences in the implementation, but this is the most significant.
|
||||
*/
|
||||
export class Request implements IRequest {
|
||||
/**
|
||||
* Http method with which to perform the request.
|
||||
*
|
||||
* Defaults to GET.
|
||||
*/
|
||||
method: RequestMethods;
|
||||
mode: RequestModesOpts;
|
||||
credentials: RequestCredentialsOpts;
|
||||
headers: Headers;
|
||||
/*
|
||||
* Non-Standard Properties
|
||||
/**
|
||||
* Headers object based on the `Headers` class in the [Fetch
|
||||
* Spec](https://fetch.spec.whatwg.org/#headers-class). {@link Headers} class reference.
|
||||
*/
|
||||
// This property deviates from the standard. Body can be set in constructor, but is only
|
||||
// accessible
|
||||
// via json(), text(), arrayBuffer(), and blob() accessors, which also change the request's state
|
||||
// to "used".
|
||||
private body: URLSearchParams | FormData | Blob | string;
|
||||
headers: Headers;
|
||||
|
||||
constructor(public url: string, {body, method = RequestMethods.GET, mode = RequestModesOpts.Cors,
|
||||
credentials = RequestCredentialsOpts.Omit,
|
||||
headers = new Headers()}: IRequestOptions = {}) {
|
||||
this.body = body;
|
||||
// Defaults to 'GET', consistent with browser
|
||||
private _body: URLSearchParams | FormData | Blob | string;
|
||||
|
||||
constructor(/** Url of the remote resource */ public url: string,
|
||||
{body, method = RequestMethods.GET, mode = RequestModesOpts.Cors,
|
||||
credentials = RequestCredentialsOpts.Omit,
|
||||
headers = new Headers()}: IRequestOptions = {}) {
|
||||
this._body = body;
|
||||
this.method = method;
|
||||
// Defaults to 'cors', consistent with browser
|
||||
// TODO(jeffbcross): implement behavior
|
||||
|
@ -31,9 +42,13 @@ export class Request implements IRequest {
|
|||
// Defaults to 'omit', consistent with browser
|
||||
// TODO(jeffbcross): implement behavior
|
||||
this.credentials = credentials;
|
||||
// Defaults to empty headers object, consistent with browser
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
text(): String { return this.body ? this.body.toString() : ''; }
|
||||
/**
|
||||
* Returns the request's body as string, assuming that body exists. If body is undefined, return
|
||||
* empty
|
||||
* string.
|
||||
*/
|
||||
text(): String { return this._body ? this._body.toString() : ''; }
|
||||
}
|
||||
|
|
|
@ -4,22 +4,79 @@ import {baseResponseOptions} from './base_response_options';
|
|||
import {BaseException, isJsObject, isString, global} from 'angular2/src/facade/lang';
|
||||
import {Headers} from './headers';
|
||||
|
||||
// TODO: make this injectable so baseResponseOptions can be overridden
|
||||
// TODO: make this injectable so baseResponseOptions can be overridden, mostly for the benefit of
|
||||
// headers merging.
|
||||
/**
|
||||
* Creates `Response` instances with default values.
|
||||
*
|
||||
* Though this object isn't
|
||||
* usually instantiated by end-users, it is the primary object interacted with when it comes time to
|
||||
* add data to a view.
|
||||
*
|
||||
* #Example
|
||||
*
|
||||
* ```
|
||||
* http.request('my-friends.txt').subscribe(response => this.friends = response.text());
|
||||
* ```
|
||||
*
|
||||
* The Response's interface is inspired by the Request constructor defined in the [Fetch
|
||||
* Spec](https://fetch.spec.whatwg.org/#response-class), but is considered a static value whose body
|
||||
* can be accessed many times. There are other differences in the implementation, but this is the
|
||||
* most significant.
|
||||
*/
|
||||
export class Response implements IResponse {
|
||||
/**
|
||||
* One of "basic", "cors", "default", "error, or "opaque".
|
||||
*
|
||||
* Defaults to "default".
|
||||
*/
|
||||
type: ResponseTypes;
|
||||
/**
|
||||
* True if the response's status is within 200-299
|
||||
*/
|
||||
ok: boolean;
|
||||
/**
|
||||
* URL of response.
|
||||
*
|
||||
* Defaults to empty string.
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Status code returned by server.
|
||||
*
|
||||
* Defaults to 200.
|
||||
*/
|
||||
status: number;
|
||||
/**
|
||||
* Text representing the corresponding reason phrase to the `status`, as defined in [ietf rfc 2616
|
||||
* section 6.1.1](https://tools.ietf.org/html/rfc2616#section-6.1.1)
|
||||
*
|
||||
* Defaults to "OK"
|
||||
*/
|
||||
statusText: string;
|
||||
/**
|
||||
* Non-standard property
|
||||
*
|
||||
* Denotes how many of the response body's bytes have been loaded, for example if the response is
|
||||
* the result of a progress event.
|
||||
*/
|
||||
bytesLoaded: number;
|
||||
/**
|
||||
* Non-standard property
|
||||
*
|
||||
* Denotes how many bytes are expected in the final response body.
|
||||
*/
|
||||
totalBytes: number;
|
||||
/**
|
||||
* Headers object based on the `Headers` class in the [Fetch
|
||||
* Spec](https://fetch.spec.whatwg.org/#headers-class).
|
||||
*/
|
||||
headers: Headers;
|
||||
constructor(private body?: string | Object | ArrayBuffer | JSON | FormData | Blob,
|
||||
constructor(private _body?: string | Object | ArrayBuffer | JSON | FormData | Blob,
|
||||
{status, statusText, headers, type, url}: ResponseOptions = baseResponseOptions) {
|
||||
if (isJsObject(headers)) {
|
||||
headers = new Headers(headers);
|
||||
}
|
||||
this.body = body;
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.headers = <Headers>headers;
|
||||
|
@ -27,20 +84,32 @@ export class Response implements IResponse {
|
|||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not yet implemented
|
||||
*/
|
||||
blob(): Blob {
|
||||
throw new BaseException('"blob()" method not implemented on Response superclass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to return body as parsed `JSON` object, or raises an exception.
|
||||
*/
|
||||
json(): JSON {
|
||||
if (isJsObject(this.body)) {
|
||||
return <JSON>this.body;
|
||||
} else if (isString(this.body)) {
|
||||
return global.JSON.parse(<string>this.body);
|
||||
if (isJsObject(this._body)) {
|
||||
return <JSON>this._body;
|
||||
} else if (isString(this._body)) {
|
||||
return global.JSON.parse(<string>this._body);
|
||||
}
|
||||
}
|
||||
|
||||
text(): string { return this.body.toString(); }
|
||||
/**
|
||||
* Returns the body as a string, presuming `toString()` can be called on the response body.
|
||||
*/
|
||||
text(): string { return this._body.toString(); }
|
||||
|
||||
/**
|
||||
* Not yet implemented
|
||||
*/
|
||||
arrayBuffer(): ArrayBuffer {
|
||||
throw new BaseException('"arrayBuffer()" method not implemented on Response superclass');
|
||||
}
|
||||
|
|
|
@ -80,9 +80,13 @@ export function main() {
|
|||
connection.mockRespond(baseResponse)
|
||||
}));
|
||||
|
||||
|
||||
it('should accept a fully-qualified request as its only parameter', () => {
|
||||
var req = new Request('https://google.com');
|
||||
backend.connections.subscribe(c => { expect(c.request.url).toBe('https://google.com'); });
|
||||
backend.connections.subscribe(c => {
|
||||
expect(c.request.url).toBe('https://google.com');
|
||||
c.mockRespond(new Response('Thank you'));
|
||||
});
|
||||
httpFactory(req).subscribe(() => {});
|
||||
});
|
||||
|
||||
|
@ -116,15 +120,16 @@ export function main() {
|
|||
|
||||
describe('Http', () => {
|
||||
describe('.request()', () => {
|
||||
it('should return an Observable', () => {
|
||||
expect(typeof http.request(url).subscribe).toBe('function');
|
||||
backend.resolveAllConnections();
|
||||
});
|
||||
it('should return an Observable',
|
||||
() => { expect(typeof http.request(url).subscribe).toBe('function'); });
|
||||
|
||||
|
||||
it('should accept a fully-qualified request as its only parameter', () => {
|
||||
var req = new Request('https://google.com');
|
||||
backend.connections.subscribe(c => { expect(c.request.url).toBe('https://google.com'); });
|
||||
backend.connections.subscribe(c => {
|
||||
expect(c.request.url).toBe('https://google.com');
|
||||
c.mockRespond(new Response('Thank you'));
|
||||
});
|
||||
http.request(req).subscribe(() => {});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import {Directive, ViewContainerRef, ProtoViewRef} from "angular2/angular2";
|
||||
|
||||
@Directive({selector: '[assign-local]', properties: ['localVariable: assignLocalTo']})
|
||||
export class LocalVariable {
|
||||
viewContainer: ViewContainerRef;
|
||||
protoViewRef: ProtoViewRef;
|
||||
view: any;
|
||||
constructor(viewContainer: ViewContainerRef, protoViewRef: ProtoViewRef) {
|
||||
this.viewContainer = viewContainer;
|
||||
this.protoViewRef = protoViewRef;
|
||||
}
|
||||
|
||||
set localVariable(exp) {
|
||||
if (!this.viewContainer.length) {
|
||||
this.view = this.viewContainer.create(this.protoViewRef);
|
||||
}
|
||||
|
||||
this.view.setLocal("$implicit", exp);
|
||||
}
|
||||
}
|
|
@ -1,30 +1,21 @@
|
|||
import {bootstrap, Component, View, NgFor, NgIf, Inject} from 'angular2/angular2';
|
||||
import {httpInjectables} from 'angular2/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';
|
||||
import {bootstrap, Component, View, NgFor, Inject} from 'angular2/angular2';
|
||||
import {Http, httpInjectables} from 'angular2/http';
|
||||
|
||||
@Component({selector: 'http-app', appInjector: [httpInjectables]})
|
||||
@Component({selector: 'http-app'})
|
||||
@View({
|
||||
directives: [NgFor, NgIf, LocalVariable],
|
||||
directives: [NgFor],
|
||||
template: `
|
||||
<h1>people</h1>
|
||||
<div *assign-local="#unwrappedPeople to people | rx">
|
||||
<ul *ng-if="unwrappedPeople" class="people">
|
||||
<li *ng-for="#person of unwrappedPeople">
|
||||
hello, {{person.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<span *ng-if="!unwrappedPeople">
|
||||
Fetching people...
|
||||
</span>
|
||||
</div>
|
||||
<ul class="people">
|
||||
<li *ng-for="#person of people">
|
||||
hello, {{person.name}}
|
||||
</li>
|
||||
</ul>
|
||||
`
|
||||
})
|
||||
export class HttpCmp {
|
||||
people: Rx.Observable<Object>;
|
||||
constructor(@Inject(HttpFactory) http: IHttp) {
|
||||
this.people = http('./people.json').map(res => res.json());
|
||||
people: Object;
|
||||
constructor(http: Http) {
|
||||
http.get('./people.json').map(res => res.json()).subscribe(people => this.people = people);
|
||||
}
|
||||
}
|
|
@ -2,29 +2,13 @@
|
|||
|
||||
import {
|
||||
bootstrap,
|
||||
ElementRef,
|
||||
Component,
|
||||
Directive,
|
||||
View,
|
||||
Injectable,
|
||||
NgFor,
|
||||
NgIf,
|
||||
Inject
|
||||
} from 'angular2/angular2';
|
||||
import {bind} from 'angular2/di';
|
||||
import {PipeRegistry, defaultPipes} from 'angular2/change_detection';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabilities';
|
||||
import {httpBindings} from 'angular2/http';
|
||||
import {Http} 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';
|
||||
import {RxPipeFactory} from './rx_pipe';
|
||||
import {httpInjectables} from 'angular2/http';
|
||||
import {HttpCmp} from './http_comp';
|
||||
|
||||
export function main() {
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||
defaultPipes.rx = [new RxPipeFactory()] bootstrap(
|
||||
HttpCmp, [bind(PipeRegistry).toValue(new PipeRegistry(defaultPipes))]);
|
||||
bootstrap(HttpCmp, [httpInjectables]);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
import {HttpCmp} from './http_comp';
|
||||
import {bootstrap} from 'angular2/angular2';
|
||||
import {bind} from 'angular2/di';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabilities';
|
||||
import {PipeRegistry, defaultPipes} from 'angular2/change_detection';
|
||||
import {RxPipeFactory} from './rx_pipe';
|
||||
import {httpInjectables} from 'angular2/http';
|
||||
|
||||
|
||||
export function main() {
|
||||
// This entry point is not transformed and exists for testing purposes.
|
||||
// See index.js for an explanation.
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||
defaultPipes.rx = [new RxPipeFactory()] bootstrap(
|
||||
HttpCmp, [bind(PipeRegistry).toValue(new PipeRegistry(defaultPipes))]);
|
||||
bootstrap(HttpCmp, [httpInjectables]);
|
||||
}
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
/// <reference path="../../../angular2/typings/rx/rx.all.d.ts" />
|
||||
|
||||
import {isBlank, isPresent, CONST} from 'angular2/src/facade/lang';
|
||||
import {Observable, ObservableWrapper} from 'angular2/src/facade/async';
|
||||
import {Pipe, WrappedValue, PipeFactory} from 'angular2/src/change_detection/pipes/pipe';
|
||||
import {ObservablePipe} from 'angular2/src/change_detection/pipes/observable_pipe';
|
||||
import {ChangeDetectorRef} from 'angular2/src/change_detection/change_detector_ref';
|
||||
import * as Rx from 'rx';
|
||||
|
||||
export class RxPipe extends ObservablePipe {
|
||||
supports(obs): boolean {
|
||||
if (Rx.hasOwnProperty('default')) {
|
||||
return obs instanceof (<any>Rx).default.Rx.Observable;
|
||||
} else {
|
||||
return obs instanceof <any>Rx.Observable;
|
||||
}
|
||||
}
|
||||
|
||||
_subscribe(obs): void {
|
||||
this._observable = obs;
|
||||
this._subscription =
|
||||
(<any>obs).subscribe(value => {this._updateLatestValue(value)}, e => { throw e; });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a factory for [ObervablePipe].
|
||||
*
|
||||
* @exportedAs angular2/pipes
|
||||
*/
|
||||
@CONST()
|
||||
export class RxPipeFactory extends PipeFactory {
|
||||
constructor() { super(); }
|
||||
|
||||
supports(obs): boolean { return obs instanceof (<any>Rx).default.Rx.Observable }
|
||||
|
||||
create(cdRef): Pipe { return new RxPipe(cdRef); }
|
||||
}
|
Loading…
Reference in New Issue