feat(http): add basic http service
This implementation only works in JavaScript, while the Observable transpilation story gets worked out. Right now, the service just makes a simple request, and returns an Observable of Response. Additional functionality will be captured in separate issues. Fixes #2028
This commit is contained in:
parent
363b9ba415
commit
21568106b1
10
gulpfile.js
10
gulpfile.js
|
@ -845,6 +845,16 @@ gulp.task('!build/change_detect.dart', function(done) {
|
|||
proc.stdout.pipe(dartStream);
|
||||
});
|
||||
|
||||
// ------------
|
||||
// additional tasks for building examples
|
||||
gulp.task('build.http.example', function() {
|
||||
//Copy over people.json used in http example
|
||||
return gulp.src('modules/examples/src/http/people.json')
|
||||
.pipe(gulp.dest(CONFIG.dest.js.prod.es5 + '/examples/src/http/'))
|
||||
.pipe(gulp.dest(CONFIG.dest.js.dev.es5 + '/examples/src/http/'))
|
||||
.pipe(gulp.dest(CONFIG.dest.js.dart2js + '/examples/src/http/'));
|
||||
});
|
||||
|
||||
// ------------
|
||||
// angular material testing rules
|
||||
gulp.task('build.css.material', function() {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import {bind, Binding} from 'angular2/di';
|
||||
import {Http, HttpFactory} from './src/http/http';
|
||||
import {XHRBackend} from 'angular2/src/http/backends/xhr_backend';
|
||||
import {BrowserXHR} from 'angular2/src/http/backends/browser_xhr';
|
||||
|
||||
export {Http};
|
||||
export var httpInjectables: List<any> = [
|
||||
XHRBackend,
|
||||
bind(BrowserXHR).toValue(BrowserXHR),
|
||||
bind(Http).toFactory(HttpFactory, [XHRBackend])
|
||||
];
|
|
@ -107,5 +107,5 @@ export class EventEmitter extends Observable {
|
|||
|
||||
throw(error) { this._subject.onError(error); }
|
||||
|
||||
return (value) { this._subject.onCompleted(); }
|
||||
return (value?) { this._subject.onCompleted(); }
|
||||
}
|
||||
|
|
|
@ -238,6 +238,7 @@ export class ListWrapper {
|
|||
l.sort();
|
||||
}
|
||||
}
|
||||
static toString<T>(l: List<T>): string { return l.toString(); }
|
||||
}
|
||||
|
||||
export function isListLikeIterable(obj): boolean {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
library angular2.src.http.backends.browser_xhr;
|
||||
|
||||
/// import 'dart:html' show HttpRequest;
|
||||
/// import 'package:angular2/di.dart';
|
||||
|
||||
/// @Injectable()
|
||||
/// class BrowserXHR {
|
||||
/// factory BrowserXHR() => new HttpRequest();
|
||||
/// }
|
|
@ -0,0 +1,9 @@
|
|||
declare var window;
|
||||
|
||||
import {Injectable} from 'angular2/di';
|
||||
|
||||
// Make sure not to evaluate this in a non-browser environment!
|
||||
@Injectable()
|
||||
export class BrowserXHR {
|
||||
constructor() { return <any>(new window.XMLHttpRequest()); }
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
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 * 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.
|
||||
**/
|
||||
export class Connection {
|
||||
/**
|
||||
* 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.
|
||||
**/
|
||||
|
||||
readyState: ReadyStates;
|
||||
request: Request;
|
||||
response: Rx.Subject<Response>;
|
||||
|
||||
constructor(req: Request) {
|
||||
// State
|
||||
if (Rx.hasOwnProperty('default')) {
|
||||
this.response = new ((<any>Rx).default.Rx.Subject)();
|
||||
} else {
|
||||
this.response = new Rx.Subject<Response>();
|
||||
}
|
||||
|
||||
this.readyState = ReadyStates.OPEN;
|
||||
this.request = req;
|
||||
this.dispose = this.dispose.bind(this);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.readyState !== ReadyStates.DONE) {
|
||||
this.readyState = ReadyStates.CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a connection has been established.
|
||||
**/
|
||||
mockRespond(res: Response) {
|
||||
if (this.readyState >= ReadyStates.DONE) {
|
||||
throw new Error('Connection has already been resolved');
|
||||
}
|
||||
this.readyState = ReadyStates.DONE;
|
||||
this.response.onNext(res);
|
||||
this.response.onCompleted();
|
||||
}
|
||||
|
||||
mockDownload(res: Response) {
|
||||
this.downloadObserver.onNext(res);
|
||||
if (res.bytesLoaded === res.totalBytes) {
|
||||
this.downloadObserver.onCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
mockError(err?) {
|
||||
// Matches XHR semantics
|
||||
this.readyState = ReadyStates.DONE;
|
||||
this.response.onError(err);
|
||||
this.response.onCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class MockBackend {
|
||||
connections: Rx.Subject<Connection>;
|
||||
connectionsArray: Array<Connection>;
|
||||
pendingConnections: Rx.Observable<Connection>;
|
||||
constructor() {
|
||||
this.connectionsArray = [];
|
||||
if (Rx.hasOwnProperty('default')) {
|
||||
this.connections = new (<any>Rx).default.Rx.Subject();
|
||||
} else {
|
||||
this.connections = new Rx.Subject<Connection>();
|
||||
}
|
||||
this.connections.subscribe(connection => this.connectionsArray.push(connection));
|
||||
this.pendingConnections = this.connections.filter((c) => c.readyState < ReadyStates.DONE);
|
||||
}
|
||||
|
||||
verifyNoPendingRequests() {
|
||||
let pending = 0;
|
||||
this.pendingConnections.subscribe((c) => pending++);
|
||||
if (pending > 0) throw new Error(`${pending} pending connections to be resolved`);
|
||||
}
|
||||
|
||||
resolveAllConnections() { this.connections.subscribe((c) => c.readyState = 4); }
|
||||
|
||||
createConnection(req: Request) {
|
||||
if (!req || !(req instanceof Request)) {
|
||||
throw new Error(`createConnection requires an instance of Request, got ${req}`);
|
||||
}
|
||||
let connection = new Connection(req);
|
||||
this.connections.onNext(connection);
|
||||
return connection;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import {ConnectionBackend, Connection} from '../interfaces';
|
||||
import {ReadyStates, RequestMethods} from '../enums';
|
||||
import {Request} from '../static_request';
|
||||
import {Response} from '../static_response';
|
||||
import {Inject} from 'angular2/di';
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {BrowserXHR} from './browser_xhr';
|
||||
import * as Rx from 'rx';
|
||||
|
||||
export class XHRConnection implements Connection {
|
||||
request: Request;
|
||||
response: Rx.Subject<Response>;
|
||||
readyState: ReadyStates;
|
||||
private _xhr;
|
||||
constructor(req: Request, NativeConstruct: any) {
|
||||
this.request = req;
|
||||
if (Rx.hasOwnProperty('default')) {
|
||||
this.response = new (<any>Rx).default.Rx.Subject();
|
||||
} else {
|
||||
this.response = new Rx.Subject<Response>();
|
||||
}
|
||||
this._xhr = new NativeConstruct();
|
||||
this._xhr.open(RequestMethods[req.method], req.url);
|
||||
this._xhr.addEventListener(
|
||||
'load',
|
||||
() => {this.response.onNext(new Response(this._xhr.response || this._xhr.responseText))});
|
||||
// TODO(jeffbcross): make this more dynamic based on body type
|
||||
this._xhr.send(this.request.text());
|
||||
}
|
||||
|
||||
dispose(): void { this._xhr.abort(); }
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class XHRBackend implements ConnectionBackend {
|
||||
constructor(private _NativeConstruct: BrowserXHR) {}
|
||||
createConnection(request: Request): XHRConnection {
|
||||
return new XHRConnection(request, this._NativeConstruct);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import {CONST_EXPR, CONST} from 'angular2/src/facade/lang';
|
||||
import {Headers} from './headers';
|
||||
import {URLSearchParams} from './url_search_params';
|
||||
import {RequestModesOpts, RequestMethods, RequestCacheOpts, RequestCredentialsOpts} from './enums';
|
||||
import {RequestOptions} from './interfaces';
|
||||
import {Injectable} from 'angular2/di';
|
||||
|
||||
@Injectable()
|
||||
export class BaseRequestOptions implements RequestOptions {
|
||||
method: RequestMethods;
|
||||
headers: Headers;
|
||||
body: URLSearchParams | FormData | string;
|
||||
mode: RequestModesOpts;
|
||||
credentials: RequestCredentialsOpts;
|
||||
cache: RequestCacheOpts;
|
||||
|
||||
constructor() {
|
||||
this.method = RequestMethods.GET;
|
||||
this.mode = RequestModesOpts.Cors;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import {Headers} from './headers';
|
||||
import {ResponseTypes} from './enums';
|
||||
import {ResponseOptions} from './interfaces';
|
||||
|
||||
export class BaseResponseOptions implements ResponseOptions {
|
||||
status: number;
|
||||
headers: Headers | Object;
|
||||
statusText: string;
|
||||
type: ResponseTypes;
|
||||
url: string;
|
||||
|
||||
constructor({status = 200, statusText = 'Ok', type = ResponseTypes.Default,
|
||||
headers = new Headers(), url = ''}: ResponseOptions = {}) {
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.type = type;
|
||||
this.headers = headers;
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
export var baseResponseOptions = Object.freeze(new BaseResponseOptions());
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
export enum RequestModesOpts { Cors, NoCors, SameOrigin };
|
||||
|
||||
export enum RequestCacheOpts { Default, NoStore, Reload, NoCache, ForceCache, OnlyIfCached };
|
||||
|
||||
export enum RequestCredentialsOpts { Omit, SameOrigin, Include };
|
||||
|
||||
export enum RequestMethods { GET, POST, PUT, DELETE, OPTIONS, HEAD };
|
||||
|
||||
export enum ReadyStates { UNSENT, OPEN, HEADERS_RECEIVED, LOADING, DONE, CANCELLED };
|
||||
|
||||
export enum ResponseTypes { Basic, Cors, Default, Error, Opaque }
|
|
@ -0,0 +1,79 @@
|
|||
import {
|
||||
isPresent,
|
||||
isBlank,
|
||||
isJsObject,
|
||||
isType,
|
||||
StringWrapper,
|
||||
BaseException
|
||||
} from 'angular2/src/facade/lang';
|
||||
import {
|
||||
isListLikeIterable,
|
||||
List,
|
||||
Map,
|
||||
MapWrapper,
|
||||
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.
|
||||
|
||||
export class Headers {
|
||||
_headersMap: Map<string, List<string>>;
|
||||
constructor(headers?: Headers | Object) {
|
||||
if (isBlank(headers)) {
|
||||
this._headersMap = MapWrapper.create();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPresent((<Headers>headers)._headersMap)) {
|
||||
this._headersMap = (<Headers>headers)._headersMap;
|
||||
} else if (isJsObject(headers)) {
|
||||
this._headersMap = MapWrapper.createFromStringMap(headers);
|
||||
MapWrapper.forEach(this._headersMap, (v, k) => {
|
||||
if (!isListLikeIterable(v)) {
|
||||
var list = ListWrapper.create();
|
||||
ListWrapper.push(list, v);
|
||||
MapWrapper.set(this._headersMap, k, list);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
append(name: string, value: string): void {
|
||||
var list = MapWrapper.get(this._headersMap, name) || ListWrapper.create();
|
||||
ListWrapper.push(list, value);
|
||||
MapWrapper.set(this._headersMap, name, list);
|
||||
}
|
||||
|
||||
delete (name: string): void { MapWrapper.delete(this._headersMap, name); }
|
||||
|
||||
forEach(fn: Function) { return MapWrapper.forEach(this._headersMap, fn); }
|
||||
|
||||
get(header: string): string {
|
||||
return ListWrapper.first(MapWrapper.get(this._headersMap, header));
|
||||
}
|
||||
|
||||
has(header: string) { return MapWrapper.contains(this._headersMap, header); }
|
||||
|
||||
keys() { return MapWrapper.keys(this._headersMap); }
|
||||
|
||||
// TODO: this implementation seems wrong. create list then check if it's iterable?
|
||||
set(header: string, value: string | List<string>): void {
|
||||
var list = ListWrapper.create();
|
||||
if (!isListLikeIterable(value)) {
|
||||
ListWrapper.push(list, value);
|
||||
} else {
|
||||
ListWrapper.push(list, ListWrapper.toString((<List<string>>value)));
|
||||
}
|
||||
|
||||
MapWrapper.set(this._headersMap, header, list);
|
||||
}
|
||||
|
||||
values() { return MapWrapper.values(this._headersMap); }
|
||||
|
||||
getAll(header: string): Array<string> {
|
||||
return MapWrapper.get(this._headersMap, header) || ListWrapper.create();
|
||||
}
|
||||
|
||||
entries() { throw new BaseException('"entries" method is not implemented on Headers class'); }
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/// <reference path="../../typings/rx/rx.all.d.ts" />
|
||||
|
||||
import {Injectable} from 'angular2/src/di/decorators';
|
||||
import {RequestOptions, Connection} from './interfaces';
|
||||
import {Request} from './static_request';
|
||||
import {Response} from './static_response';
|
||||
import {XHRBackend} from './backends/xhr_backend';
|
||||
import {BaseRequestOptions} from './base_request_options';
|
||||
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
|
||||
*
|
||||
**/
|
||||
|
||||
// Abstract
|
||||
@Injectable()
|
||||
export class Http {
|
||||
}
|
||||
|
||||
var Observable;
|
||||
if (Rx.hasOwnProperty('default')) {
|
||||
Observable = (<any>Rx).default.Rx.Observable;
|
||||
} else {
|
||||
Observable = Rx.Observable;
|
||||
}
|
||||
export function HttpFactory(backend: XHRBackend, defaultOptions: BaseRequestOptions) {
|
||||
return function(url: string, options?: RequestOptions) {
|
||||
return <Rx.Observable<Response>>(Observable.create(observer => {
|
||||
var connection: Connection = backend.createConnection(new Request(url, options));
|
||||
var internalSubscription = connection.response.subscribe(observer);
|
||||
return () => {
|
||||
internalSubscription.dispose();
|
||||
connection.dispose();
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/// <reference path="../../typings/rx/rx.all.d.ts" />
|
||||
|
||||
import {
|
||||
ReadyStates,
|
||||
RequestModesOpts,
|
||||
RequestMethods,
|
||||
RequestCacheOpts,
|
||||
RequestCredentialsOpts,
|
||||
ResponseTypes
|
||||
} from './enums';
|
||||
import {Headers} from './headers';
|
||||
import {URLSearchParams} from './url_search_params';
|
||||
|
||||
export interface RequestOptions {
|
||||
method?: RequestMethods;
|
||||
headers?: Headers;
|
||||
body?: URLSearchParams | FormData | string;
|
||||
mode?: RequestModesOpts;
|
||||
credentials?: RequestCredentialsOpts;
|
||||
cache?: RequestCacheOpts;
|
||||
}
|
||||
|
||||
export interface Request {
|
||||
method: RequestMethods;
|
||||
mode: RequestModesOpts;
|
||||
credentials: RequestCredentialsOpts;
|
||||
}
|
||||
|
||||
export interface ResponseOptions {
|
||||
status?: number;
|
||||
statusText?: string;
|
||||
headers?: Headers | Object;
|
||||
type?: ResponseTypes;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
headers: Headers;
|
||||
ok: boolean;
|
||||
status: number;
|
||||
statusText: string;
|
||||
type: ResponseTypes;
|
||||
url: string;
|
||||
totalBytes: number;
|
||||
bytesLoaded: number;
|
||||
blob(): Blob;
|
||||
arrayBuffer(): ArrayBuffer;
|
||||
text(): string;
|
||||
json(): Object;
|
||||
}
|
||||
|
||||
export interface ConnectionBackend { createConnection(observer: any, config: Request): Connection; }
|
||||
|
||||
export interface Connection {
|
||||
readyState: ReadyStates;
|
||||
request: Request;
|
||||
response: Rx.Subject<Response>;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
// 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?: RequestOptions): Rx.Observable<Response> }
|
|
@ -0,0 +1,39 @@
|
|||
import {RequestMethods, RequestModesOpts, RequestCredentialsOpts} from './enums';
|
||||
import {URLSearchParams} from './url_search_params';
|
||||
import {RequestOptions, Request as IRequest} from './interfaces';
|
||||
import {Headers} from './headers';
|
||||
import {BaseException, RegExpWrapper} from 'angular2/src/facade/lang';
|
||||
|
||||
// TODO(jeffbcross): implement body accessors
|
||||
export class Request implements IRequest {
|
||||
method: RequestMethods;
|
||||
mode: RequestModesOpts;
|
||||
credentials: RequestCredentialsOpts;
|
||||
headers: Headers;
|
||||
/*
|
||||
* Non-Standard Properties
|
||||
*/
|
||||
// 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;
|
||||
|
||||
constructor(public url: string, {body, method = RequestMethods.GET, mode = RequestModesOpts.Cors,
|
||||
credentials = RequestCredentialsOpts.Omit,
|
||||
headers = new Headers()}: RequestOptions = {}) {
|
||||
this.body = body;
|
||||
// Defaults to 'GET', consistent with browser
|
||||
this.method = method;
|
||||
// Defaults to 'cors', consistent with browser
|
||||
// TODO(jeffbcross): implement behavior
|
||||
this.mode = mode;
|
||||
// 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() : ''; }
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import {Response as IResponse, ResponseOptions} from './interfaces';
|
||||
import {ResponseTypes} from './enums';
|
||||
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
|
||||
export class Response implements IResponse {
|
||||
type: ResponseTypes;
|
||||
ok: boolean;
|
||||
url: string;
|
||||
status: number;
|
||||
statusText: string;
|
||||
bytesLoaded: number;
|
||||
totalBytes: number;
|
||||
headers: Headers;
|
||||
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;
|
||||
this.type = type;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
blob(): Blob {
|
||||
throw new BaseException('"blob()" method not implemented on Response superclass');
|
||||
}
|
||||
|
||||
json(): JSON {
|
||||
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(); }
|
||||
|
||||
arrayBuffer(): ArrayBuffer {
|
||||
throw new BaseException('"arrayBuffer()" method not implemented on Response superclass');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import {isPresent, isBlank, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {Map, MapWrapper, List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
function paramParser(rawParams: string): Map<string, List<string>> {
|
||||
var map: Map<string, List<string>> = MapWrapper.create();
|
||||
var params: List<string> = StringWrapper.split(rawParams, '&');
|
||||
ListWrapper.forEach(params, (param: string) => {
|
||||
var split: List<string> = StringWrapper.split(param, '=');
|
||||
var key = ListWrapper.get(split, 0);
|
||||
var val = ListWrapper.get(split, 1);
|
||||
var list = MapWrapper.get(map, key) || ListWrapper.create();
|
||||
ListWrapper.push(list, val);
|
||||
MapWrapper.set(map, key, list);
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
export class URLSearchParams {
|
||||
paramsMap: Map<string, List<string>>;
|
||||
constructor(public rawParams: string) { this.paramsMap = paramParser(rawParams); }
|
||||
|
||||
has(param: string): boolean { return MapWrapper.contains(this.paramsMap, param); }
|
||||
|
||||
get(param: string): string { return ListWrapper.first(MapWrapper.get(this.paramsMap, param)); }
|
||||
|
||||
getAll(param: string): List<string> {
|
||||
return MapWrapper.get(this.paramsMap, param) || ListWrapper.create();
|
||||
}
|
||||
|
||||
append(param: string, val: string): void {
|
||||
var list = MapWrapper.get(this.paramsMap, param) || ListWrapper.create();
|
||||
ListWrapper.push(list, val);
|
||||
MapWrapper.set(this.paramsMap, param, list);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
var paramsList = ListWrapper.create();
|
||||
MapWrapper.forEach(this.paramsMap, (values, k) => {
|
||||
ListWrapper.forEach(values, v => { ListWrapper.push(paramsList, k + '=' + v); });
|
||||
});
|
||||
return ListWrapper.join(paramsList, '&');
|
||||
}
|
||||
|
||||
delete (param): void { MapWrapper.delete(this.paramsMap, param); }
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
SpyObject
|
||||
} from 'angular2/test_lib';
|
||||
import {BrowserXHR} from 'angular2/src/http/backends/browser_xhr';
|
||||
import {XHRConnection, XHRBackend} from 'angular2/src/http/backends/xhr_backend';
|
||||
import {bind, Injector} from 'angular2/di';
|
||||
import {Request} from 'angular2/src/http/static_request';
|
||||
|
||||
var abortSpy;
|
||||
var sendSpy;
|
||||
var openSpy;
|
||||
var addEventListenerSpy;
|
||||
|
||||
class MockBrowserXHR extends SpyObject {
|
||||
abort: any;
|
||||
send: any;
|
||||
open: any;
|
||||
addEventListener: any;
|
||||
response: any;
|
||||
responseText: string;
|
||||
constructor() {
|
||||
super();
|
||||
this.abort = abortSpy = this.spy('abort');
|
||||
this.send = sendSpy = this.spy('send');
|
||||
this.open = openSpy = this.spy('open');
|
||||
this.addEventListener = addEventListenerSpy = this.spy('addEventListener');
|
||||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('XHRBackend', () => {
|
||||
var backend;
|
||||
var sampleRequest;
|
||||
var constructSpy = new SpyObject();
|
||||
|
||||
beforeEach(() => {
|
||||
var injector =
|
||||
Injector.resolveAndCreate([bind(BrowserXHR).toValue(MockBrowserXHR), XHRBackend]);
|
||||
backend = injector.get(XHRBackend);
|
||||
sampleRequest = new Request('https://google.com');
|
||||
});
|
||||
|
||||
it('should create a connection',
|
||||
() => { expect(() => backend.createConnection(sampleRequest)).not.toThrow(); });
|
||||
|
||||
|
||||
describe('XHRConnection', () => {
|
||||
it('should call abort when disposed', () => {
|
||||
var connection = new XHRConnection(sampleRequest, MockBrowserXHR);
|
||||
connection.dispose();
|
||||
expect(abortSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
it('should automatically call open with method and url', () => {
|
||||
new XHRConnection(sampleRequest, MockBrowserXHR);
|
||||
expect(openSpy).toHaveBeenCalledWith('GET', sampleRequest.url);
|
||||
});
|
||||
|
||||
|
||||
it('should automatically call send on the backend with request body', () => {
|
||||
var body = 'Some body to love';
|
||||
var request = new Request('https://google.com', {body: body});
|
||||
var connection = new XHRConnection(request, MockBrowserXHR);
|
||||
expect(sendSpy).toHaveBeenCalledWith(body);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import {Headers} from 'angular2/src/http/headers';
|
||||
import {Map} from 'angular2/src/facade/collection';
|
||||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit
|
||||
} from 'angular2/test_lib';
|
||||
|
||||
export function main() {
|
||||
describe('Headers', () => {
|
||||
it('should conform to spec', () => {
|
||||
// Examples borrowed from https://developer.mozilla.org/en-US/docs/Web/API/Headers/Headers
|
||||
// Spec at https://fetch.spec.whatwg.org/#dom-headers
|
||||
var myHeaders = new Headers(); // Currently empty
|
||||
myHeaders.append('Content-Type', 'image/jpeg');
|
||||
expect(myHeaders.get('Content-Type')).toBe('image/jpeg');
|
||||
var httpHeaders = {
|
||||
'Content-Type': 'image/jpeg',
|
||||
'Accept-Charset': 'utf-8',
|
||||
'X-My-Custom-Header': 'Zeke are cool'
|
||||
};
|
||||
var myHeaders = new Headers(httpHeaders);
|
||||
var secondHeadersObj = new Headers(myHeaders);
|
||||
expect(secondHeadersObj.get('Content-Type')).toBe('image/jpeg');
|
||||
});
|
||||
|
||||
|
||||
describe('initialization', () => {
|
||||
it('should create a private headersMap map',
|
||||
() => { expect(new Headers()._headersMap).toBeAnInstanceOf(Map); });
|
||||
|
||||
|
||||
it('should merge values in provided dictionary', () => {
|
||||
var headers = new Headers({foo: 'bar'});
|
||||
expect(headers.get('foo')).toBe('bar');
|
||||
expect(headers.getAll('foo')).toEqual(['bar']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('.set()', () => {
|
||||
it('should clear all values and re-set for the provided key', () => {
|
||||
var headers = new Headers({foo: 'bar'});
|
||||
expect(headers.get('foo')).toBe('bar');
|
||||
expect(headers.getAll('foo')).toEqual(['bar']);
|
||||
headers.set('foo', 'baz');
|
||||
expect(headers.get('foo')).toBe('baz');
|
||||
expect(headers.getAll('foo')).toEqual(['baz']);
|
||||
});
|
||||
|
||||
|
||||
it('should convert input array to string', () => {
|
||||
var headers = new Headers();
|
||||
headers.set('foo', ['bar', 'baz']);
|
||||
expect(headers.get('foo')).toBe('bar,baz');
|
||||
expect(headers.getAll('foo')).toEqual(['bar,baz']);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit,
|
||||
SpyObject
|
||||
} from 'angular2/test_lib';
|
||||
import {Http, HttpFactory} from 'angular2/src/http/http';
|
||||
import {XHRBackend} from 'angular2/src/http/backends/xhr_backend';
|
||||
import {httpInjectables} from 'angular2/http';
|
||||
import {Injector, bind} from 'angular2/di';
|
||||
import {MockBackend} from 'angular2/src/http/backends/mock_backend';
|
||||
import {Response} from 'angular2/src/http/static_response';
|
||||
import {ReadyStates} from 'angular2/src/http/enums';
|
||||
|
||||
class SpyObserver extends SpyObject {
|
||||
onNext: Function;
|
||||
onError: Function;
|
||||
onCompleted: Function;
|
||||
constructor() {
|
||||
super();
|
||||
this.onNext = this.spy('onNext');
|
||||
this.onError = this.spy('onError');
|
||||
this.onCompleted = this.spy('onCompleted');
|
||||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('http', () => {
|
||||
var url = 'http://foo.bar';
|
||||
var http;
|
||||
var injector;
|
||||
var backend: MockBackend;
|
||||
var baseResponse;
|
||||
var sampleObserver;
|
||||
beforeEach(() => {
|
||||
injector = Injector.resolveAndCreate([MockBackend, bind(Http).toFactory(HttpFactory, [MockBackend])]);
|
||||
http = injector.get(Http);
|
||||
backend = injector.get(MockBackend);
|
||||
baseResponse = new Response('base response');
|
||||
sampleObserver = new SpyObserver();
|
||||
});
|
||||
|
||||
afterEach(() => { /*backend.verifyNoPendingRequests();*/ });
|
||||
|
||||
|
||||
it('should return an Observable', () => {
|
||||
expect(typeof http(url).subscribe).toBe('function');
|
||||
backend.resolveAllConnections();
|
||||
});
|
||||
|
||||
|
||||
it('should perform a get request for given url if only passed a string',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
var connection;
|
||||
backend.connections.subscribe((c) => connection = c);
|
||||
var subscription = http('http://basic.connection')
|
||||
.subscribe(res => {
|
||||
expect(res.text()).toBe('base response');
|
||||
async.done();
|
||||
});
|
||||
connection.mockRespond(baseResponse)
|
||||
}));
|
||||
|
||||
|
||||
it('should perform a get request for given url if passed a ConnectionConfig instance',
|
||||
inject([AsyncTestCompleter], async => {
|
||||
var connection;
|
||||
backend.connections.subscribe((c) => connection = c);
|
||||
http('http://basic.connection', {method: ReadyStates.UNSENT})
|
||||
.subscribe(res => {
|
||||
expect(res.text()).toBe('base response');
|
||||
async.done();
|
||||
});
|
||||
connection.mockRespond(baseResponse)
|
||||
}));
|
||||
|
||||
|
||||
it('should perform a get request for given url if passed a dictionary',
|
||||
inject([AsyncTestCompleter], async => {
|
||||
var connection;
|
||||
backend.connections.subscribe((c) => connection = c);
|
||||
http(url, {method: ReadyStates.UNSENT})
|
||||
.subscribe(res => {
|
||||
expect(res.text()).toBe('base response');
|
||||
async.done();
|
||||
});
|
||||
connection.mockRespond(baseResponse)
|
||||
}));
|
||||
});
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import {
|
||||
AsyncTestCompleter,
|
||||
beforeEach,
|
||||
ddescribe,
|
||||
describe,
|
||||
expect,
|
||||
iit,
|
||||
inject,
|
||||
it,
|
||||
xit
|
||||
} from 'angular2/test_lib';
|
||||
import {URLSearchParams} from 'angular2/src/http/url_search_params';
|
||||
|
||||
export function main() {
|
||||
describe('URLSearchParams', () => {
|
||||
it('should conform to spec', () => {
|
||||
var paramsString = "q=URLUtils.searchParams&topic=api";
|
||||
var searchParams = new URLSearchParams(paramsString);
|
||||
|
||||
// Tests borrowed from example at
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
|
||||
// Compliant with spec described at https://url.spec.whatwg.org/#urlsearchparams
|
||||
expect(searchParams.has("topic")).toBe(true);
|
||||
expect(searchParams.has("foo")).toBe(false);
|
||||
expect(searchParams.get("topic")).toBe("api");
|
||||
expect(searchParams.getAll("topic")).toEqual(["api"]);
|
||||
expect(searchParams.get("foo")).toBe(null);
|
||||
searchParams.append("topic", "webdev");
|
||||
expect(searchParams.getAll("topic")).toEqual(["api", "webdev"]);
|
||||
expect(searchParams.toString()).toBe("q=URLUtils.searchParams&topic=api&topic=webdev");
|
||||
searchParams.delete("topic");
|
||||
expect(searchParams.toString()).toBe("q=URLUtils.searchParams");
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/// <reference path="../../../angular2/typings/jasmine/jasmine.d.ts" />
|
||||
|
||||
import {verifyNoBrowserErrors} from 'angular2/src/test_lib/e2e_util';
|
||||
|
||||
describe('http', function() {
|
||||
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
describe('fetching', function() {
|
||||
var URL = 'examples/src/http/index.html';
|
||||
|
||||
it('should fetch and display people', function() {
|
||||
browser.get(URL);
|
||||
|
||||
expect(getComponentText('http-app', '.people')).toEqual('hello, Jeff');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getComponentText(selector, innerSelector) {
|
||||
return browser.executeScript('return document.querySelector("' + selector + '").querySelector("' +
|
||||
innerSelector + '").textContent.trim()');
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import {bootstrap, Component, View, NgFor, NgIf, Inject} from 'angular2/angular2';
|
||||
import {httpInjectables} 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';
|
||||
|
||||
@Component({selector: 'http-app', appInjector: [httpInjectables]})
|
||||
@View({
|
||||
directives: [NgFor, NgIf, LocalVariable],
|
||||
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>
|
||||
`
|
||||
})
|
||||
export class HttpCmp {
|
||||
people: Rx.Observable<Object>;
|
||||
constructor(@Inject(Http) http: IHttp) {
|
||||
this.people = http('./people.json').map(res => res.json());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<title>Hello Http</title>
|
||||
<body>
|
||||
<http-app>
|
||||
Loading...
|
||||
</http-app>
|
||||
|
||||
$SCRIPTS$
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,30 @@
|
|||
/// <reference path="../../../angular2/typings/rx/rx.all.d.ts" />
|
||||
|
||||
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 {HttpCmp} from './http_comp';
|
||||
|
||||
export function main() {
|
||||
reflector.reflectionCapabilities = new ReflectionCapabilities();
|
||||
defaultPipes.rx = [new RxPipeFactory()] bootstrap(
|
||||
HttpCmp, [bind(PipeRegistry).toValue(new PipeRegistry(defaultPipes))]);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<title>Angular 2.0 Http (Reflection)</title>
|
||||
<body>
|
||||
<http-app>
|
||||
Loading...
|
||||
</http-app>
|
||||
|
||||
$SCRIPTS$
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,15 @@
|
|||
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';
|
||||
|
||||
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))]);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
[{"name":"Jeff"}]
|
|
@ -0,0 +1,38 @@
|
|||
/// <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); }
|
||||
}
|
|
@ -5,10 +5,13 @@ config.baseUrl = 'http://localhost:8002/';
|
|||
|
||||
config.exclude.push(
|
||||
'dist/js/cjs/examples/e2e_test/sourcemap/sourcemap_spec.js',
|
||||
//TODO(jeffbcross): remove when http has been implemented for dart
|
||||
'dist/js/cjs/examples/e2e_test/http/http_spec.js',
|
||||
// TODO: remove this line when largetable dart has been added
|
||||
'dist/js/cjs/benchmarks_external/e2e_test/largetable_perf.js',
|
||||
'dist/js/cjs/benchmarks_external/e2e_test/polymer_tree_perf.js',
|
||||
'dist/js/cjs/benchmarks_external/e2e_test/react_tree_perf.js'
|
||||
|
||||
);
|
||||
|
||||
data.createBenchpressRunner({ lang: 'dart' });
|
||||
|
|
|
@ -16,6 +16,7 @@ function killServer () {
|
|||
serverPid=$!
|
||||
|
||||
./node_modules/.bin/gulp build.css.material&
|
||||
./node_modules/.bin/gulp build.http.example&
|
||||
|
||||
trap killServer EXIT
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ function killServer () {
|
|||
serverPid=$!
|
||||
|
||||
./node_modules/.bin/gulp build.css.material&
|
||||
./node_modules/.bin/gulp build.http.example&
|
||||
|
||||
trap killServer EXIT
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ const kServedPaths = [
|
|||
'examples/src/forms',
|
||||
'examples/src/gestures',
|
||||
'examples/src/hello_world',
|
||||
'examples/src/http',
|
||||
'examples/src/key_events',
|
||||
'examples/src/sourcemap',
|
||||
'examples/src/todo',
|
||||
|
|
|
@ -44,7 +44,16 @@ function stripModulePrefix(relativePath: string): string {
|
|||
|
||||
function getSourceTree() {
|
||||
// Transpile everything in 'modules' except for rtts_assertions.
|
||||
var tsInputTree = modulesFunnel(['**/*.js', '**/*.ts', '**/*.dart'], ['rtts_assert/**/*']);
|
||||
var tsInputTree = modulesFunnel(['**/*.js', '**/*.ts', '**/*.dart'],
|
||||
// TODO(jeffbcross): add http when lib supports dart
|
||||
[
|
||||
'rtts_assert/**/*',
|
||||
'examples/e2e_test/http/**/*',
|
||||
'examples/src/http/**/*',
|
||||
'angular2/src/http/**/*',
|
||||
'angular2/test/http/**/*',
|
||||
'angular2/http.ts'
|
||||
]);
|
||||
var transpiled = ts2dart(tsInputTree, {generateLibraryName: true, generateSourceMap: false});
|
||||
// Native sources, dart only examples, etc.
|
||||
var dartSrcs = modulesFunnel(['**/*.dart', '**/*.ng_meta.json', '**/css/**']);
|
||||
|
|
Loading…
Reference in New Issue