refactor(async): refactor EventEmitter

Refactor EventEmitter and Async Facade to match ES7 Observable semantics, properly use RxJS typedefs, make EventEmitter inherit from RxJS Subject. Closes #4149.

BREAKING CHANGE:
- consumers of EventEmitter no longer need to call .toRx()
- EventEmitter is now generic and requires a type - e.g. `EventEmitter<string>`
- EventEmitter and Observable now use the `.subscribe(generatorOrNext, error, complete)` method instead of `.observer(generator)`
- ObservableWrapper uses `callNext/callError/callComplete` instead of `callNext/callThrow/callReturn`
This commit is contained in:
Rob Wormald
2015-10-24 18:48:43 -07:00
parent 72e65d6797
commit ca3986f31d
35 changed files with 341 additions and 113 deletions

View File

@ -89,7 +89,7 @@ class ParentComp {
@Directive({selector: 'custom-emitter', outputs: ['myevent']})
@Injectable()
class CustomEmitter {
myevent: EventEmitter;
myevent: EventEmitter<any>;
constructor() { this.myevent = new EventEmitter(); }
}

View File

@ -16,7 +16,7 @@ import {ObservableWrapper, EventEmitter, PromiseWrapper} from 'angular2/src/core
export function main() {
describe('EventEmitter', () => {
var emitter: EventEmitter;
var emitter: EventEmitter<any>;
beforeEach(() => { emitter = new EventEmitter(); });
@ -34,18 +34,18 @@ export function main() {
expect(error).toEqual("Boom");
async.done();
});
ObservableWrapper.callThrow(emitter, "Boom");
ObservableWrapper.callError(emitter, "Boom");
}));
it("should work when no throw callback is provided", inject([AsyncTestCompleter], (async) => {
ObservableWrapper.subscribe(emitter, (_) => {}, (_) => { async.done(); });
ObservableWrapper.callThrow(emitter, "Boom");
ObservableWrapper.callError(emitter, "Boom");
}));
it("should call the return callback", inject([AsyncTestCompleter], (async) => {
ObservableWrapper.subscribe(emitter, (_) => {}, (_) => {}, () => { async.done(); });
ObservableWrapper.callReturn(emitter);
ObservableWrapper.callComplete(emitter);
}));
it("should subscribe to the wrapper asynchronously", () => {

View File

@ -907,7 +907,7 @@ class WrappedValue implements ControlValueAccessor {
@Component({selector: "my-input", template: ''})
class MyInput implements ControlValueAccessor {
@Output('change') onChange: EventEmitter = new EventEmitter();
@Output('change') onChange: EventEmitter<any> = new EventEmitter();
value: string;
constructor(cd: NgControl) { cd.valueAccessor = this; }

View File

@ -133,7 +133,7 @@ export function main() {
if (!IS_DART) {
it("should update set errors and status before emitting an event",
inject([AsyncTestCompleter], (async) => {
c.valueChanges.toRx().subscribe(value => {
c.valueChanges.subscribe(value => {
expect(c.valid).toEqual(false);
expect(c.errors).toEqual({"required": true});
async.done();

View File

@ -1976,7 +1976,7 @@ class DoublePipe implements PipeTransform {
@Injectable()
class DirectiveEmitingEvent {
msg: string;
event: EventEmitter;
event: EventEmitter<any>;
constructor() {
this.msg = '';
@ -2002,7 +2002,7 @@ class DirectiveUpdatingHostProperties {
@Directive({selector: '[update-host-actions]', host: {'@setAttr': 'setAttribute'}})
@Injectable()
class DirectiveUpdatingHostActions {
setAttr: EventEmitter;
setAttr: EventEmitter<any>;
constructor() { this.setAttr = new EventEmitter(); }

View File

@ -486,7 +486,12 @@ var NG_API = [
'ErrorHandlingFn:dart',
'Output',
'Output.bindingPropertyName',
'EventEmitter',
/*
Dart Stream/EventEmitter
*/
'EventEmitter.add():dart',
'EventEmitter.addError():dart',
'EventEmitter.any():dart',
@ -513,24 +518,215 @@ var NG_API = [
'EventEmitter.length:dart',
'EventEmitter.listen():dart',
'EventEmitter.map():dart',
'EventEmitter.next():js',
'EventEmitter.observer():js',
'EventEmitter.pipe():dart',
'EventEmitter.reduce():dart',
'EventEmitter.return():js',
'EventEmitter.single:dart',
'EventEmitter.singleWhere():dart',
'EventEmitter.skip():dart',
'EventEmitter.skipWhile():dart',
'EventEmitter.take():dart',
'EventEmitter.takeWhile():dart',
'EventEmitter.throw():js',
'EventEmitter.timeout():dart',
'EventEmitter.toList():dart',
'EventEmitter.toRx():js',
'EventEmitter.toSet():dart',
'EventEmitter.transform():dart',
'EventEmitter.where():dart',
/*
RxJS API - may need to maintain as RxJS evolves
*/
'EventEmitter.mapTo():js',
'EventEmitter.next():js',
'EventEmitter.materialize():js',
'EventEmitter.merge():js',
'EventEmitter.mergeAll():js',
'EventEmitter.mergeMap():js',
'EventEmitter.mergeMapTo():js',
'EventEmitter.multicast():js',
'EventEmitter.observeOn():js',
'EventEmitter.remove():js',
'EventEmitter.repeat():js',
'EventEmitter.retry():js',
'EventEmitter.retryWhen():js',
'EventEmitter.throttle():js',
'EventEmitter.toPromise():js',
'EventEmitter.window():js',
'EventEmitter.windowCount():js',
'EventEmitter.windowTime():js',
'EventEmitter.windowToggle():js',
'EventEmitter.windowWhen():js',
'EventEmitter.withLatestFrom():js',
'EventEmitter.zip():js',
'EventEmitter.zipAll():js',
'Observable:js',
'Observable#combineLatest():js',
'Observable#concat():js',
'Observable#create():js',
'Observable#defer():js',
'Observable#empty():js',
'Observable#forkJoin():js',
'Observable#from():js',
'Observable#fromArray():js',
'Observable#fromEvent():js',
'Observable#fromEventPattern():js',
'Observable#fromPromise():js',
'Observable#interval():js',
'Observable#merge():js',
'Observable#never():js',
'Observable#of():js',
'Observable#range():js',
'Observable#throw():js',
'Observable#timer():js',
'Observable#zip():js',
'Observable.buffer():js',
'Observable.bufferCount():js',
'Observable.bufferTime():js',
'Observable.bufferToggle():js',
'Observable.bufferWhen():js',
'Observable.catch():js',
'Observable.combineAll():js',
'Observable.combineLatest():js',
'Observable.concat():js',
'Observable.concatAll():js',
'Observable.concatMap():js',
'Observable.concatMapTo():js',
'Observable.count():js',
'Observable.debounce():js',
'Observable.debounceTime():js',
'Observable.defaultIfEmpty():js',
'Observable.delay():js',
'Observable.dematerialize():js',
'Observable.distinctUntilChanged():js',
'Observable.do():js',
'Observable.every():js',
'Observable.expand():js',
'Observable.filter():js',
'Observable.finally():js',
'Observable.first():js',
'Observable.flatMap():js',
'Observable.flatMapTo():js',
'Observable.forEach():js',
'Observable.groupBy():js',
'Observable.ignoreElements():js',
'Observable.last():js',
'Observable.lift():js',
'Observable.map():js',
'Observable.mapTo():js',
'Observable.materialize():js',
'Observable.merge():js',
'Observable.mergeAll():js',
'Observable.mergeMap():js',
'Observable.mergeMapTo():js',
'Observable.multicast():js',
'Observable.observeOn():js',
'Subject',
'Subject#combineLatest():js',
'Subject#concat():js',
'Subject#create():js',
'Subject#defer():js',
'Subject#empty():js',
'Subject#forkJoin():js',
'Subject#from():js',
'Subject#fromArray():js',
'Subject#fromEvent():js',
'Subject#fromEventPattern():js',
'Subject#fromPromise():js',
'Subject#interval():js',
'Subject#merge():js',
'Subject#never():js',
'Subject#of():js',
'Subject#range():js',
'Subject#throw():js',
'Subject#timer():js',
'Subject#zip():js',
'Subject.add():js',
'Subject.buffer():js',
'Subject.bufferCount():js',
'Subject.bufferTime():js',
'Subject.bufferToggle():js',
'Subject.bufferWhen():js',
'Subject.catch():js',
'Subject.combineAll():js',
'Subject.combineLatest():js',
'Subject.complete():js',
'Subject.concat():js',
'Subject.concatAll():js',
'Subject.concatMap():js',
'Subject.concatMapTo():js',
'Subject.count():js',
'Subject.debounce():js',
'Subject.debounceTime():js',
'Subject.defaultIfEmpty():js',
'Subject.delay():js',
'Subject.dematerialize():js',
'Subject.distinctUntilChanged():js',
'Subject.do():js',
'Subject.error():js',
'Subject.every():js',
'Subject.expand():js',
'Subject.filter():js',
'Subject.finally():js',
'Subject.first():js',
'Subject.flatMap():js',
'Subject.flatMapTo():js',
'Subject.forEach():js',
'Subject.groupBy():js',
'Subject.ignoreElements():js',
'Subject.last():js',
'Subject.lift():js',
'Subject.map():js',
'Subject.mapTo():js',
'Subject.materialize():js',
'Subject.merge():js',
'Subject.mergeAll():js',
'Subject.mergeMap():js',
'Subject.mergeMapTo():js',
'Subject.multicast():js',
'Subject.next():js',
'Subject.observeOn():js',
'Subject.partition():js',
'Subject.publish():js',
'Subject.publishBehavior():js',
'Subject.publishReplay():js',
'Subject.reduce():js',
'Subject.remove():js',
'Subject.repeat():js',
'Subject.retry():js',
'Subject.retryWhen():js',
'Subject.sample():js',
'Subject.sampleTime():js',
'Subject.scan():js',
'Subject.share():js',
'Subject.shareReplay():js',
'Subject.single():js',
'Subject.skip():js',
'Subject.skipUntil():js',
'Subject.startWith():js',
'Subject.subscribe():js',
'Subject.subscribeOn():js',
'Subject.switch():js',
'Subject.switchMap():js',
'Subject.switchMapTo():js',
'Subject.take():js',
'Subject.takeUntil():js',
'Subject.throttle():js',
'Subject.timeout():js',
'Subject.timeoutWith():js',
'Subject.toArray():js',
'Subject.toPromise():js',
'Subject.unsubscribe():js',
'Subject.window():js',
'Subject.windowCount():js',
'Subject.windowTime():js',
'Subject.windowToggle():js',
'Subject.windowWhen():js',
'Subject.withLatestFrom():js',
'Subject.zip():js',
'Subject.zipAll():js',
'OutputMetadata',
'OutputMetadata.bindingPropertyName',
'ExpressionChangedAfterItHasBeenCheckedException',
@ -839,8 +1035,6 @@ var NG_API = [
'NoProviderError.message=',
'NoProviderError.stackTrace',
'NumberPipe',
'Observable.observer():js',
'Observable:js',
'ObservableListDiff.check():dart',
'ObservableListDiff.collection:dart',
'ObservableListDiff.diff():dart',

View File

@ -51,7 +51,7 @@ import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver';
var cmpInstanceCount;
var log: string[];
var eventBus: EventEmitter;
var eventBus: EventEmitter<any>;
var completer: PromiseCompleter<any>;
export function main() {

View File

@ -4,7 +4,7 @@ import 'dart:core';
import 'dart:async';
import "package:angular2/src/core/facade/async.dart";
class MockEventEmitter extends EventEmitter {
class MockEventEmitter<T> extends EventEmitter<T> {
final controller = new StreamController.broadcast(sync: true);
@override

View File

@ -1,11 +1,11 @@
import {EventEmitter} from 'angular2/src/core/facade/async';
export class MockEventEmitter extends EventEmitter {
export class MockEventEmitter<T> extends EventEmitter<T> {
private _nextFns: Function[] = [];
constructor() { super(); }
observer(generator: any): any {
subscribe(generator: any): any {
this._nextFns.push(generator.next);
return new MockDisposable();
}

View File

@ -13,11 +13,11 @@ import {NgZone} from 'angular2/src/core/zone/ng_zone';
* Such that whatever goes into one's sink comes out the others source.
*/
export function createPairedMessageBuses(): PairedMessageBuses {
var firstChannels: {[key: string]: MockEventEmitter} = {};
var firstChannels: {[key: string]: MockEventEmitter<any>} = {};
var workerMessageBusSink = new MockMessageBusSink(firstChannels);
var uiMessageBusSource = new MockMessageBusSource(firstChannels);
var secondChannels: {[key: string]: MockEventEmitter} = {};
var secondChannels: {[key: string]: MockEventEmitter<any>} = {};
var uiMessageBusSink = new MockMessageBusSink(secondChannels);
var workerMessageBusSource = new MockMessageBusSource(secondChannels);
@ -30,7 +30,7 @@ export class PairedMessageBuses {
}
export class MockMessageBusSource implements MessageBusSource {
constructor(private _channels: {[key: string]: MockEventEmitter}) {}
constructor(private _channels: {[key: string]: MockEventEmitter<any>}) {}
initChannel(channel: string, runInZone = true) {
if (!StringMapWrapper.contains(this._channels, channel)) {
@ -38,7 +38,7 @@ export class MockMessageBusSource implements MessageBusSource {
}
}
from(channel: string): MockEventEmitter {
from(channel: string): MockEventEmitter<any> {
if (!StringMapWrapper.contains(this._channels, channel)) {
throw new BaseException(`${channel} is not set up. Did you forget to call initChannel?`);
}
@ -49,7 +49,7 @@ export class MockMessageBusSource implements MessageBusSource {
}
export class MockMessageBusSink implements MessageBusSink {
constructor(private _channels: {[key: string]: MockEventEmitter}) {}
constructor(private _channels: {[key: string]: MockEventEmitter<any>}) {}
initChannel(channel: string, runInZone = true) {
if (!StringMapWrapper.contains(this._channels, channel)) {
@ -57,7 +57,7 @@ export class MockMessageBusSink implements MessageBusSink {
}
}
to(channel: string): MockEventEmitter {
to(channel: string): MockEventEmitter<any> {
if (!StringMapWrapper.contains(this._channels, channel)) {
this._channels[channel] = new MockEventEmitter();
}
@ -79,9 +79,9 @@ export class MockMessageBus extends MessageBus {
this.source.initChannel(channel, runInZone);
}
to(channel: string): MockEventEmitter { return this.sink.to(channel); }
to(channel: string): MockEventEmitter<any> { return this.sink.to(channel); }
from(channel: string): MockEventEmitter { return this.source.from(channel); }
from(channel: string): MockEventEmitter<any> { return this.source.from(channel); }
attachToZone(zone: NgZone) {}
}