X Tutup
Skip to content

Commit 16bc238

Browse files
committed
feat(core): make transformers handle @Input/@Output/@HostBinding/@HostListener
Closes #5080
1 parent 045919b commit 16bc238

File tree

3 files changed

+164
-1
lines changed

3 files changed

+164
-1
lines changed

modules_dart/transform/lib/src/transform/common/directive_metadata_reader.dart

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,95 @@ class _DirectiveMetadataVisitor extends Object
225225
return null;
226226
}
227227

228+
@override
229+
Object visitFieldDeclaration(FieldDeclaration node) {
230+
for (var variable in node.fields.variables) {
231+
for (var meta in node.metadata) {
232+
if (_isAnnotation(meta, 'Output')) {
233+
final renamed = _getRenamedValue(meta);
234+
if (renamed != null) {
235+
_outputs.add('${variable.name}: ${renamed}');
236+
} else {
237+
_outputs.add('${variable.name}');
238+
}
239+
}
240+
241+
if (_isAnnotation(meta, 'Input')) {
242+
final renamed = _getRenamedValue(meta);
243+
if (renamed != null) {
244+
_inputs.add('${variable.name}: ${renamed}');
245+
} else {
246+
_inputs.add('${variable.name}');
247+
}
248+
}
249+
250+
if (_isAnnotation(meta, 'HostBinding')) {
251+
final renamed = _getRenamedValue(meta);
252+
if (renamed != null) {
253+
_host['[${renamed}]'] = '${variable.name}';
254+
} else {
255+
_host['[${variable.name}]'] = '${variable.name}';
256+
}
257+
}
258+
}
259+
}
260+
return null;
261+
}
262+
263+
@override
264+
Object visitMethodDeclaration(MethodDeclaration node) {
265+
for (var meta in node.metadata) {
266+
if (_isAnnotation(meta, 'HostListener')) {
267+
if (meta.arguments.arguments.length == 0 || meta.arguments.arguments.length > 2) {
268+
throw new ArgumentError(
269+
'Incorrect value passed to HostListener. Expected 1 or 2.');
270+
}
271+
272+
final eventName = _getHostListenerEventName(meta);
273+
final params = _getHostListenerParams(meta);
274+
_host['(${eventName})'] = '${node.name}($params)';
275+
}
276+
}
277+
return null;
278+
}
279+
280+
//TODO Use AnnotationMatcher instead of string matching
281+
bool _isAnnotation(Annotation node, String annotationName) {
282+
var id = node.name;
283+
final name = id is PrefixedIdentifier ? '${id.identifier}' : '$id';
284+
return name == annotationName;
285+
}
286+
287+
String _getRenamedValue(Annotation node) {
288+
if (node.arguments.arguments.length == 1) {
289+
final renamed = naiveEval(node.arguments.arguments.single);
290+
if (renamed is! String) {
291+
throw new ArgumentError(
292+
'Incorrect value. Expected a String, but got "${renamed}".');
293+
}
294+
return renamed;
295+
} else {
296+
return null;
297+
}
298+
}
299+
300+
String _getHostListenerEventName(Annotation node) {
301+
final name = naiveEval(node.arguments.arguments.first);
302+
if (name is! String) {
303+
throw new ArgumentError(
304+
'Incorrect event name. Expected a String, but got "${name}".');
305+
}
306+
return name;
307+
}
308+
309+
String _getHostListenerParams(Annotation node) {
310+
if (node.arguments.arguments.length == 2) {
311+
return naiveEval(node.arguments.arguments[1]).join(',');
312+
} else {
313+
return "";
314+
}
315+
}
316+
228317
@override
229318
Object visitClassDeclaration(ClassDeclaration node) {
230319
node.metadata.accept(this);
@@ -237,6 +326,8 @@ class _DirectiveMetadataVisitor extends Object
237326
_lifecycleHooks = node.implementsClause != null
238327
? node.implementsClause.accept(_lifecycleVisitor)
239328
: const [];
329+
330+
node.members.accept(this);
240331
}
241332
return null;
242333
}

modules_dart/transform/test/transform/directive_processor/all_tests.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,34 @@ void allTests() {
494494
..prefix = 'dep2');
495495
});
496496

497+
it('should merge `outputs` from the annotation and fields.',
498+
() async {
499+
var model = await _testCreateModel('directives_files/components.dart');
500+
expect(model.types['ComponentWithOutputs'].outputs).
501+
toEqual({'a': 'a', 'b': 'b', 'c': 'renamed'});
502+
});
503+
504+
it('should merge `inputs` from the annotation and fields.',
505+
() async {
506+
var model = await _testCreateModel('directives_files/components.dart');
507+
expect(model.types['ComponentWithInputs'].inputs).
508+
toEqual({'a': 'a', 'b': 'b', 'c': 'renamed'});
509+
});
510+
511+
it('should merge host bindings from the annotation and fields.',
512+
() async {
513+
var model = await _testCreateModel('directives_files/components.dart');
514+
expect(model.types['ComponentWithHostBindings'].hostProperties).
515+
toEqual({'a': 'a', 'b': 'b', 'renamed': 'c'});
516+
});
517+
518+
it('should merge host listeners from the annotation and fields.',
519+
() async {
520+
var model = await _testCreateModel('directives_files/components.dart');
521+
expect(model.types['ComponentWithHostListeners'].hostListeners).
522+
toEqual({'a': 'onA()', 'b': 'onB()', 'c': 'onC(\$event.target,\$event.target.value)'});
523+
});
524+
497525
it('should warn if @Component has a `template` and @View is present.',
498526
() async {
499527
final logger = new RecordingLogger();

modules_dart/transform/test/transform/directive_processor/directives_files/components.dart

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
library angular2.test.transform.directive_processor.directive_files.components;
22

33
import 'package:angular2/angular2.dart'
4-
show Component, Directive, View, NgElement;
4+
show Component, Directive, View, NgElement, Output, Input;
55
import 'dep1.dart';
66
import 'dep2.dart' as dep2;
77

@@ -18,3 +18,47 @@ class ViewFirst {}
1818
template: '<dep1></dep1><dep2></dep2>',
1919
directives: [Dep, dep2.Dep])
2020
class ComponentOnly {}
21+
22+
@Component(
23+
selector: 'component-with-outputs',
24+
template: '<dep1></dep1><dep2></dep2>',
25+
outputs: ['a']
26+
)
27+
class ComponentWithOutputs {
28+
@Output() Object b;
29+
@Output('renamed') Object c;
30+
}
31+
32+
@Component(
33+
selector: 'component-with-inputs',
34+
template: '<dep1></dep1><dep2></dep2>',
35+
inputs: ['a']
36+
)
37+
class ComponentWithInputs {
38+
@Input() Object b;
39+
@Input('renamed') Object c;
40+
}
41+
42+
@Component(
43+
selector: 'component-with-inputs',
44+
template: '<dep1></dep1><dep2></dep2>',
45+
host: {
46+
'[a]':'a'
47+
}
48+
)
49+
class ComponentWithHostBindings {
50+
@HostBinding() Object b;
51+
@HostBinding('renamed') Object c;
52+
}
53+
54+
@Component(
55+
selector: 'component-with-inputs',
56+
template: '<dep1></dep1><dep2></dep2>',
57+
host: {
58+
'(a)':'onA()'
59+
}
60+
)
61+
class ComponentWithHostListeners {
62+
@HostListener('b') void onB() {}
63+
@HostListener('c', ['\$event.target', '\$event.target.value']) void onC(t,v) {}
64+
}

0 commit comments

Comments
 (0)
X Tutup