feat(view): changed event emitters to be observables

This commit is contained in:
vsavkin
2015-04-14 14:34:41 -07:00
parent 8b28e99373
commit 233cb0f96a
15 changed files with 353 additions and 318 deletions

View File

@ -3,3 +3,4 @@ export * from './core';
export * from './annotations'; export * from './annotations';
export * from './directives'; export * from './directives';
export * from './forms'; export * from './forms';
export {Observable, EventEmitter} from 'angular2/src/facade/async';

View File

@ -360,6 +360,30 @@ export class Directive extends Injectable {
*/ */
properties:any; // StringMap properties:any; // StringMap
/**
* Enumerates the set of emitted events.
*
* ## Syntax
*
* ```
* @Component({
* events: ['status-change']
* })
* class TaskComponent {
* statusChange:EventEmitter;
*
* constructor() {
* this.complete = new EventEmitter();
* }
*
* onComplete() {
* this.statusChange.next("completed");
* }
* }
* ```
*/
events:List<string>;
/** /**
* Specifies which DOM hostListeners a directive listens to. * Specifies which DOM hostListeners a directive listens to.
* *
@ -426,11 +450,13 @@ export class Directive extends Injectable {
constructor({ constructor({
selector, selector,
properties, properties,
events,
hostListeners, hostListeners,
lifecycle lifecycle
}:{ }:{
selector:string, selector:string,
properties:any, properties:any,
events:List,
hostListeners: any, hostListeners: any,
lifecycle:List lifecycle:List
}={}) }={})
@ -438,6 +464,7 @@ export class Directive extends Injectable {
super(); super();
this.selector = selector; this.selector = selector;
this.properties = properties; this.properties = properties;
this.events = events;
this.hostListeners = hostListeners; this.hostListeners = hostListeners;
this.lifecycle = lifecycle; this.lifecycle = lifecycle;
} }
@ -551,6 +578,7 @@ export class Component extends Directive {
constructor({ constructor({
selector, selector,
properties, properties,
events,
hostListeners, hostListeners,
injectables, injectables,
lifecycle, lifecycle,
@ -558,6 +586,7 @@ export class Component extends Directive {
}:{ }:{
selector:string, selector:string,
properties:Object, properties:Object,
events:List,
hostListeners:Object, hostListeners:Object,
injectables:List, injectables:List,
lifecycle:List, lifecycle:List,
@ -567,6 +596,7 @@ export class Component extends Directive {
super({ super({
selector: selector, selector: selector,
properties: properties, properties: properties,
events: events,
hostListeners: hostListeners, hostListeners: hostListeners,
lifecycle: lifecycle lifecycle: lifecycle
}); });
@ -634,12 +664,14 @@ export class DynamicComponent extends Directive {
constructor({ constructor({
selector, selector,
properties, properties,
events,
hostListeners, hostListeners,
injectables, injectables,
lifecycle lifecycle
}:{ }:{
selector:string, selector:string,
properties:Object, properties:Object,
events:List,
hostListeners:Object, hostListeners:Object,
injectables:List, injectables:List,
lifecycle:List lifecycle:List
@ -647,6 +679,7 @@ export class DynamicComponent extends Directive {
super({ super({
selector: selector, selector: selector,
properties: properties, properties: properties,
events: events,
hostListeners: hostListeners, hostListeners: hostListeners,
lifecycle: lifecycle lifecycle: lifecycle
}); });
@ -727,12 +760,14 @@ export class Decorator extends Directive {
constructor({ constructor({
selector, selector,
properties, properties,
events,
hostListeners, hostListeners,
lifecycle, lifecycle,
compileChildren = true, compileChildren = true,
}:{ }:{
selector:string, selector:string,
properties:any, properties:any,
events:List,
hostListeners:any, hostListeners:any,
lifecycle:List, lifecycle:List,
compileChildren:boolean compileChildren:boolean
@ -741,6 +776,7 @@ export class Decorator extends Directive {
super({ super({
selector: selector, selector: selector,
properties: properties, properties: properties,
events: events,
hostListeners: hostListeners, hostListeners: hostListeners,
lifecycle: lifecycle lifecycle: lifecycle
}); });
@ -846,17 +882,20 @@ export class Viewport extends Directive {
constructor({ constructor({
selector, selector,
properties, properties,
events,
hostListeners, hostListeners,
lifecycle lifecycle
}:{ }:{
selector:string, selector:string,
properties:any, properties:any,
events:List,
lifecycle:List lifecycle:List
}={}) }={})
{ {
super({ super({
selector: selector, selector: selector,
properties: properties, properties: properties,
events: events,
hostListeners: hostListeners, hostListeners: hostListeners,
lifecycle: lifecycle lifecycle: lifecycle
}); });

View File

@ -1,34 +1,11 @@
import {CONST} from 'angular2/src/facade/lang'; import {CONST} from 'angular2/src/facade/lang';
import {DependencyAnnotation} from 'angular2/di'; import {DependencyAnnotation} from 'angular2/di';
/**
* Specifies that a function for emitting events should be injected.
*
* NOTE: This is changing pre 1.0.
*
* The directive can inject an emitter function that would emit events onto the directive host element.
*
* @exportedAs angular2/annotations
*/
export class EventEmitter extends DependencyAnnotation {
eventName: string;
@CONST()
constructor(eventName) {
super();
this.eventName = eventName;
}
get token() {
return Function;
}
}
/** /**
* Specifies that a function for setting host properties should be injected. * Specifies that a function for setting host properties should be injected.
* *
* NOTE: This is changing pre 1.0. * NOTE: This is changing pre 1.0.
* *
* The directive can inject a property setter that would allow setting this property on the host element. * The directive can inject a property setter that would allow setting this property on the host element.
* *
* @exportedAs angular2/annotations * @exportedAs angular2/annotations

View File

@ -3,13 +3,14 @@ import {Math} from 'angular2/src/facade/math';
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoProviderError, ProviderError, CyclicDependencyError} from 'angular2/di'; import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoProviderError, ProviderError, CyclicDependencyError} from 'angular2/di';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
import {EventEmitter, PropertySetter, Attribute, Query} from 'angular2/src/core/annotations/di'; import {PropertySetter, Attribute, Query} from 'angular2/src/core/annotations/di';
import * as viewModule from 'angular2/src/core/compiler/view'; import * as viewModule from 'angular2/src/core/compiler/view';
import {ViewContainer} from 'angular2/src/core/compiler/view_container'; import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {NgElement} from 'angular2/src/core/compiler/ng_element'; import {NgElement} from 'angular2/src/core/compiler/ng_element';
import {Directive, Component, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations'; import {Directive, Component, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations';
import {ChangeDetector, ChangeDetectorRef} from 'angular2/change_detection'; import {ChangeDetector, ChangeDetectorRef} from 'angular2/change_detection';
import {QueryList} from './query_list'; import {QueryList} from './query_list';
import {reflector} from 'angular2/src/reflection/reflection';
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10; var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
@ -194,17 +195,14 @@ export class TreeNode {
export class DirectiveDependency extends Dependency { export class DirectiveDependency extends Dependency {
depth:int; depth:int;
eventEmitterName:string;
propSetterName:string; propSetterName:string;
attributeName:string; attributeName:string;
queryDirective; queryDirective;
constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean, constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean, properties:List,
properties:List, depth:int, eventEmitterName: string, depth:int, propSetterName: string, attributeName:string, queryDirective) {
propSetterName: string, attributeName:string, queryDirective) {
super(key, asPromise, lazy, optional, properties); super(key, asPromise, lazy, optional, properties);
this.depth = depth; this.depth = depth;
this.eventEmitterName = eventEmitterName;
this.propSetterName = propSetterName; this.propSetterName = propSetterName;
this.attributeName = attributeName; this.attributeName = attributeName;
this.queryDirective = queryDirective; this.queryDirective = queryDirective;
@ -213,18 +211,16 @@ export class DirectiveDependency extends Dependency {
_verify() { _verify() {
var count = 0; var count = 0;
if (isPresent(this.eventEmitterName)) count++;
if (isPresent(this.propSetterName)) count++; if (isPresent(this.propSetterName)) count++;
if (isPresent(this.queryDirective)) count++; if (isPresent(this.queryDirective)) count++;
if (isPresent(this.attributeName)) count++; if (isPresent(this.attributeName)) count++;
if (count > 1) throw new BaseException( if (count > 1) throw new BaseException(
'A directive injectable can contain only one of the following @EventEmitter, @PropertySetter, @Attribute or @Query.'); 'A directive injectable can contain only one of the following @PropertySetter, @Attribute or @Query.');
} }
static createFrom(d:Dependency):Dependency { static createFrom(d:Dependency):Dependency {
return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional, return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional,
d.properties, DirectiveDependency._depth(d.properties), d.properties, DirectiveDependency._depth(d.properties),
DirectiveDependency._eventEmitterName(d.properties),
DirectiveDependency._propSetterName(d.properties), DirectiveDependency._propSetterName(d.properties),
DirectiveDependency._attributeName(d.properties), DirectiveDependency._attributeName(d.properties),
DirectiveDependency._query(d.properties) DirectiveDependency._query(d.properties)
@ -238,11 +234,6 @@ export class DirectiveDependency extends Dependency {
return 0; return 0;
} }
static _eventEmitterName(properties):string {
var p = ListWrapper.find(properties, (p) => p instanceof EventEmitter);
return isPresent(p) ? p.eventName : null;
}
static _propSetterName(properties):string { static _propSetterName(properties):string {
var p = ListWrapper.find(properties, (p) => p instanceof PropertySetter); var p = ListWrapper.find(properties, (p) => p instanceof PropertySetter);
return isPresent(p) ? p.propName : null; return isPresent(p) ? p.propName : null;
@ -277,6 +268,10 @@ export class DirectiveBinding extends ResolvedBinding {
} }
} }
get eventEmitters():List<string> {
return isPresent(this.annotation) && isPresent(this.annotation.events) ? this.annotation.events : [];
}
get changeDetection() { get changeDetection() {
if (this.annotation instanceof Component) { if (this.annotation instanceof Component) {
var c:Component = this.annotation; var c:Component = this.annotation;
@ -296,10 +291,6 @@ export class DirectiveBinding extends ResolvedBinding {
var binding = new Binding(type, {toClass: type}); var binding = new Binding(type, {toClass: type});
return DirectiveBinding.createFromBinding(binding, annotation); return DirectiveBinding.createFromBinding(binding, annotation);
} }
static _hasEventEmitter(eventName: string, binding: DirectiveBinding) {
return ListWrapper.any(binding.dependencies, (d) => (d.eventEmitterName == eventName));
}
} }
// TODO(rado): benchmark and consider rolling in as ElementInjector fields. // TODO(rado): benchmark and consider rolling in as ElementInjector fields.
@ -317,6 +308,16 @@ export class PreBuiltObjects {
} }
} }
class EventEmitterAccessor {
eventName:string;
getter:Function;
constructor(eventName:string, getter:Function) {
this.eventName = eventName;
this.getter = getter;
}
}
/** /**
Difference between di.Injector and ElementInjector Difference between di.Injector and ElementInjector
@ -337,17 +338,18 @@ ElementInjector:
PERF BENCHMARK: http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/ PERF BENCHMARK: http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/
*/ */
export class ProtoElementInjector { export class ProtoElementInjector {
_binding0:ResolvedBinding; _binding0:DirectiveBinding;
_binding1:ResolvedBinding; _binding1:DirectiveBinding;
_binding2:ResolvedBinding; _binding2:DirectiveBinding;
_binding3:ResolvedBinding; _binding3:DirectiveBinding;
_binding4:ResolvedBinding; _binding4:DirectiveBinding;
_binding5:ResolvedBinding; _binding5:DirectiveBinding;
_binding6:ResolvedBinding; _binding6:DirectiveBinding;
_binding7:ResolvedBinding; _binding7:DirectiveBinding;
_binding8:ResolvedBinding; _binding8:DirectiveBinding;
_binding9:ResolvedBinding; _binding9:DirectiveBinding;
_binding0IsComponent:boolean; _binding0IsComponent:boolean;
_keyId0:int; _keyId0:int;
_keyId1:int; _keyId1:int;
@ -364,6 +366,7 @@ export class ProtoElementInjector {
view:viewModule.AppView; view:viewModule.AppView;
distanceToParent:number; distanceToParent:number;
attributes:Map; attributes:Map;
eventEmitterAccessors:List<List<EventEmitterAccessor>>;
numberOfDirectives:number; numberOfDirectives:number;
@ -397,22 +400,69 @@ export class ProtoElementInjector {
this.numberOfDirectives = bindings.length; this.numberOfDirectives = bindings.length;
var length = bindings.length; var length = bindings.length;
this.eventEmitterAccessors = ListWrapper.createFixedSize(length);
if (length > 0) {this._binding0 = this._createBinding(bindings[0]); this._keyId0 = this._binding0.key.id;} if (length > 0) {
if (length > 1) {this._binding1 = this._createBinding(bindings[1]); this._keyId1 = this._binding1.key.id;} this._binding0 = this._createBinding(bindings[0]);
if (length > 2) {this._binding2 = this._createBinding(bindings[2]); this._keyId2 = this._binding2.key.id;} this._keyId0 = this._binding0.key.id;
if (length > 3) {this._binding3 = this._createBinding(bindings[3]); this._keyId3 = this._binding3.key.id;} this.eventEmitterAccessors[0] = this._createEventEmitterAccessors(this._binding0);
if (length > 4) {this._binding4 = this._createBinding(bindings[4]); this._keyId4 = this._binding4.key.id;} }
if (length > 5) {this._binding5 = this._createBinding(bindings[5]); this._keyId5 = this._binding5.key.id;} if (length > 1) {
if (length > 6) {this._binding6 = this._createBinding(bindings[6]); this._keyId6 = this._binding6.key.id;} this._binding1 = this._createBinding(bindings[1]);
if (length > 7) {this._binding7 = this._createBinding(bindings[7]); this._keyId7 = this._binding7.key.id;} this._keyId1 = this._binding1.key.id;
if (length > 8) {this._binding8 = this._createBinding(bindings[8]); this._keyId8 = this._binding8.key.id;} this.eventEmitterAccessors[1] = this._createEventEmitterAccessors(this._binding1);
if (length > 9) {this._binding9 = this._createBinding(bindings[9]); this._keyId9 = this._binding9.key.id;} }
if (length > 2) {
this._binding2 = this._createBinding(bindings[2]);
this._keyId2 = this._binding2.key.id;
this.eventEmitterAccessors[2] = this._createEventEmitterAccessors(this._binding2);
}
if (length > 3) {
this._binding3 = this._createBinding(bindings[3]);
this._keyId3 = this._binding3.key.id;
this.eventEmitterAccessors[3] = this._createEventEmitterAccessors(this._binding3);
}
if (length > 4) {
this._binding4 = this._createBinding(bindings[4]);
this._keyId4 = this._binding4.key.id;
this.eventEmitterAccessors[4] = this._createEventEmitterAccessors(this._binding4);
}
if (length > 5) {
this._binding5 = this._createBinding(bindings[5]);
this._keyId5 = this._binding5.key.id;
this.eventEmitterAccessors[5] = this._createEventEmitterAccessors(this._binding5);
}
if (length > 6) {
this._binding6 = this._createBinding(bindings[6]);
this._keyId6 = this._binding6.key.id;
this.eventEmitterAccessors[6] = this._createEventEmitterAccessors(this._binding6);
}
if (length > 7) {
this._binding7 = this._createBinding(bindings[7]);
this._keyId7 = this._binding7.key.id;
this.eventEmitterAccessors[7] = this._createEventEmitterAccessors(this._binding7);
}
if (length > 8) {
this._binding8 = this._createBinding(bindings[8]);
this._keyId8 = this._binding8.key.id;
this.eventEmitterAccessors[8] = this._createEventEmitterAccessors(this._binding8);
}
if (length > 9) {
this._binding9 = this._createBinding(bindings[9]);
this._keyId9 = this._binding9.key.id;
this.eventEmitterAccessors[9] = this._createEventEmitterAccessors(this._binding9);
}
if (length > 10) { if (length > 10) {
throw 'Maximum number of directives per element has been reached.'; throw 'Maximum number of directives per element has been reached.';
} }
} }
_createEventEmitterAccessors(b:DirectiveBinding) {
return ListWrapper.map(b.eventEmitters, eventName =>
new EventEmitterAccessor(eventName, reflector.getter(eventName))
);
}
instantiate(parent:ElementInjector):ElementInjector { instantiate(parent:ElementInjector):ElementInjector {
return new ElementInjector(this, parent); return new ElementInjector(this, parent);
} }
@ -447,21 +497,6 @@ export class ProtoElementInjector {
if (index == 9) return this._binding9; if (index == 9) return this._binding9;
throw new OutOfBoundsAccess(index); throw new OutOfBoundsAccess(index);
} }
hasEventEmitter(eventName: string) {
var p = this;
if (isPresent(p._binding0) && DirectiveBinding._hasEventEmitter(eventName, p._binding0)) return true;
if (isPresent(p._binding1) && DirectiveBinding._hasEventEmitter(eventName, p._binding1)) return true;
if (isPresent(p._binding2) && DirectiveBinding._hasEventEmitter(eventName, p._binding2)) return true;
if (isPresent(p._binding3) && DirectiveBinding._hasEventEmitter(eventName, p._binding3)) return true;
if (isPresent(p._binding4) && DirectiveBinding._hasEventEmitter(eventName, p._binding4)) return true;
if (isPresent(p._binding5) && DirectiveBinding._hasEventEmitter(eventName, p._binding5)) return true;
if (isPresent(p._binding6) && DirectiveBinding._hasEventEmitter(eventName, p._binding6)) return true;
if (isPresent(p._binding7) && DirectiveBinding._hasEventEmitter(eventName, p._binding7)) return true;
if (isPresent(p._binding8) && DirectiveBinding._hasEventEmitter(eventName, p._binding8)) return true;
if (isPresent(p._binding9) && DirectiveBinding._hasEventEmitter(eventName, p._binding9)) return true;
return false;
}
} }
export class ElementInjector extends TreeNode { export class ElementInjector extends TreeNode {
@ -607,6 +642,10 @@ export class ElementInjector extends TreeNode {
return this._getDirectiveByKeyId(Key.get(type).id) !== _undefined; return this._getDirectiveByKeyId(Key.get(type).id) !== _undefined;
} }
getEventEmitterAccessors() {
return this._proto.eventEmitterAccessors;
}
/** Gets the NgElement associated with this ElementInjector */ /** Gets the NgElement associated with this ElementInjector */
getNgElement() { getNgElement() {
return this._preBuiltObjects.element; return this._preBuiltObjects.element;
@ -689,7 +728,6 @@ export class ElementInjector extends TreeNode {
} }
_getByDependency(dep:DirectiveDependency, requestor:Key) { _getByDependency(dep:DirectiveDependency, requestor:Key) {
if (isPresent(dep.eventEmitterName)) return this._buildEventEmitter(dep);
if (isPresent(dep.propSetterName)) return this._buildPropSetter(dep); if (isPresent(dep.propSetterName)) return this._buildPropSetter(dep);
if (isPresent(dep.attributeName)) return this._buildAttribute(dep); if (isPresent(dep.attributeName)) return this._buildAttribute(dep);
if (isPresent(dep.queryDirective)) return this._findQuery(dep.queryDirective).list; if (isPresent(dep.queryDirective)) return this._findQuery(dep.queryDirective).list;
@ -699,13 +737,6 @@ export class ElementInjector extends TreeNode {
return this._getByKey(dep.key, dep.depth, dep.optional, requestor); return this._getByKey(dep.key, dep.depth, dep.optional, requestor);
} }
_buildEventEmitter(dep: DirectiveDependency) {
var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId);
return (event) => {
view.triggerEventHandlers(dep.eventEmitterName, event, this._proto.index);
};
}
_buildPropSetter(dep) { _buildPropSetter(dep) {
var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId); var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId);
var renderer = view.renderer; var renderer = view.renderer;
@ -934,18 +965,10 @@ export class ElementInjector extends TreeNode {
throw new OutOfBoundsAccess(index); throw new OutOfBoundsAccess(index);
} }
getDirectiveBindingAtIndex(index:int) {
return this._proto.getDirectiveBindingAtIndex(index);
}
hasInstances() { hasInstances() {
return this._constructionCounter > 0; return this._constructionCounter > 0;
} }
hasEventEmitter(eventName: string) {
return this._proto.hasEventEmitter(eventName);
}
/** Gets whether this element is exporting a component instance as $implicit. */ /** Gets whether this element is exporting a component instance as $implicit. */
isExportingComponent() { isExportingComponent() {
return this._proto.exportComponent; return this._proto.exportComponent;

View File

@ -2,6 +2,7 @@ import {Injectable, Inject, OpaqueToken, Injector} from 'angular2/di';
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
import * as eli from './element_injector'; import * as eli from './element_injector';
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {ObservableWrapper} from 'angular2/src/facade/async';
import * as vcModule from './view_container'; import * as vcModule from './view_container';
import * as viewModule from './view'; import * as viewModule from './view';
import {BindingPropagationConfig, Locals} from 'angular2/change_detection'; import {BindingPropagationConfig, Locals} from 'angular2/change_detection';
@ -165,6 +166,7 @@ export class AppViewHydrator {
var elementInjector = view.elementInjectors[i]; var elementInjector = view.elementInjectors[i];
if (isPresent(elementInjector)) { if (isPresent(elementInjector)) {
elementInjector.instantiateDirectives(appInjector, hostElementInjector, shadowDomAppInjector, view.preBuiltObjects[i]); elementInjector.instantiateDirectives(appInjector, hostElementInjector, shadowDomAppInjector, view.preBuiltObjects[i]);
this._setUpEventEmitters(view, elementInjector);
// The exporting of $implicit is a special case. Since multiple elements will all export // The exporting of $implicit is a special case. Since multiple elements will all export
// the different values as $implicit, directly assign $implicit bindings to the variable // the different values as $implicit, directly assign $implicit bindings to the variable
@ -194,6 +196,25 @@ export class AppViewHydrator {
return renderComponentIndex; return renderComponentIndex;
} }
_setUpEventEmitters(view:viewModule.AppView, elementInjector:eli.ElementInjector) {
var emitters = elementInjector.getEventEmitterAccessors();
for(var directiveIndex = 0; directiveIndex < emitters.length; ++directiveIndex) {
var directiveEmitters = emitters[directiveIndex];
var directive = elementInjector.getDirectiveAtIndex(directiveIndex);
for (var eventIndex = 0; eventIndex < directiveEmitters.length; ++eventIndex) {
var eventEmitterAccessor = directiveEmitters[eventIndex];
this._setUpSubscription(view, directive, directiveIndex, eventEmitterAccessor);
}
}
}
_setUpSubscription(view:viewModule.AppView, directive:Object, directiveIndex:number, eventEmitterAccessor) {
var eventEmitter = eventEmitterAccessor.getter(directive);
ObservableWrapper.subscribe(eventEmitter,
eventObj => view.triggerEventHandlers(eventEmitterAccessor.eventName, eventObj, directiveIndex));
}
/** /**
* This should only be called by View or ViewContainer. * This should only be called by View or ViewContainer.
*/ */

View File

@ -37,27 +37,50 @@ class ObservableWrapper {
return s.listen(onNext, onError: onError, onDone: onComplete, cancelOnError: true); return s.listen(onNext, onError: onError, onDone: onComplete, cancelOnError: true);
} }
static StreamController createController() { static void callNext(EventEmitter emitter, value) {
return new StreamController.broadcast(); emitter.add(value);
} }
static Stream createObservable(StreamController controller) { static void callThrow(EventEmitter emitter, error) {
return controller.stream; emitter.addError(error);
} }
static void callNext(StreamController controller, value) { static void callReturn(EventEmitter emitter) {
controller.add(value); emitter.close();
}
static void callThrow(StreamController controller, error) {
controller.addError(error);
}
static void callReturn(StreamController controller) {
controller.close();
} }
} }
class EventEmitter extends Stream {
StreamController<String> _controller;
EventEmitter() {
_controller = new StreamController.broadcast();
}
StreamSubscription listen(void onData(String line), {
void onError(Error error),
void onDone(),
bool cancelOnError }) {
return _controller.stream.listen(onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError);
}
void add(value) {
_controller.add(value);
}
void addError(error) {
_controller.addError(error);
}
void close() {
_controller.close();
}
}
class _Completer { class _Completer {
final Completer c; final Completer c;

View File

@ -53,6 +53,28 @@ export class PromiseWrapper {
} }
} }
export class ObservableWrapper {
static subscribe(emitter:EventEmitter, onNext, onThrow = null, onReturn = null) {
return emitter.observer({next: onNext, throw: onThrow, return: onReturn});
}
static callNext(emitter:EventEmitter, value:any) {
emitter.next(value);
}
static callThrow(emitter:EventEmitter, error:any) {
emitter.throw(error);
}
static callReturn(emitter:EventEmitter) {
emitter.return();
}
}
//TODO: vsavkin change to interface
export class Observable {
observer(generator:Function){}
}
/** /**
* Use Rx.Observable but provides an adapter to make it work as specified here: * Use Rx.Observable but provides an adapter to make it work as specified here:
@ -60,39 +82,37 @@ export class PromiseWrapper {
* *
* Once a reference implementation of the spec is available, switch to it. * Once a reference implementation of the spec is available, switch to it.
*/ */
export var Observable = Rx.Observable; export class EventEmitter extends Observable {
export var ObservableController = Rx.Subject; _subject:Rx.Subject;
export class ObservableWrapper { constructor() {
static createController():Rx.Subject { super();
return new Rx.Subject(); this._subject = new Rx.Subject();
} }
static createObservable(subject:Rx.Subject):Observable { observer(generator) {
return subject; // Rx.Scheduler.immediate and setTimeout is a workaround, so Rx works with zones.js.
// Once https://github.com/angular/zone.js/issues/51 is fixed, the hack should be removed.
return this._subject.observeOn(Rx.Scheduler.immediate).subscribe(
(value) => {setTimeout(() => generator.next(value));},
(error) => generator.throw ? generator.throw(error) : null,
() => generator.return ? generator.return() : null
);
} }
static subscribe(observable:Observable, generatorOrOnNext, onThrow = null, onReturn = null) { toRx():Rx.Observable {
if (isPresent(generatorOrOnNext.next)) { return this._subject;
return observable.observeOn(Rx.Scheduler.timeout).subscribe(
(value) => generatorOrOnNext.next(value),
(error) => generatorOrOnNext.throw(error),
() => generatorOrOnNext.return()
);
} else {
return observable.observeOn(Rx.Scheduler.timeout).subscribe(generatorOrOnNext, onThrow, onReturn);
}
} }
static callNext(subject:Rx.Subject, value:any) { next(value) {
subject.onNext(value); this._subject.onNext(value);
} }
static callThrow(subject:Rx.Subject, error:any) { throw(error) {
subject.onError(error); this._subject.onError(error);
} }
static callReturn(subject:Rx.Subject) { return(value) {
subject.onCompleted(); this._subject.onCompleted();
} }
} }

View File

@ -49,35 +49,50 @@ export class PromiseWrapper {
} }
export class ObservableWrapper {
static subscribe(emitter: EventEmitter, onNext, onThrow = null, onReturn = null) {
return emitter.observer({next: onNext, throw: onThrow, return: onReturn});
}
static callNext(emitter: EventEmitter, value: any) { emitter.next(value); }
static callThrow(emitter: EventEmitter, error: any) { emitter.throw(error); }
static callReturn(emitter: EventEmitter) { emitter.return (null); }
}
// TODO: vsavkin change to interface
export class Observable {
observer(generator: any) {}
}
/** /**
* Use Rx.Observable but provides an adapter to make it work as specified here: * Use Rx.Observable but provides an adapter to make it work as specified here:
* https://github.com/jhusain/observable-spec * https://github.com/jhusain/observable-spec
* *
* Once a reference implementation of the spec is available, switch to it. * Once a reference implementation of the spec is available, switch to it.
*/ */
type Observable = Rx.Observable<any>; export class EventEmitter extends Observable {
type ObservableController = Rx.Subject<any>; _subject: Rx.Subject<any>;
export class ObservableWrapper { constructor() {
static createController(): Rx.Subject<any> { return new Rx.Subject(); } super();
this._subject = new Rx.Subject<any>();
static createObservable<T>(subject: Rx.Subject<T>): Rx.Observable<T> { return subject; }
static subscribe(observable: Rx.Observable<any>, generatorOrOnNext, onThrow = null,
onReturn = null) {
if (isPresent(generatorOrOnNext.next)) {
return observable.observeOn(Rx.Scheduler.timeout)
.subscribe((value) => generatorOrOnNext.next(value),
(error) => generatorOrOnNext.throw(error), () => generatorOrOnNext.return ());
} else {
return observable.observeOn(Rx.Scheduler.timeout)
.subscribe(generatorOrOnNext, onThrow, onReturn);
}
} }
static callNext(subject: Rx.Subject<any>, value: any) { subject.onNext(value); } observer(generator) {
var immediateScheduler = (<any>Rx.Scheduler).immediate;
return this._subject.observeOn(immediateScheduler)
.subscribe((value) => { setTimeout(() => generator.next(value)); },
(error) => generator.throw ? generator.throw(error) : null,
() => generator.return ? generator.return () : null);
}
static callThrow(subject: Rx.Subject<any>, error: any) { subject.onError(error); } toRx(): Rx.Observable<any> { return this._subject; }
static callReturn(subject: Rx.Subject<any>) { subject.onCompleted(); } next(value) { this._subject.onNext(value); }
}
throw(error) { this._subject.onError(error); }
return (value) { this._subject.onCompleted(); }
}

