@@ -143,14 +144,14 @@ class OrderListComponent {
directives: [FORM_DIRECTIVES]
})
class OrderItemComponent {
- item: OrderItem;
- delete = new EventEmitter();
+ @Input() item: OrderItem;
+ @Output() delete = new EventEmitter();
onDelete(): void { this.delete.next(this.item); }
}
-@Component({selector: 'order-details-cmp'})
-@View({
+@Component({
+ selector: 'order-details-cmp',
template: `
Selected Order
@@ -189,8 +190,9 @@ class OrderDetailsComponent {
addItem(): void { this._service.addItemForOrder(this.order); }
}
-@Component({selector: 'order-management-app', bindings: [DataService]})
-@View({
+@Component({
+ selector: 'order-management-app',
+ providers: [DataService],
template: `
diff --git a/modules/playground/src/zippy_component/zippy.ts b/modules/playground/src/zippy_component/zippy.ts
index cca3d0332496..cd79ee08a5bd 100644
--- a/modules/playground/src/zippy_component/zippy.ts
+++ b/modules/playground/src/zippy_component/zippy.ts
@@ -1,21 +1,19 @@
-import {Component, View, EventEmitter} from 'angular2/angular2';
+import {Component, EventEmitter, Input, Output} from 'angular2/angular2';
import {ObservableWrapper} from 'angular2/src/core/facade/async';
-@Component(
- {selector: 'zippy', inputs: ['title'], outputs: ['openHandler: open', 'closeHandler: close']})
-@View({templateUrl: 'zippy.html'})
+@Component({selector: 'zippy', templateUrl: 'zippy.html'})
export class Zippy {
visible: boolean = true;
- title: string = '';
- openHandler: EventEmitter
= new EventEmitter();
- closeHandler: EventEmitter = new EventEmitter();
+ @Input() title: string = '';
+ @Output() open: EventEmitter = new EventEmitter();
+ @Output() close: EventEmitter = new EventEmitter();
toggle() {
this.visible = !this.visible;
if (this.visible) {
- ObservableWrapper.callNext(this.openHandler, null);
+ ObservableWrapper.callNext(this.open, null);
} else {
- ObservableWrapper.callNext(this.closeHandler, null);
+ ObservableWrapper.callNext(this.close, null);
}
}
}
diff --git a/modules_dart/transform/lib/src/transform/common/directive_metadata_reader.dart b/modules_dart/transform/lib/src/transform/common/directive_metadata_reader.dart
index ef325acfd2e9..383d437335e1 100644
--- a/modules_dart/transform/lib/src/transform/common/directive_metadata_reader.dart
+++ b/modules_dart/transform/lib/src/transform/common/directive_metadata_reader.dart
@@ -225,6 +225,95 @@ class _DirectiveMetadataVisitor extends Object
return null;
}
+ @override
+ Object visitFieldDeclaration(FieldDeclaration node) {
+ for (var variable in node.fields.variables) {
+ for (var meta in node.metadata) {
+ if (_isAnnotation(meta, 'Output')) {
+ final renamed = _getRenamedValue(meta);
+ if (renamed != null) {
+ _outputs.add('${variable.name}: ${renamed}');
+ } else {
+ _outputs.add('${variable.name}');
+ }
+ }
+
+ if (_isAnnotation(meta, 'Input')) {
+ final renamed = _getRenamedValue(meta);
+ if (renamed != null) {
+ _inputs.add('${variable.name}: ${renamed}');
+ } else {
+ _inputs.add('${variable.name}');
+ }
+ }
+
+ if (_isAnnotation(meta, 'HostBinding')) {
+ final renamed = _getRenamedValue(meta);
+ if (renamed != null) {
+ _host['[${renamed}]'] = '${variable.name}';
+ } else {
+ _host['[${variable.name}]'] = '${variable.name}';
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @override
+ Object visitMethodDeclaration(MethodDeclaration node) {
+ for (var meta in node.metadata) {
+ if (_isAnnotation(meta, 'HostListener')) {
+ if (meta.arguments.arguments.length == 0 || meta.arguments.arguments.length > 2) {
+ throw new ArgumentError(
+ 'Incorrect value passed to HostListener. Expected 1 or 2.');
+ }
+
+ final eventName = _getHostListenerEventName(meta);
+ final params = _getHostListenerParams(meta);
+ _host['(${eventName})'] = '${node.name}($params)';
+ }
+ }
+ return null;
+ }
+
+ //TODO Use AnnotationMatcher instead of string matching
+ bool _isAnnotation(Annotation node, String annotationName) {
+ var id = node.name;
+ final name = id is PrefixedIdentifier ? '${id.identifier}' : '$id';
+ return name == annotationName;
+ }
+
+ String _getRenamedValue(Annotation node) {
+ if (node.arguments.arguments.length == 1) {
+ final renamed = naiveEval(node.arguments.arguments.single);
+ if (renamed is! String) {
+ throw new ArgumentError(
+ 'Incorrect value. Expected a String, but got "${renamed}".');
+ }
+ return renamed;
+ } else {
+ return null;
+ }
+ }
+
+ String _getHostListenerEventName(Annotation node) {
+ final name = naiveEval(node.arguments.arguments.first);
+ if (name is! String) {
+ throw new ArgumentError(
+ 'Incorrect event name. Expected a String, but got "${name}".');
+ }
+ return name;
+ }
+
+ String _getHostListenerParams(Annotation node) {
+ if (node.arguments.arguments.length == 2) {
+ return naiveEval(node.arguments.arguments[1]).join(',');
+ } else {
+ return "";
+ }
+ }
+
@override
Object visitClassDeclaration(ClassDeclaration node) {
node.metadata.accept(this);
@@ -237,6 +326,8 @@ class _DirectiveMetadataVisitor extends Object
_lifecycleHooks = node.implementsClause != null
? node.implementsClause.accept(_lifecycleVisitor)
: const [];
+
+ node.members.accept(this);
}
return null;
}
diff --git a/modules_dart/transform/test/transform/directive_processor/all_tests.dart b/modules_dart/transform/test/transform/directive_processor/all_tests.dart
index 9597cd318fe8..6ea05410ad2c 100644
--- a/modules_dart/transform/test/transform/directive_processor/all_tests.dart
+++ b/modules_dart/transform/test/transform/directive_processor/all_tests.dart
@@ -494,6 +494,34 @@ void allTests() {
..prefix = 'dep2');
});
+ it('should merge `outputs` from the annotation and fields.',
+ () async {
+ var model = await _testCreateModel('directives_files/components.dart');
+ expect(model.types['ComponentWithOutputs'].outputs).
+ toEqual({'a': 'a', 'b': 'b', 'c': 'renamed'});
+ });
+
+ it('should merge `inputs` from the annotation and fields.',
+ () async {
+ var model = await _testCreateModel('directives_files/components.dart');
+ expect(model.types['ComponentWithInputs'].inputs).
+ toEqual({'a': 'a', 'b': 'b', 'c': 'renamed'});
+ });
+
+ it('should merge host bindings from the annotation and fields.',
+ () async {
+ var model = await _testCreateModel('directives_files/components.dart');
+ expect(model.types['ComponentWithHostBindings'].hostProperties).
+ toEqual({'a': 'a', 'b': 'b', 'renamed': 'c'});
+ });
+
+ it('should merge host listeners from the annotation and fields.',
+ () async {
+ var model = await _testCreateModel('directives_files/components.dart');
+ expect(model.types['ComponentWithHostListeners'].hostListeners).
+ toEqual({'a': 'onA()', 'b': 'onB()', 'c': 'onC(\$event.target,\$event.target.value)'});
+ });
+
it('should warn if @Component has a `template` and @View is present.',
() async {
final logger = new RecordingLogger();
diff --git a/modules_dart/transform/test/transform/directive_processor/directives_files/components.dart b/modules_dart/transform/test/transform/directive_processor/directives_files/components.dart
index bba9769f3c78..beb545c82523 100644
--- a/modules_dart/transform/test/transform/directive_processor/directives_files/components.dart
+++ b/modules_dart/transform/test/transform/directive_processor/directives_files/components.dart
@@ -1,7 +1,7 @@
library angular2.test.transform.directive_processor.directive_files.components;
import 'package:angular2/angular2.dart'
- show Component, Directive, View, NgElement;
+ show Component, Directive, View, NgElement, Output, Input;
import 'dep1.dart';
import 'dep2.dart' as dep2;
@@ -18,3 +18,47 @@ class ViewFirst {}
template: '',
directives: [Dep, dep2.Dep])
class ComponentOnly {}
+
+@Component(
+ selector: 'component-with-outputs',
+ template: '',
+ outputs: ['a']
+)
+class ComponentWithOutputs {
+ @Output() Object b;
+ @Output('renamed') Object c;
+}
+
+@Component(
+ selector: 'component-with-inputs',
+ template: '',
+ inputs: ['a']
+)
+class ComponentWithInputs {
+ @Input() Object b;
+ @Input('renamed') Object c;
+}
+
+@Component(
+ selector: 'component-with-inputs',
+ template: '',
+ host: {
+ '[a]':'a'
+ }
+)
+class ComponentWithHostBindings {
+ @HostBinding() Object b;
+ @HostBinding('renamed') Object c;
+}
+
+@Component(
+ selector: 'component-with-inputs',
+ template: '',
+ host: {
+ '(a)':'onA()'
+ }
+)
+class ComponentWithHostListeners {
+ @HostListener('b') void onB() {}
+ @HostListener('c', ['\$event.target', '\$event.target.value']) void onC(t,v) {}
+}