From 5b5ffe75d0d1a3ac6204114203d8d566d49e9c70 Mon Sep 17 00:00:00 2001 From: Jeff Cross Date: Tue, 9 Jun 2015 15:18:57 -0700 Subject: [PATCH] docs(Http): add docs for Http lib Fixes #2442 --- docs/public-docs-package/index.js | 3 +- modules/angular2/angular2.ts | 1 + modules/angular2/http.dart | 1 + modules/angular2/http.ts | 37 +++- .../src/http/backends/mock_backend.ts | 190 +++++++++++++++--- .../angular2/src/http/backends/xhr_backend.ts | 43 ++++ .../angular2/src/http/base_request_options.ts | 41 ++++ modules/angular2/src/http/headers.ts | 8 +- modules/angular2/src/http/http.ts | 163 ++++++++++----- modules/angular2/src/http/interfaces.ts | 21 ++ modules/angular2/src/http/static_request.ts | 47 +++-- modules/angular2/src/http/static_response.ts | 85 +++++++- modules/angular2/test/http/http_spec.ts | 17 +- .../src/http/assign_local_directive.ts | 20 -- modules/examples/src/http/http_comp.ts | 33 ++- modules/examples/src/http/index.ts | 20 +- modules/examples/src/http/index_dynamic.ts | 8 +- modules/examples/src/http/rx_pipe.ts | 38 ---- 18 files changed, 558 insertions(+), 218 deletions(-) create mode 100644 modules/angular2/http.dart delete mode 100644 modules/examples/src/http/assign_local_directive.ts delete mode 100644 modules/examples/src/http/rx_pipe.ts diff --git a/docs/public-docs-package/index.js b/docs/public-docs-package/index.js index 070076c221..44a0f3776a 100644 --- a/docs/public-docs-package/index.js +++ b/docs/public-docs-package/index.js @@ -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' ]; diff --git a/modules/angular2/angular2.ts b/modules/angular2/angular2.ts index 53db8d8359..ae3df8692c 100644 --- a/modules/angular2/angular2.ts +++ b/modules/angular2/angular2.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'; diff --git a/modules/angular2/http.dart b/modules/angular2/http.dart new file mode 100644 index 0000000000..c5103b3117 --- /dev/null +++ b/modules/angular2/http.dart @@ -0,0 +1 @@ +library angular2.http; \ No newline at end of file diff --git a/modules/angular2/http.ts b/modules/angular2/http.ts index 5491226cfb..0e542ecb03 100644 --- a/modules/angular2/http.ts +++ b/modules/angular2/http.ts @@ -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 = [ bind(BrowserXHR) .toValue(BrowserXHR), diff --git a/modules/angular2/src/http/backends/mock_backend.ts b/modules/angular2/src/http/backends/mock_backend.ts index 9d370f2130..0902ff897f 100644 --- a/modules/angular2/src/http/backends/mock_backend.ts +++ b/modules/angular2/src/http/backends/mock_backend.ts @@ -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; - - /** - * 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; constructor(req: Request) { - // State if (Rx.hasOwnProperty('default')) { this.response = new ((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; - connectionsArray: Array; - pendingConnections: Rx.Observable; +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; + + /** + * 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; + /** + * [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; constructor() { + var Observable; this.connectionsArray = []; if (Rx.hasOwnProperty('default')) { this.connections = new (Rx).default.Rx.Subject(); + Observable = (Rx).default.Rx.Observable; } else { - this.connections = new Rx.Subject(); + this.connections = new Rx.Subject(); + 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; } diff --git a/modules/angular2/src/http/backends/xhr_backend.ts b/modules/angular2/src/http/backends/xhr_backend.ts index 258d8505a8..e13324a45f 100644 --- a/modules/angular2/src/http/backends/xhr_backend.ts +++ b/modules/angular2/src/http/backends/xhr_backend.ts @@ -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; readyState: ReadyStates; private _xhr; @@ -20,6 +33,7 @@ export class XHRConnection implements Connection { this.response = new Rx.Subject(); } 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) {} diff --git a/modules/angular2/src/http/base_request_options.ts b/modules/angular2/src/http/base_request_options.ts index 345ce21244..5c8d832ec7 100644 --- a/modules/angular2/src/http/base_request_options.ts +++ b/modules/angular2/src/http/base_request_options.ts @@ -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(); } diff --git a/modules/angular2/src/http/headers.ts b/modules/angular2/src/http/headers.ts index 88efcae7e3..34ab615182 100644 --- a/modules/angular2/src/http/headers.ts +++ b/modules/angular2/src/http/headers.ts @@ -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>; constructor(headers?: Headers | Object) { diff --git a/modules/angular2/src/http/http.ts b/modules/angular2/src/http/http.ts index fc1c585329..5731ee7020 100644 --- a/modules/angular2/src/http/http.ts +++ b/modules/angular2/src/http/http.ts @@ -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: ` - *
    - *
  • - * hello, {{person.name}} - *
  • - *