View File

@ -1,5 +1,5 @@
import {isPresent} from 'angular2/src/facade/lang'; import {isPresent} from 'angular2/src/facade/lang';
import {Observable, ObservableController, ObservableWrapper} from 'angular2/src/facade/async'; import {Observable, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {StringMap, StringMapWrapper, ListWrapper, List} from 'angular2/src/facade/collection'; import {StringMap, StringMapWrapper, ListWrapper, List} from 'angular2/src/facade/collection';
import {Validators} from './validators'; import {Validators} from './validators';
@ -40,8 +40,7 @@ export class AbstractControl {
_parent:any; /* ControlGroup | ControlArray */ _parent:any; /* ControlGroup | ControlArray */
validator:Function; validator:Function;
valueChanges:Observable; _valueChanges:EventEmitter;
_valueChangesController:ObservableController;
constructor(validator:Function) { constructor(validator:Function) {
this.validator = validator; this.validator = validator;
@ -72,6 +71,10 @@ export class AbstractControl {
return ! this.pristine; return ! this.pristine;
} }
get valueChanges():Observable {
return this._valueChanges;
}
setParent(parent){ setParent(parent){
this._parent = parent; this._parent = parent;
} }
@ -95,16 +98,14 @@ export class Control extends AbstractControl {
constructor(value:any, validator:Function = Validators.nullValidator) { constructor(value:any, validator:Function = Validators.nullValidator) {
super(validator); super(validator);
this._setValueErrorsStatus(value); this._setValueErrorsStatus(value);
this._valueChanges = new EventEmitter();
this._valueChangesController = ObservableWrapper.createController();
this.valueChanges = ObservableWrapper.createObservable(this._valueChangesController);
} }
updateValue(value:any):void { updateValue(value:any):void {
this._setValueErrorsStatus(value); this._setValueErrorsStatus(value);
this._pristine = false; this._pristine = false;
ObservableWrapper.callNext(this._valueChangesController, this._value); ObservableWrapper.callNext(this._valueChanges, this._value);
this._updateParent(); this._updateParent();
} }
@ -137,8 +138,7 @@ export class ControlGroup extends AbstractControl {
this.controls = controls; this.controls = controls;
this._optionals = isPresent(optionals) ? optionals : {}; this._optionals = isPresent(optionals) ? optionals : {};
this._valueChangesController = ObservableWrapper.createController(); this._valueChanges = new EventEmitter();
this.valueChanges = ObservableWrapper.createObservable(this._valueChangesController);
this._setParentForControls(); this._setParentForControls();
this._setValueErrorsStatus(); this._setValueErrorsStatus();
@ -169,7 +169,7 @@ export class ControlGroup extends AbstractControl {
this._setValueErrorsStatus(); this._setValueErrorsStatus();
this._pristine = false; this._pristine = false;
ObservableWrapper.callNext(this._valueChangesController, this._value); ObservableWrapper.callNext(this._valueChanges, this._value);
this._updateParent(); this._updateParent();
} }
@ -222,8 +222,7 @@ export class ControlArray extends AbstractControl {
super(validator); super(validator);
this.controls = controls; this.controls = controls;
this._valueChangesController = ObservableWrapper.createController(); this._valueChanges = new EventEmitter();
this.valueChanges = ObservableWrapper.createObservable(this._valueChangesController);
this._setParentForControls(); this._setParentForControls();
this._setValueErrorsStatus(); this._setValueErrorsStatus();
@ -258,7 +257,7 @@ export class ControlArray extends AbstractControl {
this._setValueErrorsStatus(); this._setValueErrorsStatus();
this._pristine = false; this._pristine = false;
ObservableWrapper.callNext(this._valueChangesController, this._value); ObservableWrapper.callNext(this._valueChanges, this._value);
this._updateParent(); this._updateParent();
} }

View File

@ -4,7 +4,7 @@ import {ListWrapper, MapWrapper, List, StringMapWrapper, iterateListLike} from '
import {ProtoElementInjector, PreBuiltObjects, DirectiveBinding, TreeNode, ElementRef} import {ProtoElementInjector, PreBuiltObjects, DirectiveBinding, TreeNode, ElementRef}
from 'angular2/src/core/compiler/element_injector'; from 'angular2/src/core/compiler/element_injector';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
import {EventEmitter, PropertySetter, Attribute, Query} from 'angular2/src/core/annotations/di'; import {PropertySetter, Attribute, Query} from 'angular2/src/core/annotations/di';
import {onDestroy} from 'angular2/src/core/annotations/annotations'; import {onDestroy} from 'angular2/src/core/annotations/annotations';
import {Optional, Injector, Inject, bind} from 'angular2/di'; import {Optional, Injector, Inject, bind} from 'angular2/di';
import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; import {AppProtoView, AppView} from 'angular2/src/core/compiler/view';
@ -12,11 +12,11 @@ import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {NgElement} from 'angular2/src/core/compiler/ng_element'; import {NgElement} from 'angular2/src/core/compiler/ng_element';
import {Directive} from 'angular2/src/core/annotations/annotations'; import {Directive} from 'angular2/src/core/annotations/annotations';
import {DynamicChangeDetector, ChangeDetectorRef, Parser, Lexer} from 'angular2/change_detection'; import {DynamicChangeDetector, ChangeDetectorRef, Parser, Lexer} from 'angular2/change_detection';
import {ViewRef, Renderer, EventBinding} from 'angular2/src/render/api'; import {ViewRef, Renderer} from 'angular2/src/render/api';
import {QueryList} from 'angular2/src/core/compiler/query_list'; import {QueryList} from 'angular2/src/core/compiler/query_list';
class DummyDirective extends Directive { class DummyDirective extends Directive {
constructor({lifecycle} = {}) { super({lifecycle: lifecycle}); } constructor({lifecycle, events} = {}) { super({lifecycle: lifecycle, events: events}); }
} }
@proxy @proxy
@ -80,23 +80,10 @@ class NeedsService {
} }
} }
class NeedsEventEmitter { class HasEventEmitter {
clickEmitter; emitter;
constructor(@EventEmitter('click') clickEmitter:Function) { constructor() {
this.clickEmitter = clickEmitter; this.emitter = "emitter";
}
click() {
this.clickEmitter(null);
}
}
class NeedsEventEmitterNoType {
clickEmitter;
constructor(@EventEmitter('click') clickEmitter) {
this.clickEmitter = clickEmitter;
}
click() {
this.clickEmitter(null);
} }
} }
@ -380,6 +367,20 @@ export function main() {
'Index 10 is out-of-bounds.'); 'Index 10 is out-of-bounds.');
}); });
}); });
describe('event emitters', () => {
it('should return a list of event emitter accessors', () => {
var binding = DirectiveBinding.createFromType(
HasEventEmitter, new DummyDirective({events: ['emitter']}));
var inj = new ProtoElementInjector(null, 0, [binding]);
expect(inj.eventEmitterAccessors.length).toEqual(1);
var accessor = inj.eventEmitterAccessors[0][0];
expect(accessor.eventName).toEqual('emitter');
expect(accessor.getter(new HasEventEmitter())).toEqual('emitter');
});
});
}); });
describe("ElementInjector", function () { describe("ElementInjector", function () {
@ -703,51 +704,6 @@ export function main() {
}); });
}); });
describe('event emitters', () => {
function createpreBuildObject(eventName, eventHandler) {
var handlers = StringMapWrapper.create();
StringMapWrapper.set(handlers, eventName, eventHandler);
var pv = new AppProtoView(null, null);
pv.bindElement(null, 0, null, null, null);
var eventBindings = ListWrapper.create();
ListWrapper.push(eventBindings, new EventBinding(eventName, new Parser(new Lexer()).parseAction('handler()', '')));
pv.bindEvent(eventBindings);
var view = new AppView(null, pv, MapWrapper.create());
view.context = new ContextWithHandler(eventHandler);
return new PreBuiltObjects(view, null, null, null);
}
it('should be injectable and callable', () => {
var called = false;
var preBuildObject = createpreBuildObject('click', () => { called = true;});
var inj = injector([NeedsEventEmitter], null, null, preBuildObject);
inj.get(NeedsEventEmitter).click();
expect(called).toEqual(true);
});
it('should be injectable and callable without specifying param type annotation', () => {
var called = false;
var preBuildObject = createpreBuildObject('click', () => { called = true;});
var inj = injector([NeedsEventEmitterNoType], null, null, preBuildObject);
inj.get(NeedsEventEmitterNoType).click();
expect(called).toEqual(true);
});
it('should be queryable through hasEventEmitter', () => {
var inj = injector([NeedsEventEmitter]);
expect(inj.hasEventEmitter('click')).toBe(true);
expect(inj.hasEventEmitter('move')).toBe(false);
});
it('should be queryable through hasEventEmitter without specifying param type annotation', () => {
var inj = injector([NeedsEventEmitterNoType]);
expect(inj.hasEventEmitter('click')).toBe(true);
expect(inj.hasEventEmitter('move')).toBe(false);
});
});
describe('property setter', () => { describe('property setter', () => {
var renderer, view; var renderer, view;

View File

@ -18,7 +18,7 @@ import {TestBed} from 'angular2/src/test_lib/test_bed';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {Type, isPresent, BaseException, assertionsEnabled, isJsObject, global} from 'angular2/src/facade/lang'; import {Type, isPresent, BaseException, assertionsEnabled, isJsObject, global} from 'angular2/src/facade/lang';
import {PromiseWrapper} from 'angular2/src/facade/async'; import {PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {Injector, bind} from 'angular2/di'; import {Injector, bind} from 'angular2/di';
import {dynamicChangeDetection, import {dynamicChangeDetection,
@ -27,7 +27,7 @@ import {dynamicChangeDetection,
import {Decorator, Component, Viewport, DynamicComponent} from 'angular2/src/core/annotations/annotations'; import {Decorator, Component, Viewport, DynamicComponent} from 'angular2/src/core/annotations/annotations';
import {View} from 'angular2/src/core/annotations/view'; import {View} from 'angular2/src/core/annotations/view';
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
import {EventEmitter, Attribute} from 'angular2/src/core/annotations/di'; import {Attribute} from 'angular2/src/core/annotations/di';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
import {ElementRef} from 'angular2/src/core/compiler/element_injector'; import {ElementRef} from 'angular2/src/core/compiler/element_injector';
@ -532,14 +532,14 @@ export function main() {
var emitter = injector.get(DecoratorEmitingEvent); var emitter = injector.get(DecoratorEmitingEvent);
var listener = injector.get(DecoratorListeningEvent); var listener = injector.get(DecoratorListeningEvent);
expect(emitter.msg).toEqual('');
expect(listener.msg).toEqual(''); expect(listener.msg).toEqual('');
emitter.fireEvent('fired !'); emitter.fireEvent('fired !');
expect(emitter.msg).toEqual('fired !');
expect(listener.msg).toEqual('fired !');
async.done(); PromiseWrapper.setTimeout(() => {
expect(listener.msg).toEqual('fired !');
async.done();
}, 0);
}); });
})); }));
@ -994,23 +994,19 @@ class DoublePipeFactory {
@Decorator({ @Decorator({
selector: '[emitter]', selector: '[emitter]',
hostListeners: {'event': 'onEvent($event)'} events: ['event']
}) })
class DecoratorEmitingEvent { class DecoratorEmitingEvent {
msg: string; msg: string;
emitter; event:EventEmitter;
constructor(@EventEmitter('event') emitter:Function) { constructor() {
this.msg = ''; this.msg = '';
this.emitter = emitter; this.event = new EventEmitter();
} }
fireEvent(msg: string) { fireEvent(msg: string) {
this.emitter(msg); ObservableWrapper.callNext(this.event, msg);
}
onEvent(msg: string) {
this.msg = msg;
} }
} }

View File

@ -47,6 +47,7 @@ export function main() {
var res = new SpyElementInjector(); var res = new SpyElementInjector();
res.spy('isExportingComponent').andCallFake( () => false ); res.spy('isExportingComponent').andCallFake( () => false );
res.spy('isExportingElement').andCallFake( () => false ); res.spy('isExportingElement').andCallFake( () => false );
res.spy('getEventEmitterAccessors').andCallFake( () => [] );
return res; return res;
} }

