docs(Http): add docs for Http lib

Fixes #2442
This commit is contained in:
Jeff Cross 2015-06-09 15:18:57 -07:00
parent e68e69e7e5
commit 5b5ffe75d0
18 changed files with 558 additions and 218 deletions

View File

@ -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'
];

View File

@ -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';

View File

@ -0,0 +1 @@
library angular2.http;

View File

@ -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),

View File

@ -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;
}

View File

@ -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) {}

View File

@ -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(); }

View File

@ -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) {

View File

@ -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') {

View File

@ -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> }

View File

@ -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() : ''; }
}

View File

@ -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');
}

View File

@ -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(() => {});
});
});

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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]);
}

View File

@ -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]);
}

View File

@ -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); }
}