- * ` - * }) - * 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 >(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 { 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') { diff --git a/modules/angular2/src/http/interfaces.ts b/modules/angular2/src/http/interfaces.ts index dc4c3292f5..d3872c3bfe 100644 --- a/modules/angular2/src/http/interfaces.ts +++ b/modules/angular2/src/http/interfaces.ts @@ -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 } diff --git a/modules/angular2/src/http/static_request.ts b/modules/angular2/src/http/static_request.ts index bbc453f401..0be3ecd60c 100644 --- a/modules/angular2/src/http/static_request.ts +++ b/modules/angular2/src/http/static_request.ts @@ -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() : ''; } } diff --git a/modules/angular2/src/http/static_response.ts b/modules/angular2/src/http/static_response.ts index d4305e2ced..740e55c62d 100644 --- a/modules/angular2/src/http/static_response.ts +++ b/modules/angular2/src/http/static_response.ts @@ -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; @@ -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 this.body; - } else if (isString(this.body)) { - return global.JSON.parse(this.body); + if (isJsObject(this._body)) { + return this._body; + } else if (isString(this._body)) { + return global.JSON.parse(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'); } diff --git a/modules/angular2/test/http/http_spec.ts b/modules/angular2/test/http/http_spec.ts index 97d599a3f4..8083eda9db 100644 --- a/modules/angular2/test/http/http_spec.ts +++ b/modules/angular2/test/http/http_spec.ts @@ -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(() => {}); }); }); diff --git a/modules/examples/src/http/assign_local_directive.ts b/modules/examples/src/http/assign_local_directive.ts deleted file mode 100644 index 1ad43c8e37..0000000000 --- a/modules/examples/src/http/assign_local_directive.ts +++ /dev/null @@ -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); - } -} diff --git a/modules/examples/src/http/http_comp.ts b/modules/examples/src/http/http_comp.ts index 321fc06010..7ca8b09bfb 100644 --- a/modules/examples/src/http/http_comp.ts +++ b/modules/examples/src/http/http_comp.ts @@ -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: `

people

-
-
    -
  • - hello, {{person.name}} -
  • -
- - Fetching people... - -
+
    +
  • + hello, {{person.name}} +
  • +
` }) export class HttpCmp { - people: Rx.Observable; - 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); } } \ No newline at end of file diff --git a/modules/examples/src/http/index.ts b/modules/examples/src/http/index.ts index 32a97ea6f2..f2a25ad79c 100644 --- a/modules/examples/src/http/index.ts +++ b/modules/examples/src/http/index.ts @@ -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]); } diff --git a/modules/examples/src/http/index_dynamic.ts b/modules/examples/src/http/index_dynamic.ts index e63372f815..1599ef841d 100644 --- a/modules/examples/src/http/index_dynamic.ts +++ b/modules/examples/src/http/index_dynamic.ts @@ -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]); } diff --git a/modules/examples/src/http/rx_pipe.ts b/modules/examples/src/http/rx_pipe.ts deleted file mode 100644 index 44d0654215..0000000000 --- a/modules/examples/src/http/rx_pipe.ts +++ /dev/null @@ -1,38 +0,0 @@ -/// - -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 (Rx).default.Rx.Observable; - } else { - return obs instanceof Rx.Observable; - } - } - - _subscribe(obs): void { - this._observable = obs; - this._subscription = - (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 (Rx).default.Rx.Observable } - - create(cdRef): Pipe { return new RxPipe(cdRef); } -}