View File

@ -1,99 +1,61 @@
import {describe, it, expect, beforeEach, ddescribe, iit, xit, el, import {describe, it, expect, beforeEach, ddescribe, iit, xit, el,
SpyObject, AsyncTestCompleter, inject, IS_DARTIUM} from 'angular2/test_lib'; SpyObject, AsyncTestCompleter, inject, IS_DARTIUM} from 'angular2/test_lib';
import {ObservableWrapper, Observable, ObservableController, PromiseWrapper} from 'angular2/src/facade/async'; import {ObservableWrapper, EventEmitter, PromiseWrapper} from 'angular2/src/facade/async';
export function main() { export function main() {
describe('Observable', () => { describe('EventEmitter', () => {
var obs:Observable; var emitter:EventEmitter;
var controller:ObservableController;
beforeEach(() => { beforeEach(() => {
controller = ObservableWrapper.createController(); emitter = new EventEmitter();
obs = ObservableWrapper.createObservable(controller);
}); });
it("should call the next callback", inject([AsyncTestCompleter], (async) => { it("should call the next callback", inject([AsyncTestCompleter], (async) => {
ObservableWrapper.subscribe(obs, (value) => { ObservableWrapper.subscribe(emitter, (value) => {
expect(value).toEqual(99); expect(value).toEqual(99);
async.done(); async.done();
}); });
ObservableWrapper.callNext(controller, 99); ObservableWrapper.callNext(emitter, 99);
})); }));
it("should call the throw callback", inject([AsyncTestCompleter], (async) => { it("should call the throw callback", inject([AsyncTestCompleter], (async) => {
ObservableWrapper.subscribe(obs, (_) => {}, (error) => { ObservableWrapper.subscribe(emitter, (_) => {}, (error) => {
expect(error).toEqual("Boom"); expect(error).toEqual("Boom");
async.done(); async.done();
}); });
ObservableWrapper.callThrow(controller, "Boom"); ObservableWrapper.callThrow(emitter, "Boom");
}));
it("should work when no throw callback is provided", inject([AsyncTestCompleter], (async) => {
ObservableWrapper.subscribe(emitter, (_) => {}, (_) => {
async.done();
});
ObservableWrapper.callThrow(emitter, "Boom");
})); }));
it("should call the return callback", inject([AsyncTestCompleter], (async) => { it("should call the return callback", inject([AsyncTestCompleter], (async) => {
ObservableWrapper.subscribe(obs, (_) => {}, (_) => {}, () => { ObservableWrapper.subscribe(emitter, (_) => {}, (_) => {}, () => {
async.done(); async.done();
}); });
ObservableWrapper.callReturn(controller); ObservableWrapper.callReturn(emitter);
})); }));
it("should subscribe to the wrapper asynchronously", () => { it("should subscribe to the wrapper asynchronously", () => {
var called = false; var called = false;
ObservableWrapper.subscribe(obs, (value) => { ObservableWrapper.subscribe(emitter, (value) => {
called = true; called = true;
}); });
ObservableWrapper.callNext(controller, 99); ObservableWrapper.callNext(emitter, 99);
expect(called).toBe(false); expect(called).toBe(false);
}); });
if (!IS_DARTIUM) {
// See here: https://github.com/jhusain/observable-spec
describe("Generator", () => {
var generator;
beforeEach(() => {
generator = new SpyObject();
generator.spy("next");
generator.spy("throw");
generator.spy("return");
});
it("should call next on the given generator", inject([AsyncTestCompleter], (async) => {
generator.spy("next").andCallFake((value) => {
expect(value).toEqual(99);
async.done();
});
ObservableWrapper.subscribe(obs, generator);
ObservableWrapper.callNext(controller, 99);
}));
it("should call throw on the given generator", inject([AsyncTestCompleter], (async) => {
generator.spy("throw").andCallFake((error) => {
expect(error).toEqual("Boom");
async.done();
});
ObservableWrapper.subscribe(obs, generator);
ObservableWrapper.callThrow(controller, "Boom");
}));
it("should call return on the given generator", inject([AsyncTestCompleter], (async) => {
generator.spy("return").andCallFake(() => {
async.done();
});
ObservableWrapper.subscribe(obs, generator);
ObservableWrapper.callReturn(controller);
}));
});
}
//TODO: vsavkin: add tests cases //TODO: vsavkin: add tests cases
//should call dispose on the subscription if generator returns {done:true} //should call dispose on the subscription if generator returns {done:true}
//should call dispose on the subscription on throw //should call dispose on the subscription on throw
//should call dispose on the subscription on return //should call dispose on the subscription on return
}); });
} }
//make sure rx observables are async

