diff --git a/lib/block.dart b/lib/block.dart index 2e3f29f87..5c336321b 100644 --- a/lib/block.dart +++ b/lib/block.dart @@ -112,6 +112,11 @@ class Block implements ElementWrapper { } else { nodeModule.type(type, type, visibility: visibility); } + for (var publishType in ref.directive.$publishTypes) { + nodeModule.factory(publishType, + (Injector injector) => injector.get(type), + visibility: visibility); + } nodeAttrs[ref.directive.$name] = ref.value; if (ref.directive.isStructural) { blockListFactory = (Injector injector) => $blockListFactory([node], ref.blockTypes, injector); diff --git a/lib/directive.dart b/lib/directive.dart index 7fc4378a0..0e3972f14 100644 --- a/lib/directive.dart +++ b/lib/directive.dart @@ -4,11 +4,20 @@ String _COMPONENT = '-component'; String _DIRECTIVE = '-directive'; String _ATTR_DIRECTIVE = '-attr' + _DIRECTIVE; -class NgComponent { +class NgAnnotationBase { + final String visibility; + final List publishTypes; + + const NgAnnotationBase({ + this.visibility: NgDirective.LOCAL_VISIBILITY, + this.publishTypes + }); +} + +class NgComponent extends NgAnnotationBase { final String template; final String templateUrl; final String cssUrl; - final String visibility; final Map map; final String publishAs; final bool applyAuthorStyles; @@ -18,15 +27,16 @@ class NgComponent { this.template, this.templateUrl, this.cssUrl, - this.visibility: NgDirective.LOCAL_VISIBILITY, + visibility, this.map, this.publishAs, this.applyAuthorStyles, - this.resetStyleInheritance - }); + this.resetStyleInheritance, + publishTypes : const [] + }) : super(visibility: visibility, publishTypes: publishTypes); } -class NgDirective { +class NgDirective extends NgAnnotationBase { static const String LOCAL_VISIBILITY = 'local'; static const String CHILDREN_VISIBILITY = 'children'; static const String DIRECT_CHILDREN_VISIBILITY = 'direct_children'; @@ -34,14 +44,14 @@ class NgDirective { final String selector; final String transclude; final int priority; - final String visibility; const NgDirective({ this.selector, this.transclude, this.priority : 0, - this.visibility: LOCAL_VISIBILITY - }); + visibility, + publishTypes : const [] + }) : super(visibility: visibility, publishTypes: publishTypes); } /** @@ -55,6 +65,8 @@ class NgShadowRootOptions { this.resetStyleInheritance = false]); } +Map _directiveCache = new Map(); + // TODO(pavelgj): Get rid of Directive and use NgComponent/NgDirective directly. class Directive { Type type; @@ -70,14 +82,23 @@ class Directive { Map $map; String $visibility; NgShadowRootOptions $shadowRootOptions = new NgShadowRootOptions(); + List $publishTypes = []; bool isComponent = false; bool isStructural = false; - Directive(this.type) { + Directive._new(Type this.type); + + factory Directive(Type type) { + var instance = _directiveCache[type]; + if (instance != null) { + return instance; + } + + instance = new Directive._new(type); var name = type.toString(); var isAttr = false; - $name = name.splitMapJoin( + instance.$name = name.splitMapJoin( new RegExp(r'[A-Z]'), onMatch: (m) => '-' + m.group(0).toLowerCase()) .substring(1); @@ -91,38 +112,48 @@ class Directive { var selector; if (directive != null) { selector = directive.selector; - $transclude = directive.transclude; - $priority = directive.priority; - $visibility = directive.visibility; + instance.$transclude = directive.transclude; + instance.$priority = directive.priority; + instance.$visibility = directive.visibility; + instance.$publishTypes = directive.publishTypes; } if (component != null) { - $template = component.template; - $templateUrl = component.templateUrl; - $cssUrl = component.cssUrl; - $visibility = component.visibility; - $map = component.map; - $publishAs = component.publishAs; - $shadowRootOptions = new NgShadowRootOptions(component.applyAuthorStyles, - component.resetStyleInheritance); + instance.$template = component.template; + instance.$templateUrl = component.templateUrl; + instance.$cssUrl = component.cssUrl; + instance.$visibility = component.visibility; + instance.$map = component.map; + instance.$publishAs = component.publishAs; + instance.$shadowRootOptions = + new NgShadowRootOptions(component.applyAuthorStyles, + component.resetStyleInheritance); + instance.$publishTypes = component.publishTypes; } if (selector != null) { - $name = selector; - } else if ($name.endsWith(_ATTR_DIRECTIVE)) { - $name = '[${$name.substring(0, $name.length - _ATTR_DIRECTIVE.length)}]'; - } else if ($name.endsWith(_DIRECTIVE)) { - $name = $name.substring(0, $name.length - _DIRECTIVE.length); - } else if ($name.endsWith(_COMPONENT)) { - isComponent = true; - $name = $name.substring(0, $name.length - _COMPONENT.length); + instance.$name = selector; + } else if (instance.$name.endsWith(_ATTR_DIRECTIVE)) { + var attrName = instance.$name. + substring(0, instance.$name.length - _ATTR_DIRECTIVE.length); + instance.$name = '[$attrName]'; + } else if (instance.$name.endsWith(_DIRECTIVE)) { + instance.$name = instance.$name. + substring(0, instance.$name.length - _DIRECTIVE.length); + } else if (instance.$name.endsWith(_COMPONENT)) { + instance.isComponent = true; + instance.$name = instance.$name. + substring(0, instance.$name.length - _COMPONENT.length); } else { - throw "Directive name '$name' must end with $_DIRECTIVE, $_ATTR_DIRECTIVE, $_COMPONENT or have a \$selector field."; + throw "Directive name '$name' must end with $_DIRECTIVE, " + "$_ATTR_DIRECTIVE, $_COMPONENT or have a \$selector field."; } - isStructural = $transclude != null; - if (isComponent && $map == null) { - $map = new Map(); + instance.isStructural = instance.$transclude != null; + if (instance.isComponent && instance.$map == null) { + instance.$map = new Map(); } + _directiveCache[type] = instance; + return instance; } } diff --git a/lib/mirrors.dart b/lib/mirrors.dart index d7be90456..64300029e 100644 --- a/lib/mirrors.dart +++ b/lib/mirrors.dart @@ -30,7 +30,16 @@ reflectStaticField(Type type, String field) { } // TODO(pavelgj): cache. -Iterable reflectMetadata(Type type, Type metadata) => - fastReflectClass(type).metadata.where( - (InstanceMirror im) => im.reflectee.runtimeType == metadata) - .map((InstanceMirror im) => im.reflectee); +Iterable reflectMetadata(Type type, Type metadata) { + var meta; + try { + meta = fastReflectClass(type).metadata; + } catch(e) { + // TODO(pavelgj): A temporary workaround for http://dartbug.com/11960 + if (e.message == 'Function.prototype.toString is not generic') { + meta = []; + } + } + return meta.where((InstanceMirror im) => im.reflectee.runtimeType == metadata) + .map((InstanceMirror im) => im.reflectee); +} diff --git a/test/compiler_spec.dart b/test/compiler_spec.dart index c11ea2239..1973738f4 100644 --- a/test/compiler_spec.dart +++ b/test/compiler_spec.dart @@ -54,6 +54,17 @@ class IncludeTranscludeAttrDirective { } } +class PublishTypesDirectiveSuperType { +} + +@NgDirective(publishTypes: const [PublishTypesDirectiveSuperType]) +class PublishTypesAttrDirective implements PublishTypesDirectiveSuperType { + static Injector _injector; + PublishTypesAttrDirective(Injector injector) { + _injector = injector; + } +} + main() { describe('dte.compiler', () { @@ -65,6 +76,7 @@ main() { beforeEach(module((AngularModule module) { module ..directive(TabComponent) + ..directive(PublishTypesAttrDirective) ..directive(PaneComponent) ..directive(SimpleTranscludeInAttachAttrDirective) ..directive(IncludeTranscludeAttrDirective) @@ -487,6 +499,13 @@ main() { nextTurn(); expect(element.textWithShadow()).toEqual('WORKED'); }))); + + it('should "publish" controller to injector under provided publishTypes', inject(() { + var element = $(r'
'); + $compile(element)(injector, element); + expect(PublishTypesAttrDirective._injector.get(PublishTypesAttrDirective)). + toBe(PublishTypesAttrDirective._injector.get(PublishTypesDirectiveSuperType)); + })); }); describe('controller scoping', () {