View File

@ -1,11 +1,10 @@
import {Component, View, Parent, Ancestor, Attribute, PropertySetter, import {Component, View, Parent, Ancestor, Attribute, PropertySetter} from 'angular2/angular2';
EventEmitter} from 'angular2/angular2';
import {Optional} from 'angular2/src/di/annotations'; import {Optional} from 'angular2/src/di/annotations';
import {MdRadioDispatcher} from 'angular2_material/src/components/radio/radio_dispatcher' import {MdRadioDispatcher} from 'angular2_material/src/components/radio/radio_dispatcher'
import {MdTheme} from 'angular2_material/src/core/theme' import {MdTheme} from 'angular2_material/src/core/theme'
import {onChange} from 'angular2/src/core/annotations/annotations'; import {onChange} from 'angular2/src/core/annotations/annotations';
import {isPresent, StringWrapper} from 'angular2/src/facade/lang'; import {isPresent, StringWrapper} from 'angular2/src/facade/lang';
// import {KeyCodes} from 'angular2_material/src/core/constants' import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async';
import {Math} from 'angular2/src/facade/math'; import {Math} from 'angular2/src/facade/math';
import {ListWrapper} from 'angular2/src/facade/collection'; import {ListWrapper} from 'angular2/src/facade/collection';
@ -177,6 +176,7 @@ export class MdRadioButton {
@Component({ @Component({
selector: 'md-radio-group', selector: 'md-radio-group',
lifecycle: [onChange], lifecycle: [onChange],
events: ['change'],
properties: { properties: {
'disabled': 'disabled', 'disabled': 'disabled',
'value': 'value' 'value': 'value'
@ -212,6 +212,8 @@ export class MdRadioGroup {
/** The ID of the selected radio button. */ /** The ID of the selected radio button. */
selectedRadioId: string; selectedRadioId: string;
change:EventEmitter;
constructor( constructor(
@Attribute('tabindex') tabindex: string, @Attribute('tabindex') tabindex: string,
@Attribute('disabled') disabled: string, @Attribute('disabled') disabled: string,
@ -219,11 +221,10 @@ export class MdRadioGroup {
@PropertySetter('attr.role') roleSetter: Function, @PropertySetter('attr.role') roleSetter: Function,
@PropertySetter('attr.aria-disabled') ariaDisabledSetter: Function, @PropertySetter('attr.aria-disabled') ariaDisabledSetter: Function,
@PropertySetter('attr.aria-activedescendant') ariaActiveDescendantSetter: Function, @PropertySetter('attr.aria-activedescendant') ariaActiveDescendantSetter: Function,
@EventEmitter('change') changeEmitter: Function,
radioDispatcher: MdRadioDispatcher) { radioDispatcher: MdRadioDispatcher) {
this.name_ = `md-radio-group-${_uniqueIdCounter++}`; this.name_ = `md-radio-group-${_uniqueIdCounter++}`;
this.radios_ = []; this.radios_ = [];
this.changeEmitter = changeEmitter; this.change = new EventEmitter();
this.ariaActiveDescendantSetter = ariaActiveDescendantSetter; this.ariaActiveDescendantSetter = ariaActiveDescendantSetter;
this.ariaDisabledSetter = ariaDisabledSetter; this.ariaDisabledSetter = ariaDisabledSetter;
this.radioDispatcher = radioDispatcher; this.radioDispatcher = radioDispatcher;
@ -277,7 +278,7 @@ export class MdRadioGroup {
this.value = value; this.value = value;
this.selectedRadioId = id; this.selectedRadioId = id;
this.ariaActiveDescendantSetter(id); this.ariaActiveDescendantSetter(id);
this.changeEmitter(); ObservableWrapper.callNext(this.change, null);
} }
/** Registers a child radio button with this group. */ /** Registers a child radio button with this group. */

View File

@ -53,6 +53,7 @@ class HeaderFields {
// This component is self-contained and can be tested in isolation. // This component is self-contained and can be tested in isolation.
@Component({ @Component({
selector: 'survey-question', selector: 'survey-question',
events: ['destroy'],
properties: { properties: {
"question" : "question", "question" : "question",
"index" : "index" "index" : "index"
@ -100,16 +101,16 @@ class HeaderFields {
class SurveyQuestion { class SurveyQuestion {
question:ControlGroup; question:ControlGroup;
index:number; index:number;
onDelete:Function; destroy:EventEmitter;
constructor(@EventEmitter("delete") onDelete:Function) { constructor() {
this.onDelete = onDelete; this.destroy = new EventEmitter();
} }
deleteQuestion() { deleteQuestion() {
// Invoking an injected event emitter will fire an event, // Invoking an injected event emitter will fire an event,
// which in this case will result in calling `deleteQuestion(i)` // which in this case will result in calling `deleteQuestion(i)`
this.onDelete(); this.destroy.next(null);
} }
} }
@ -132,7 +133,7 @@ class SurveyQuestion {
*for="var q of form.controls.questions.controls; var i=index" *for="var q of form.controls.questions.controls; var i=index"
[question]="q" [question]="q"
[index]="i + 1" [index]="i + 1"
(delete)="deleteQuestion(i)"> (destroy)="destroyQuestion(i)">
</survey-question> </survey-question>
<button (click)="submitForm()">Submit</button> <button (click)="submitForm()">Submit</button>
@ -175,14 +176,14 @@ class SurveyBuilder {
// complex form interactions in a declarative fashion. // complex form interactions in a declarative fashion.
// //
// We are disabling the responseLength control when the question type is checkbox. // We are disabling the responseLength control when the question type is checkbox.
newQuestion.controls.type.valueChanges.subscribe((v) => newQuestion.controls.type.valueChanges.observer({
v == 'text' || v == 'textarea' ? next: (v) => v == 'text' || v == 'textarea' ? newQuestion.include('responseLength') : newQuestion.exclude('responseLength')
newQuestion.include('responseLength') : newQuestion.exclude('responseLength')); });
this.form.controls.questions.push(newQuestion); this.form.controls.questions.push(newQuestion);
} }
deleteQuestion(index:number) { destroyQuestion(index:number) {
this.form.controls.questions.removeAt(index); this.form.controls.questions.removeAt(index);
} }