From 962ecfb2a0360f784885b186f84c0704552c2e29 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 16 Jun 2014 22:46:46 -0700 Subject: [PATCH 1/3] perf(View): Improve View instantiation speed and memory consumption. View contains many injectors which are expensive in both speed and memory. The New DirectiveInjector assumes that there are no more than 10 directives per element and can be a lot more efficient than a array/hash lookup and those it can be faster as well as smaller. This change makes View instantiation speed 4x faster in Dartium VM. BREAKING CHANGE: - Injector no longer supports visibility - The Directive:module instead of returning Module now takes DirectiveModule (which supports visibility) - Application Injector and DirectiveInjector now have separate trees. (The root if DirectiveInjector is ApplicationInjector) --- benchmark/pubspec.lock | 2 +- benchmark/pubspec.yaml | 1 + benchmark/web/tree.dart | 14 +- bin/parser_generator_for_spec.dart | 14 +- lib/application.dart | 15 +- lib/application_factory.dart | 3 - lib/application_factory_static.dart | 25 +- lib/cache/cache.dart | 2 + lib/change_detection/ast_parser.dart | 1 + .../dirty_checking_change_detector.dart | 7 +- lib/change_detection/prototype_map.dart | 2 + lib/change_detection/watch_group.dart | 37 +- lib/core/annotation.dart | 4 +- lib/core/annotation_src.dart | 63 ++- lib/core/formatter.dart | 33 +- lib/core/module.dart | 3 + lib/core/module_internal.dart | 8 +- lib/core/parser/dynamic_parser.dart | 1 + lib/core/parser/lexer.dart | 1 + lib/core/parser/syntax.dart | 1 - lib/core/registry.dart | 70 +-- lib/core/registry_static.dart | 2 +- lib/core/static_keys.dart | 15 +- lib/core_dom/common.dart | 22 +- lib/core_dom/directive_injector.dart | 403 ++++++++++++++++++ lib/core_dom/directive_map.dart | 41 +- lib/core_dom/element_binder.dart | 164 +++---- lib/core_dom/module_internal.dart | 11 +- lib/core_dom/selector.dart | 18 +- .../shadow_dom_component_factory.dart | 46 +- lib/core_dom/static_keys.dart | 52 +-- lib/core_dom/tagging_view_factory.dart | 58 ++- .../transcluding_component_factory.dart | 45 +- lib/core_dom/view.dart | 42 +- lib/core_dom/view_factory.dart | 55 ++- lib/directive/module.dart | 3 +- lib/directive/ng_base_css.dart | 4 +- lib/directive/ng_control.dart | 2 +- lib/directive/ng_form.dart | 7 +- lib/directive/ng_if.dart | 39 +- lib/directive/ng_include.dart | 11 +- lib/directive/ng_model.dart | 12 +- lib/directive/ng_model_select.dart | 13 +- lib/directive/ng_repeat.dart | 2 - lib/directive/ng_switch.dart | 1 - lib/introspection.dart | 4 +- lib/introspection_js.dart | 9 +- lib/mock/module.dart | 5 +- lib/mock/probe.dart | 8 +- lib/mock/test_bed.dart | 7 +- lib/mock/test_injection.dart | 39 +- lib/playback/playback_http.dart | 1 + lib/routing/module.dart | 8 +- lib/routing/ng_bind_route.dart | 9 +- lib/routing/ng_view.dart | 29 +- lib/tools/expression_extractor.dart | 1 - .../transformer/expression_generator.dart | 13 +- lib/tools/transformer/options.dart | 2 +- .../transformer/static_angular_generator.dart | 4 - lib/transformer.dart | 11 +- pubspec.lock | 2 +- pubspec.yaml | 6 +- scripts/git/validate-commit-msg.js | 3 +- test/_specs.dart | 3 +- test/angular_spec.dart | 24 +- test/core/annotation_src_spec.dart | 6 +- test/core/core_directive_spec.dart | 18 +- test/core/parser/parser_spec.dart | 12 +- test/core/registry_spec.dart | 64 --- test/core/templateurl_spec.dart | 24 +- test/core_dom/compiler_spec.dart | 48 ++- test/core_dom/directive_injector_spec.dart | 122 ++++++ test/core_dom/event_handler_spec.dart | 7 +- test/core_dom/http_spec.dart | 3 +- test/core_dom/mustache_spec.dart | 10 +- test/core_dom/selector_spec.dart | 124 +++--- test/core_dom/view_spec.dart | 66 +-- test/directive/ng_bind_html_spec.dart | 6 +- test/directive/ng_bind_spec.dart | 2 +- test/directive/ng_form_spec.dart | 6 +- test/directive/ng_if_spec.dart | 2 +- test/directive/ng_model_spec.dart | 18 +- test/directive/ng_non_bindable_spec.dart | 2 +- test/directive/ng_repeat_spec.dart | 37 +- test/formatter/currency_spec.dart | 2 +- test/formatter/date_spec.dart | 2 +- test/formatter/filter_spec.dart | 2 +- test/formatter/number_spec.dart | 2 +- test/introspection_spec.dart | 6 +- test/io/expression_extractor_spec.dart | 4 +- test/routing/ng_bind_route_spec.dart | 8 +- test/routing/ng_view_spec.dart | 3 +- test/routing/routing_spec.dart | 2 +- .../static_angular_generator_spec.dart | 6 +- 94 files changed, 1351 insertions(+), 826 deletions(-) create mode 100644 lib/core_dom/directive_injector.dart delete mode 100644 test/core/registry_spec.dart create mode 100644 test/core_dom/directive_injector_spec.dart diff --git a/benchmark/pubspec.lock b/benchmark/pubspec.lock index 4fceed7bd..e3197a1cc 100644 --- a/benchmark/pubspec.lock +++ b/benchmark/pubspec.lock @@ -38,7 +38,7 @@ packages: di: description: di source: hosted - version: "1.0.0" + version: "2.0.0-alpha.1" html5lib: description: html5lib source: hosted diff --git a/benchmark/pubspec.yaml b/benchmark/pubspec.yaml index 141c3f449..0f46da616 100644 --- a/benchmark/pubspec.yaml +++ b/benchmark/pubspec.yaml @@ -8,6 +8,7 @@ dev_dependencies: benchmark_harness: '>=1.0.0' unittest: '>=0.10.1 <0.12.0' mock: '>=0.10.0 <0.12.0' + di: '>=2.0.0-alpha.1' transformers: - angular - $dart2js: diff --git a/benchmark/web/tree.dart b/benchmark/web/tree.dart index 7e40f139a..0fa2e84a8 100644 --- a/benchmark/web/tree.dart +++ b/benchmark/web/tree.dart @@ -1,4 +1,5 @@ import 'package:di/di.dart'; +import 'package:di/di_dynamic.dart'; import 'package:angular/angular.dart'; import 'package:angular/core_dom/module_internal.dart'; import 'package:angular/application_factory.dart'; @@ -246,15 +247,16 @@ class NgFreeTreeClass implements ShadowRootAware { // Main function runs the benchmark. main() { + setupModuleTypeReflector(); var cleanup, createDom; var module = new Module() - ..type(TreeComponent) - ..type(TreeUrlComponent) - ..type(NgFreeTree) - ..type(NgFreeTreeScoped) - ..type(NgFreeTreeClass) - ..factory(ScopeDigestTTL, (i) => new ScopeDigestTTL.value(15)) + ..bind(TreeComponent) + ..bind(TreeUrlComponent) + ..bind(NgFreeTree) + ..bind(NgFreeTreeScoped) + ..bind(NgFreeTreeClass) + ..bind(ScopeDigestTTL, toFactory: () => new ScopeDigestTTL.value(15)) ..bind(CompilerConfig, toValue: new CompilerConfig.withOptions(elementProbeEnabled: false)); var injector = applicationFactory().addModule(module).run(); diff --git a/bin/parser_generator_for_spec.dart b/bin/parser_generator_for_spec.dart index 8aeca334c..020770b87 100644 --- a/bin/parser_generator_for_spec.dart +++ b/bin/parser_generator_for_spec.dart @@ -1,15 +1,21 @@ import 'dart:io' as io; import 'package:di/di.dart'; -import 'package:di/dynamic_injector.dart'; +import 'package:di/di_dynamic.dart'; +import 'package:angular/cache/module.dart'; +import 'package:angular/core/parser/lexer.dart'; import 'package:angular/core/parser/parser.dart'; import 'package:angular/tools/parser_getter_setter/generator.dart'; main(arguments) { - Module module = new Module()..bind(Parser, toImplementation: DynamicParser); + setupModuleTypeReflector(); + Module module = new Module() + ..bind(Lexer) + ..bind(ParserGetterSetter) + ..bind(Parser, toImplementation: DynamicParser) + ..install(new CacheModule()); module.bind(ParserBackend, toImplementation: DartGetterSetterGen); - Injector injector = new DynamicInjector(modules: [module], - allowImplicitInjection: true); + Injector injector = new ModuleInjector([module]); // List generated using: // node node_modules/karma/bin/karma run | grep -Eo ":XNAY:.*:XNAY:" | sed -e 's/:XNAY://g' | sed -e "s/^/'/" | sed -e "s/$/',/" | sort | uniq > missing_expressions diff --git a/lib/application.dart b/lib/application.dart index 421e7f470..a4b637fe2 100644 --- a/lib/application.dart +++ b/lib/application.dart @@ -83,6 +83,7 @@ import 'package:angular/routing/module.dart'; import 'package:angular/introspection_js.dart'; import 'package:angular/core_dom/static_keys.dart'; +import 'package:angular/core_dom/directive_injector.dart'; /** * This is the top level module which describes all Angular components, @@ -95,6 +96,7 @@ import 'package:angular/core_dom/static_keys.dart'; */ class AngularModule extends Module { AngularModule() { + DirectiveInjector.initUID(); install(new CacheModule()); install(new CoreModule()); install(new CoreDomModule()); @@ -103,7 +105,6 @@ class AngularModule extends Module { install(new PerfModule()); install(new RoutingModule()); - bind(MetadataExtractor); bind(Expando, toValue: elementExpando); } } @@ -150,7 +151,7 @@ abstract class Application { modules.add(ngModule); ngModule..bind(VmTurnZone, toValue: zone) ..bind(Application, toValue: this) - ..bind(dom.Node, toFactory: (i) => i.getByKey(new Key(Application)).element); + ..bind(dom.Node, toFactory: (Application app) => app.element, inject: [Application]); } /** @@ -171,9 +172,11 @@ abstract class Application { ExceptionHandler exceptionHandler = injector.getByKey(EXCEPTION_HANDLER_KEY); initializeDateFormatting(null, null).then((_) { try { - var compiler = injector.getByKey(COMPILER_KEY); - var viewFactory = compiler(rootElements, injector.getByKey(DIRECTIVE_MAP_KEY)); - viewFactory(injector, rootElements); + Compiler compiler = injector.getByKey(COMPILER_KEY); + DirectiveMap directiveMap = injector.getByKey(DIRECTIVE_MAP_KEY); + RootScope rootScope = injector.getByKey(ROOT_SCOPE_KEY); + ViewFactory viewFactory = compiler(rootElements, directiveMap); + viewFactory(rootScope, null, injector, rootElements); } catch (e, s) { exceptionHandler(e, s); } @@ -186,5 +189,5 @@ abstract class Application { * Creates an injector function that can be used for retrieving services as well as for * dependency injection. */ - Injector createInjector(); + Injector createInjector() => new ModuleInjector(modules); } diff --git a/lib/application_factory.dart b/lib/application_factory.dart index 25b95cd40..0baa76921 100644 --- a/lib/application_factory.dart +++ b/lib/application_factory.dart @@ -9,7 +9,6 @@ */ library angular.app.factory; -import 'package:di/dynamic_injector.dart'; import 'package:angular/angular.dart'; import 'package:angular/core/registry.dart'; import 'package:angular/core/parser/parser.dart' show ClosureMap; @@ -64,8 +63,6 @@ class _DynamicApplication extends Application { ..bind(FieldGetterFactory, toImplementation: DynamicFieldGetterFactory) ..bind(ClosureMap, toImplementation: DynamicClosureMap); } - - Injector createInjector() => new DynamicInjector(modules: modules); } /** diff --git a/lib/application_factory_static.dart b/lib/application_factory_static.dart index 3379177ff..9a85aead2 100644 --- a/lib/application_factory_static.dart +++ b/lib/application_factory_static.dart @@ -30,7 +30,6 @@ */ library angular.app.factory.static; -import 'package:di/static_injector.dart'; import 'package:di/di.dart' show TypeFactory, Injector; import 'package:angular/application.dart'; import 'package:angular/core/registry.dart'; @@ -46,9 +45,7 @@ export 'package:angular/change_detection/change_detection.dart' show FieldSetter; class _StaticApplication extends Application { - final Map typeFactories; - - _StaticApplication(Map this.typeFactories, + _StaticApplication( Map metadata, Map fieldGetters, Map fieldSetters, @@ -58,9 +55,6 @@ class _StaticApplication extends Application { ..bind(FieldGetterFactory, toValue: new StaticFieldGetterFactory(fieldGetters)) ..bind(ClosureMap, toValue: new StaticClosureMap(fieldGetters, fieldSetters, symbols)); } - - Injector createInjector() => - new StaticInjector(modules: modules, typeFactories: typeFactories); } /** @@ -81,20 +75,19 @@ class _StaticApplication extends Application { * becomes: * * main() { - * staticApplication(generated_static_injector.factories, - * generated_static_metadata.typeAnnotations, - * generated_static_expressions.getters, - * generated_static_expressions.setters, - * generated_static_expressions.symbols) - * .addModule(new Module()..bind(HelloWorldController)) - * .run(); + * staticApplication( + * generated_static_metadata.typeAnnotations, + * generated_static_expressions.getters, + * generated_static_expressions.setters, + * generated_static_expressions.symbols) + * .addModule(new Module()..bind(HelloWorldController)) + * .run(); * */ Application staticApplicationFactory( - Map typeFactories, Map metadata, Map fieldGetters, Map fieldSetters, Map symbols) { - return new _StaticApplication(typeFactories, metadata, fieldGetters, fieldSetters, symbols); + return new _StaticApplication(metadata, fieldGetters, fieldSetters, symbols); } diff --git a/lib/cache/cache.dart b/lib/cache/cache.dart index 248c7f9ed..1dd439049 100644 --- a/lib/cache/cache.dart +++ b/lib/cache/cache.dart @@ -69,6 +69,8 @@ class UnboundedCache implements Cache { CacheStats stats() => new CacheStats(capacity, size, _hits, _misses); // Debugging helper. String toString() => "[$runtimeType: size=${_entries.length}, items=$_entries]"; + int get length => -1; + void clear() {} } diff --git a/lib/change_detection/ast_parser.dart b/lib/change_detection/ast_parser.dart index 46129ee5a..77444e899 100644 --- a/lib/change_detection/ast_parser.dart +++ b/lib/change_detection/ast_parser.dart @@ -2,6 +2,7 @@ library angular.change_detection.ast_parser; import 'dart:collection'; +import 'package:di/di.dart' show Injectable; import 'package:angular/core/parser/syntax.dart' as syntax; import 'package:angular/core/parser/parser.dart'; import 'package:angular/core/formatter.dart'; diff --git a/lib/change_detection/dirty_checking_change_detector.dart b/lib/change_detection/dirty_checking_change_detector.dart index d59ebb7e3..c927ff9dd 100644 --- a/lib/change_detection/dirty_checking_change_detector.dart +++ b/lib/change_detection/dirty_checking_change_detector.dart @@ -1417,7 +1417,12 @@ class DuplicateMap { final map = new HashMap(); void put(ItemRecord record, [ItemRecord insertBefore = null]) { - map.putIfAbsent(record.item, () => new _DuplicateItemRecordList()).add(record, insertBefore); + var key = record.item; + _DuplicateItemRecordList duplicates = map[key]; + if (duplicates == null) { + duplicates = map[key] = new _DuplicateItemRecordList(); + } + duplicates.add(record, insertBefore); } /** diff --git a/lib/change_detection/prototype_map.dart b/lib/change_detection/prototype_map.dart index 3c634b36c..b324a12a1 100644 --- a/lib/change_detection/prototype_map.dart +++ b/lib/change_detection/prototype_map.dart @@ -34,4 +34,6 @@ class PrototypeMap implements Map { } // todo(vbe) include prototype ? V putIfAbsent(key, fn) => self.putIfAbsent(key, fn); + + toString() => self.toString(); } diff --git a/lib/change_detection/watch_group.dart b/lib/change_detection/watch_group.dart index 6fea24311..701b7574c 100644 --- a/lib/change_detection/watch_group.dart +++ b/lib/change_detection/watch_group.dart @@ -140,9 +140,10 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList { } Watch watch(AST expression, ReactionFn reactionFn) { - WatchRecord<_Handler> watchRecord = - _cache.putIfAbsent(expression.expression, - () => expression.setupWatch(this)); + WatchRecord<_Handler> watchRecord = _cache[expression.expression]; + if (watchRecord == null) { + _cache[expression.expression] = watchRecord = expression.setupWatch(this); + } return watchRecord.handler.addReactionFn(reactionFn); } @@ -161,8 +162,10 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList { _fieldCost++; fieldHandler.watchRecord = watchRecord; - WatchRecord<_Handler> lhsWR = _cache.putIfAbsent(lhs.expression, - () => lhs.setupWatch(this)); + WatchRecord<_Handler> lhsWR = _cache[lhs.expression]; + if (lhsWR == null) { + lhsWR = _cache[lhs.expression] = lhs.setupWatch(this); + } // We set a field forwarding handler on LHS. This will allow the change // objects to propagate to the current WatchRecord. @@ -178,8 +181,10 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList { var watchRecord = _changeDetector.watch(null, null, collectionHandler); _collectionCost++; collectionHandler.watchRecord = watchRecord; - WatchRecord<_Handler> astWR = _cache.putIfAbsent(ast.expression, - () => ast.setupWatch(this)); + WatchRecord<_Handler> astWR = _cache[ast.expression]; + if (astWR == null) { + astWR = _cache[ast.expression] = ast.setupWatch(this); + } // We set a field forwarding handler on LHS. This will allow the change // objects to propagate to the current WatchRecord. @@ -230,8 +235,10 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList { invokeHandler.watchRecord = evalWatchRecord; if (lhsAST != null) { - var lhsWR = _cache.putIfAbsent(lhsAST.expression, - () => lhsAST.setupWatch(this)); + var lhsWR = _cache[lhsAST.expression]; + if (lhsWR == null) { + lhsWR = _cache[lhsAST.expression] = lhsAST.setupWatch(this); + } lhsWR.handler.addForwardHandler(invokeHandler); invokeHandler.acceptValue(lhsWR.currentValue); } @@ -239,8 +246,10 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList { // Convert the args from AST to WatchRecords for (var i = 0; i < argsAST.length; i++) { var ast = argsAST[i]; - WatchRecord<_Handler> record = - _cache.putIfAbsent(ast.expression, () => ast.setupWatch(this)); + WatchRecord<_Handler> record = _cache[ast.expression]; + if (record == null) { + record = _cache[ast.expression] = ast.setupWatch(this); + } _ArgHandler handler = new _PositionalArgHandler(this, evalWatchRecord, i); _ArgHandlerList._add(invokeHandler, handler); record.handler.addForwardHandler(handler); @@ -248,8 +257,10 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList { } namedArgsAST.forEach((Symbol name, AST ast) { - WatchRecord<_Handler> record = _cache.putIfAbsent(ast.expression, - () => ast.setupWatch(this)); + WatchRecord<_Handler> record = _cache[ast.expression]; + if (record == null) { + record = _cache[ast.expression] = ast.setupWatch(this); + } _ArgHandler handler = new _NamedArgHandler(this, evalWatchRecord, name); _ArgHandlerList._add(invokeHandler, handler); record.handler.addForwardHandler(handler); diff --git a/lib/core/annotation.dart b/lib/core/annotation.dart index 768def9aa..cee2ad10f 100644 --- a/lib/core/annotation.dart +++ b/lib/core/annotation.dart @@ -11,12 +11,14 @@ export "package:angular/core/annotation_src.dart" show ShadowRootAware, Formatter, - Injectable, + DirectiveBinder, + DirectiveBinderFn, Directive, Component, Controller, Decorator, + Visibility, DirectiveAnnotation, NgAttr, diff --git a/lib/core/annotation_src.dart b/lib/core/annotation_src.dart index 2458fd8d9..7501aede0 100644 --- a/lib/core/annotation_src.dart +++ b/lib/core/annotation_src.dart @@ -1,36 +1,29 @@ library angular.core.annotation_src; -import "package:di/di.dart" show Injector, Visibility; +import "package:di/di.dart" show Injector, Visibility, Factory; -RegExp _ATTR_NAME = new RegExp(r'\[([^\]]+)\]$'); - -const String SHADOW_DOM_INJECTOR_NAME = 'SHADOW_INJECTOR'; - -skipShadow(Injector injector) - => injector.name == SHADOW_DOM_INJECTOR_NAME ? injector.parent : injector; +abstract class DirectiveBinder { + bind(key, {Function toFactory, Factory toFactoryPos, inject, + Visibility visibility: Visibility.LOCAL}); +} -localVisibility (Injector requesting, Injector defining) - => identical(skipShadow(requesting), defining); +typedef void DirectiveBinderFn(DirectiveBinder module); -directChildrenVisibility(Injector requesting, Injector defining) { - requesting = skipShadow(requesting); - return identical(requesting.parent, defining) || localVisibility(requesting, defining); -} +RegExp _ATTR_NAME = new RegExp(r'\[([^\]]+)\]$'); Directive cloneWithNewMap(Directive annotation, map) => annotation._cloneWithNewMap(map); String mappingSpec(DirectiveAnnotation annotation) => annotation._mappingSpec; +class Visibility { + static const LOCAL = const Visibility._('LOCAL'); + static const CHILDREN = const Visibility._('CHILDREN'); + static const DIRECT_CHILD = const Visibility._('DIRECT_CHILD'); -/** - * An annotation when applied to a class indicates that the class (service) will - * be instantiated by di injector. This annotation is also used to designate which - * classes need to have a static factory generated when using static angular, and - * therefore is required on any injectable class. - */ -class Injectable { - const Injectable(); + final String name; + const Visibility._(this.name); + toString() => 'Visibility: $name'; } /** @@ -39,16 +32,19 @@ class Injectable { abstract class Directive { /// The directive can only be injected to other directives on the same element. - static const Visibility LOCAL_VISIBILITY = localVisibility; + @deprecated // ('Use Visibility.LOCAL instead') + static const Visibility LOCAL_VISIBILITY = Visibility.LOCAL; /// The directive can be injected to other directives on the same or child elements. - static const Visibility CHILDREN_VISIBILITY = null; + @deprecated// ('Use Visibility.CHILDREN instead') + static const Visibility CHILDREN_VISIBILITY = Visibility.CHILDREN; /** * The directive on this element can only be injected to other directives * declared on elements which are direct children of the current element. */ - static const Visibility DIRECT_CHILDREN_VISIBILITY = directChildrenVisibility; + @deprecated// ('Use Visibility.DIRECT_CHILD instead') + static const Visibility DIRECT_CHILDREN_VISIBILITY = Visibility.DIRECT_CHILD; /** * CSS selector which will trigger this component/directive. @@ -132,7 +128,7 @@ abstract class Directive { * * [Directive.CHILDREN_VISIBILITY] * * [Directive.DIRECT_CHILDREN_VISIBILITY] */ - final Function module; + final DirectiveBinderFn module; /** * Use map to define the mapping of DOM attributes to fields. @@ -215,8 +211,8 @@ abstract class Directive { const Directive({ this.selector, - this.children: Directive.COMPILE_CHILDREN, - this.visibility: Directive.LOCAL_VISIBILITY, + this.children, + this.visibility, this.module, this.map: const {}, this.exportExpressions: const [], @@ -224,10 +220,6 @@ abstract class Directive { }); toString() => selector; - get hashCode => selector.hashCode; - operator==(other) => - other is Directive && selector == other.selector; - Directive _cloneWithNewMap(newMap); } @@ -328,7 +320,7 @@ class Component extends Directive { applyAuthorStyles, resetStyleInheritance, this.publishAs, - module, + DirectiveBinderFn module, map, selector, visibility, @@ -386,7 +378,7 @@ class Decorator extends Directive { const Decorator({children: Directive.COMPILE_CHILDREN, map, selector, - module, + DirectiveBinderFn module, visibility, exportExpressions, exportExpressionAttrs}) @@ -439,7 +431,7 @@ class Controller extends Decorator { children: Directive.COMPILE_CHILDREN, this.publishAs, map, - module, + DirectiveBinderFn module, selector, visibility, exportExpressions, @@ -588,8 +580,5 @@ class Formatter { const Formatter({this.name}); - int get hashCode => name.hashCode; - bool operator==(other) => name == other.name; - toString() => 'Formatter: $name'; } diff --git a/lib/core/formatter.dart b/lib/core/formatter.dart index 25f317414..e88e01f65 100644 --- a/lib/core/formatter.dart +++ b/lib/core/formatter.dart @@ -1,5 +1,6 @@ library angular.core_internal.formatter_map; +import 'dart:collection'; import 'package:di/di.dart'; import 'package:angular/core/annotation_src.dart'; import 'package:angular/core/registry.dart'; @@ -8,16 +9,30 @@ import 'package:angular/core/registry.dart'; * Registry of formatters at runtime. */ @Injectable() -class FormatterMap extends AnnotationMap { - Injector _injector; - FormatterMap(Injector injector, MetadataExtractor extractMetadata) - : this._injector = injector, - super(injector, extractMetadata); +class FormatterMap { + final Map _map = new HashMap(); + final Injector _injector; - call(String name) { - var formatter = new Formatter(name: name); - var formatterType = this[formatter]; - return _injector.get(formatterType); + FormatterMap(this._injector, MetadataExtractor extractMetadata) { + (_injector as ModuleInjector).types.forEach((type) { + extractMetadata(type) + .where((annotation) => annotation is Formatter) + .forEach((Formatter formatter) { + _map[formatter.name] = type; + }); + }); + } + + call(String name) => _injector.get(this[name]); + + Type operator[](String name) { + Type formatterType = _map[name]; + if (formatterType == null) throw "No formatter '$name' found!"; + return formatterType; + } + + void forEach(fn(K, Type)) { + _map.forEach(fn); } } diff --git a/lib/core/module.dart b/lib/core/module.dart index 0029d5dea..55d8a30b7 100644 --- a/lib/core/module.dart +++ b/lib/core/module.dart @@ -26,6 +26,9 @@ export "package:angular/cache/module.dart" show CacheRegister, CacheRegisterStats; +export "package:angular/core_dom/directive_injector.dart" show + DirectiveInjector; + export "package:angular/core_dom/module_internal.dart" show Animation, AnimationResult, diff --git a/lib/core/module_internal.dart b/lib/core/module_internal.dart index d302d5efd..40053f947 100644 --- a/lib/core/module_internal.dart +++ b/lib/core/module_internal.dart @@ -36,18 +36,16 @@ class CoreModule extends Module { CoreModule() { bind(ScopeDigestTTL); - bind(MetadataExtractor); bind(ExceptionHandler); bind(FormatterMap); bind(Interpolate); bind(RootScope); - bind(Scope, toFactory: (injector) => injector.getByKey(ROOT_SCOPE_KEY)); - bind(ClosureMap, toFactory: (_) => throw "Must provide dynamic/static ClosureMap."); + bind(Scope, inject: [ROOT_SCOPE_KEY]); + bind(ClosureMap, toFactory: () => throw "Must provide dynamic/static ClosureMap."); bind(ScopeStats); bind(ScopeStatsEmitter); - bind(ScopeStatsConfig, toFactory: (i) => new ScopeStatsConfig()); + bind(ScopeStatsConfig, toFactory: () => new ScopeStatsConfig()); bind(Object, toValue: {}); // RootScope context - bind(VmTurnZone); bind(Parser, toImplementation: DynamicParser); bind(ParserBackend, toImplementation: DynamicParserBackend); diff --git a/lib/core/parser/dynamic_parser.dart b/lib/core/parser/dynamic_parser.dart index 26486b15c..8a74e1eba 100644 --- a/lib/core/parser/dynamic_parser.dart +++ b/lib/core/parser/dynamic_parser.dart @@ -1,5 +1,6 @@ library angular.core.parser.dynamic_parser; +import 'package:di/di.dart' show Injectable; import 'package:angular/cache/module.dart'; import 'package:angular/core/annotation_src.dart' hide Formatter; import 'package:angular/core/module_internal.dart' show FormatterMap; diff --git a/lib/core/parser/lexer.dart b/lib/core/parser/lexer.dart index 41c96c3a8..3ad848cd4 100644 --- a/lib/core/parser/lexer.dart +++ b/lib/core/parser/lexer.dart @@ -1,5 +1,6 @@ library angular.core.parser.lexer; +import 'package:di/di.dart' show Injectable; import 'package:angular/core/annotation_src.dart'; import 'package:angular/core/parser/characters.dart'; diff --git a/lib/core/parser/syntax.dart b/lib/core/parser/syntax.dart index e4393860c..de2ebc70e 100644 --- a/lib/core/parser/syntax.dart +++ b/lib/core/parser/syntax.dart @@ -205,5 +205,4 @@ class _DefaultFormatterMap implements FormatterMap { call(name) => throw 'No Formatter: $name found!'; Type operator[](annotation) => null; forEach(fn) { } - annotationsFor(type) => null; } diff --git a/lib/core/registry.dart b/lib/core/registry.dart index 9d66de3ed..05692d4a1 100644 --- a/lib/core/registry.dart +++ b/lib/core/registry.dart @@ -1,74 +1,6 @@ library angular.core.registry; -import 'package:di/di.dart' show Injector; - -abstract class AnnotationMap { - final Map _map = {}; - - AnnotationMap(Injector injector, MetadataExtractor extractMetadata) { - injector.types.forEach((type) { - extractMetadata(type) - .where((annotation) => annotation is K) - .forEach((annotation) { - _map[annotation] = type; - }); - }); - } - - Type operator[](K annotation) { - var value = _map[annotation]; - if (value == null) throw 'No $annotation found!'; - return value; - } - - void forEach(fn(K, Type)) { - _map.forEach(fn); - } - - List annotationsFor(Type type) { - final res = []; - forEach((ann, annType) { - if (annType == type) res.add(ann); - }); - return res; - } -} - -abstract class AnnotationsMap { - final Map> map = {}; - - AnnotationsMap(Injector injector, MetadataExtractor extractMetadata) { - injector.types.forEach((type) { - extractMetadata(type) - .where((annotation) => annotation is K) - .forEach((annotation) { - map.putIfAbsent(annotation, () => []).add(type); - }); - }); - } - - List operator[](K annotation) { - var value = map[annotation]; - if (value == null) throw 'No $annotation found!'; - return value; - } - - void forEach(fn(K, Type)) { - map.forEach((annotation, types) { - types.forEach((type) { - fn(annotation, type); - }); - }); - } - - List annotationsFor(Type type) { - var res = []; - forEach((ann, annType) { - if (annType == type) res.add(ann); - }); - return res; - } -} +import 'package:di/di.dart' show Injector, ModuleInjector; abstract class MetadataExtractor { Iterable call(Type type); diff --git a/lib/core/registry_static.dart b/lib/core/registry_static.dart index fcb287b13..a62d90b35 100644 --- a/lib/core/registry_static.dart +++ b/lib/core/registry_static.dart @@ -1,6 +1,6 @@ library angular.core_static; -import 'package:angular/core/annotation_src.dart' show Injectable; +import 'package:di/di.dart' show Injectable; import 'package:angular/core/registry.dart'; @Injectable() diff --git a/lib/core/static_keys.dart b/lib/core/static_keys.dart index 8f810b894..fe29b88ee 100644 --- a/lib/core/static_keys.dart +++ b/lib/core/static_keys.dart @@ -1,11 +1,12 @@ library angular.static_keys; import 'package:di/di.dart'; -import 'module_internal.dart'; +import 'package:angular/core/module_internal.dart'; -Key EXCEPTION_HANDLER_KEY = new Key(ExceptionHandler); -Key ROOT_SCOPE_KEY = new Key(RootScope); -Key SCOPE_KEY = new Key(Scope); -Key SCOPE_STATS_CONFIG_KEY = new Key(ScopeStatsConfig); -Key FORMATTER_MAP_KEY = new Key(FormatterMap); -Key INTERPOLATE_KEY = new Key(Interpolate); \ No newline at end of file +final Key INJECTOR_KEY = new Key(Injector); +final Key EXCEPTION_HANDLER_KEY = new Key(ExceptionHandler); +final Key ROOT_SCOPE_KEY = new Key(RootScope); +final Key SCOPE_KEY = new Key(Scope); +final Key SCOPE_STATS_CONFIG_KEY = new Key(ScopeStatsConfig); +final Key FORMATTER_MAP_KEY = new Key(FormatterMap); +final Key INTERPOLATE_KEY = new Key(Interpolate); diff --git a/lib/core_dom/common.dart b/lib/core_dom/common.dart index 53a2c6c59..139f07004 100644 --- a/lib/core_dom/common.dart +++ b/lib/core_dom/common.dart @@ -17,13 +17,18 @@ class MappingParts { class DirectiveRef { final dom.Node element; final Type type; + final Factory factory; + final List paramKeys; final Key typeKey; final Directive annotation; final String value; final AST valueAST; final mappings = new List(); - DirectiveRef(this.element, this.type, this.annotation, this.typeKey, [ this.value, this.valueAST ]); + DirectiveRef(this.element, type, this.annotation, this.typeKey, [ this.value, this.valueAST ]) + : type = type, + factory = Module.DEFAULT_REFLECTOR.factoryFor(type), + paramKeys = Module.DEFAULT_REFLECTOR.parameterKeysFor(type); String toString() { var html = element is dom.Element @@ -41,11 +46,12 @@ class DirectiveRef { */ Injector forceNewDirectivesAndFormatters(Injector injector, List modules) { modules.add(new Module() - ..bind(Scope, toFactory: (i) { - var scope = i.parent.getByKey(SCOPE_KEY); - return scope.createChild(new PrototypeMap(scope.context)); - })); - - return injector.createChild(modules, - forceNewInstances: [DirectiveMap, FormatterMap]); + ..bind(Scope, toFactory: (Injector injector) { + var scope = injector.parent.getByKey(SCOPE_KEY); + return scope.createChild(new PrototypeMap(scope.context)); + }, inject: [INJECTOR_KEY]) + ..bind(DirectiveMap) + ..bind(FormatterMap)); + + return new ModuleInjector(modules, injector); } diff --git a/lib/core_dom/directive_injector.dart b/lib/core_dom/directive_injector.dart new file mode 100644 index 000000000..c99cdc2de --- /dev/null +++ b/lib/core_dom/directive_injector.dart @@ -0,0 +1,403 @@ +library angular.node_injector; + +import 'dart:collection'; +import 'dart:html' show Node, Element, ShadowRoot; +import 'dart:profiler'; + +import 'package:di/di.dart'; +import 'package:angular/core/static_keys.dart'; +import 'package:angular/core_dom/static_keys.dart'; +import 'package:angular/mock/module.dart'; + +import 'package:angular/core/module.dart' show Scope, RootScope; +import 'package:angular/core/annotation.dart' show Visibility, DirectiveBinder; +import 'package:angular/core_dom/module_internal.dart' + show Animate, View, ViewFactory, BoundViewFactory, ViewPort, NodeAttrs, ElementProbe, + NgElement, ContentPort, TemplateLoader, ShadowRootEventHandler, EventHandler; + +var _TAG_GET = new UserTag('DirectiveInjector.get()'); +var _TAG_INSTANTIATE = new UserTag('DirectiveInjector.instantiate()'); + +final DIRECTIVE_INJECTOR_KEY = new Key(DirectiveInjector); +final CONTENT_PORT_KEY = new Key(ContentPort); +final TEMPLATE_LOADER_KEY = new Key(TemplateLoader); +final SHADOW_ROOT_KEY = new Key(ShadowRoot); + +const int VISIBILITY_LOCAL = -1; +const int VISIBILITY_DIRECT_CHILD = -2; +const int VISIBILITY_CHILDREN = -3; +const int VISIBILITY_COMPONENT_OFFSET = VISIBILITY_CHILDREN; +const int VISIBILITY_COMPONENT_LOCAL = VISIBILITY_LOCAL + VISIBILITY_COMPONENT_OFFSET; +const int VISIBILITY_COMPONENT_DIRECT_CHILD = VISIBILITY_DIRECT_CHILD + VISIBILITY_COMPONENT_OFFSET; +const int VISIBILITY_COMPONENT_CHILDREN = VISIBILITY_CHILDREN + VISIBILITY_COMPONENT_OFFSET; + +const int UNDEFINED_ID = 0; +const int INJECTOR_KEY_ID = 1; +const int DIRECTIVE_INJECTOR_KEY_ID = 2; +const int NODE_KEY_ID = 3; +const int ELEMENT_KEY_ID = 4; +const int NODE_ATTRS_KEY_ID = 5; +const int ANIMATE_KEY_ID = 6; +const int SCOPE_KEY_ID = 7; +const int VIEW_KEY_ID = 8; +const int VIEW_PORT_KEY_ID = 9; +const int VIEW_FACTORY_KEY_ID = 10; +const int NG_ELEMENT_KEY_ID = 11; +const int BOUND_VIEW_FACTORY_KEY_ID = 12; +const int ELEMENT_PROBE_KEY_ID = 13; +const int TEMPLATE_LOADER_KEY_ID = 14; +const int SHADOW_ROOT_KEY_ID = 15; +const int CONTENT_PORT_KEY_ID = 16; +const int EVENT_HANDLER_KEY_ID = 17; +const int KEEP_ME_LAST = 18; + +class DirectiveInjector implements DirectiveBinder { + static bool _isInit = false; + static initUID() { + if (_isInit) return; + _isInit = true; + INJECTOR_KEY.uid = INJECTOR_KEY_ID; + DIRECTIVE_INJECTOR_KEY.uid = DIRECTIVE_INJECTOR_KEY_ID; + NODE_KEY.uid = NODE_KEY_ID; + ELEMENT_KEY.uid = ELEMENT_KEY_ID; + NODE_ATTRS_KEY.uid = NODE_ATTRS_KEY_ID; + SCOPE_KEY.uid = SCOPE_KEY_ID; + VIEW_KEY.uid = VIEW_KEY_ID; + VIEW_PORT_KEY.uid = VIEW_PORT_KEY_ID; + VIEW_FACTORY_KEY.uid = VIEW_FACTORY_KEY_ID; + NG_ELEMENT_KEY.uid = NG_ELEMENT_KEY_ID; + BOUND_VIEW_FACTORY_KEY.uid = BOUND_VIEW_FACTORY_KEY_ID; + ELEMENT_PROBE_KEY.uid = ELEMENT_PROBE_KEY_ID; + TEMPLATE_LOADER_KEY.uid = TEMPLATE_LOADER_KEY_ID; + SHADOW_ROOT_KEY.uid = SHADOW_ROOT_KEY_ID; + CONTENT_PORT_KEY.uid = CONTENT_PORT_KEY_ID; + EVENT_HANDLER_KEY.uid = EVENT_HANDLER_KEY_ID; + ANIMATE_KEY.uid = ANIMATE_KEY_ID; + for(var i = 1; i < KEEP_ME_LAST; i++) { + if (_KEYS[i].uid != i) throw 'MISSORDERED KEYS ARRAY: ${_KEYS} at $i'; + } + } + static List _KEYS = + [ UNDEFINED_ID + , INJECTOR_KEY + , DIRECTIVE_INJECTOR_KEY + , NODE_KEY + , ELEMENT_KEY + , NODE_ATTRS_KEY + , ANIMATE_KEY + , SCOPE_KEY + , VIEW_KEY + , VIEW_PORT_KEY + , VIEW_FACTORY_KEY + , NG_ELEMENT_KEY + , BOUND_VIEW_FACTORY_KEY + , ELEMENT_PROBE_KEY + , TEMPLATE_LOADER_KEY + , SHADOW_ROOT_KEY + , CONTENT_PORT_KEY + , EVENT_HANDLER_KEY + , KEEP_ME_LAST + ]; + + final DirectiveInjector parent; + final Injector appInjector; + final Node _node; + final NodeAttrs _nodeAttrs; + final Animate _animate; + final EventHandler _eventHandler; + Scope scope; //TODO(misko): this should be final after we get rid of controller + + NgElement _ngElement; + ElementProbe _elementProbe; + + Key _key0 = null; dynamic _obj0; List _pKeys0; Factory _factory0; + Key _key1 = null; dynamic _obj1; List _pKeys1; Factory _factory1; + Key _key2 = null; dynamic _obj2; List _pKeys2; Factory _factory2; + Key _key3 = null; dynamic _obj3; List _pKeys3; Factory _factory3; + Key _key4 = null; dynamic _obj4; List _pKeys4; Factory _factory4; + Key _key5 = null; dynamic _obj5; List _pKeys5; Factory _factory5; + Key _key6 = null; dynamic _obj6; List _pKeys6; Factory _factory6; + Key _key7 = null; dynamic _obj7; List _pKeys7; Factory _factory7; + Key _key8 = null; dynamic _obj8; List _pKeys8; Factory _factory8; + Key _key9 = null; dynamic _obj9; List _pKeys9; Factory _factory9; + + static _toVisId(Visibility v) => identical(v, Visibility.LOCAL) + ? VISIBILITY_LOCAL + : (identical(v, Visibility.CHILDREN) ? VISIBILITY_CHILDREN : VISIBILITY_DIRECT_CHILD); + + static _toVis(int id) { + switch (id) { + case VISIBILITY_LOCAL: return Visibility.LOCAL; + case VISIBILITY_DIRECT_CHILD: return Visibility.DIRECT_CHILD; + case VISIBILITY_CHILDREN: return Visibility.CHILDREN; + case VISIBILITY_COMPONENT_LOCAL: return Visibility.LOCAL; + case VISIBILITY_COMPONENT_DIRECT_CHILD: return Visibility.DIRECT_CHILD; + case VISIBILITY_COMPONENT_CHILDREN: return Visibility.CHILDREN; + default: return null; + } + } + + DirectiveInjector(parent, appInjector, this._node, this._nodeAttrs, this._eventHandler, + this.scope, this._animate) + : appInjector = appInjector, + parent = parent == null ? new DefaultDirectiveInjector(appInjector) : parent; + + DirectiveInjector._default(this.appInjector) + : parent = null, + _node = null, + _nodeAttrs = null, + _eventHandler = null, + scope = null, + _animate = null; + + bind(key, {Function toFactory, Factory toFactoryPos, inject, + Visibility visibility: Visibility.LOCAL}) { + if (key == null) throw 'Key is required'; + if (key is! Key) key = new Key(key); + if (toFactory != null && toFactoryPos != null) { + throw "Can not have both toFactory and toFactoryPos."; + } + + if (inject == null) { + if (toFactory != null) throw "Can not have toFactory without inject"; + inject = Module.DEFAULT_REFLECTOR.parameterKeysFor(key.type); + toFactoryPos = Module.DEFAULT_REFLECTOR.factoryFor(key.type); + } else { + if (inject is! List) inject = [inject]; + for(var i=0; i < inject.length; i++) { + if (inject[i] is! Key) inject[i] = new Key(inject[i]); + } + } + + if (toFactory != null && toFactoryPos == null) { + toFactoryPos = (List args) => Function.apply(toFactory, args); + } + if (toFactoryPos == null) toFactoryPos = _IDENTITY; + bindByKey(key, toFactoryPos, inject, visibility); + } + static Function _IDENTITY = (args) => args[0]; + + bindByKey(Key key, Factory factory, List parameterKeys, [Visibility visibility]) { + if (visibility == null) visibility = Visibility.LOCAL; + int visibilityId = _toVisId(visibility); + int keyVisId = key.uid; + if (keyVisId != visibilityId) { + if (keyVisId == null) { + key.uid = visibilityId; + } else { + throw "Can not set $visibility on $key, it alread has ${_toVis(keyVisId)}"; + } + } + if (_key0 == null || identical(_key0, key)) { _key0 = key; _pKeys0 = parameterKeys; _factory0 = factory; } + else if (_key1 == null || identical(_key1, key)) { _key1 = key; _pKeys1 = parameterKeys; _factory1 = factory; } + else if (_key2 == null || identical(_key2, key)) { _key2 = key; _pKeys2 = parameterKeys; _factory2 = factory; } + else if (_key3 == null || identical(_key3, key)) { _key3 = key; _pKeys3 = parameterKeys; _factory3 = factory; } + else if (_key4 == null || identical(_key4, key)) { _key4 = key; _pKeys4 = parameterKeys; _factory4 = factory; } + else if (_key5 == null || identical(_key5, key)) { _key5 = key; _pKeys5 = parameterKeys; _factory5 = factory; } + else if (_key6 == null || identical(_key6, key)) { _key6 = key; _pKeys6 = parameterKeys; _factory6 = factory; } + else if (_key7 == null || identical(_key7, key)) { _key7 = key; _pKeys7 = parameterKeys; _factory7 = factory; } + else if (_key8 == null || identical(_key8, key)) { _key8 = key; _pKeys8 = parameterKeys; _factory8 = factory; } + else if (_key9 == null || identical(_key9, key)) { _key9 = key; _pKeys9 = parameterKeys; _factory9 = factory; } + else { throw 'Maximum number of directives per element reached.'; } + } + + Object get(Type type) => getByKey(new Key(type)); + + Object getByKey(Key key, [int depth = 0]) { + var oldTag = _TAG_GET.makeCurrent(); + try { + return _getByKey(key, depth); + } on ResolvingError catch (e, s) { + e.appendKey(key); + rethrow; + } finally { + oldTag.makeCurrent(); + } + } + + Object _getByKey(Key key, int depth) { + int uid = key.uid; + if (uid == null || uid == UNDEFINED_ID) return appInjector.getByKey(key, depth + 1); + bool isDirective = uid < 0; + return isDirective ? _getDirectiveByKey(key, uid, depth + 1) : _getById(uid); + } + + _getDirectiveByKey(Key k, int visType, int d) { + do { + if (_key0 == null) break; if (identical(_key0, k)) return _obj0 == null ? _obj0 = _new(_pKeys0, _factory0, d) : _obj0; + if (_key1 == null) break; if (identical(_key1, k)) return _obj1 == null ? _obj1 = _new(_pKeys1, _factory1, d) : _obj1; + if (_key2 == null) break; if (identical(_key2, k)) return _obj2 == null ? _obj2 = _new(_pKeys2, _factory2, d) : _obj2; + if (_key3 == null) break; if (identical(_key3, k)) return _obj3 == null ? _obj3 = _new(_pKeys3, _factory3, d) : _obj3; + if (_key4 == null) break; if (identical(_key4, k)) return _obj4 == null ? _obj4 = _new(_pKeys4, _factory4, d) : _obj4; + if (_key5 == null) break; if (identical(_key5, k)) return _obj5 == null ? _obj5 = _new(_pKeys5, _factory5, d) : _obj5; + if (_key6 == null) break; if (identical(_key6, k)) return _obj6 == null ? _obj6 = _new(_pKeys6, _factory6, d) : _obj6; + if (_key7 == null) break; if (identical(_key7, k)) return _obj7 == null ? _obj7 = _new(_pKeys7, _factory7, d) : _obj7; + if (_key8 == null) break; if (identical(_key8, k)) return _obj8 == null ? _obj8 = _new(_pKeys8, _factory8, d) : _obj8; + if (_key9 == null) break; if (identical(_key9, k)) return _obj9 == null ? _obj9 = _new(_pKeys9, _factory9, d) : _obj9; + } while (false); + switch (visType) { + case VISIBILITY_LOCAL: return appInjector.getByKey(k, d); + case VISIBILITY_DIRECT_CHILD: return parent._getDirectiveByKey(k, VISIBILITY_LOCAL, d); + case VISIBILITY_CHILDREN: return parent._getDirectiveByKey(k, VISIBILITY_CHILDREN, d); + // SHADOW + case VISIBILITY_COMPONENT_LOCAL: return parent._getDirectiveByKey(k, VISIBILITY_LOCAL, d); + case VISIBILITY_COMPONENT_DIRECT_CHILD: return parent._getDirectiveByKey(k, VISIBILITY_DIRECT_CHILD, d); + case VISIBILITY_COMPONENT_CHILDREN: return parent._getDirectiveByKey(k, VISIBILITY_CHILDREN, d); + default: throw null; + } + } + + List get directives { + var directives = []; + if (_obj0 != null) directives.add(_obj0); + if (_obj1 != null) directives.add(_obj1); + if (_obj2 != null) directives.add(_obj2); + if (_obj3 != null) directives.add(_obj3); + if (_obj4 != null) directives.add(_obj4); + if (_obj5 != null) directives.add(_obj5); + if (_obj6 != null) directives.add(_obj6); + if (_obj7 != null) directives.add(_obj7); + if (_obj8 != null) directives.add(_obj8); + if (_obj9 != null) directives.add(_obj9); + return directives; + } + + Object _getById(int keyId) { + switch(keyId) { + case INJECTOR_KEY_ID: return appInjector; + case DIRECTIVE_INJECTOR_KEY_ID: return this; + case NODE_KEY_ID: return _node; + case ELEMENT_KEY_ID: return _node; + case NODE_ATTRS_KEY_ID: return _nodeAttrs; + case ANIMATE_KEY_ID: return _animate; + case SCOPE_KEY_ID: return scope; + case ELEMENT_PROBE_KEY_ID: return elementProbe; + case NG_ELEMENT_KEY_ID: return ngElement; + case EVENT_HANDLER_KEY_ID: return _eventHandler; + case CONTENT_PORT_KEY_ID: return parent._getById(keyId); + default: new NoProviderError(_KEYS[keyId]); + } + } + + dynamic _new(List paramKeys, Function constructor, int depth) { + var oldTag = _TAG_GET.makeCurrent(); + var params = new List(paramKeys.length); + for(var i = 0; i < paramKeys.length; i++) { + params[i] = _getByKey(paramKeys[i], depth + 1); + } + _TAG_INSTANTIATE.makeCurrent(); + var obj = constructor(params); + oldTag.makeCurrent(); + return obj; + } + + + ElementProbe get elementProbe { + if (_elementProbe == null) { + ElementProbe parentProbe = parent is DirectiveInjector ? parent.elementProbe : null; + _elementProbe = new ElementProbe(parentProbe, _node, this, appInjector, scope); + } + return _elementProbe; + } + + NgElement get ngElement { + if (_ngElement == null) { + _ngElement = new NgElement(_node, scope, _animate); + } + return _ngElement; + } +} + +class TemplateDirectiveInjector extends DirectiveInjector { + final ViewFactory _viewFactory; + ViewPort _viewPort; + BoundViewFactory _boundViewFactory; + + TemplateDirectiveInjector(DirectiveInjector parent, Injector appInjector, + Node node, NodeAttrs nodeAttrs, EventHandler eventHandler, + Scope scope, Animate animate, this._viewFactory) + : super(parent, appInjector, node, nodeAttrs, eventHandler, scope, animate); + + + Object _getById(int keyId) { + switch(keyId) { + case VIEW_FACTORY_KEY_ID: return _viewFactory; + case VIEW_PORT_KEY_ID: return ((_viewPort) == null) ? + _viewPort = new ViewPort(this, appInjector, scope, _node, _animate) : _viewPort; + case BOUND_VIEW_FACTORY_KEY_ID: return (_boundViewFactory == null) ? + _boundViewFactory = _viewFactory.bind(this.parent, appInjector) : _boundViewFactory; + default: return super._getById(keyId); + } + } + +} + +abstract class ComponentDirectiveInjector extends DirectiveInjector { + + final TemplateLoader _templateLoader; + final ShadowRoot _shadowRoot; + + ComponentDirectiveInjector(DirectiveInjector parent, Injector appInjector, + EventHandler eventHandler, Scope scope, + this._templateLoader, this._shadowRoot) + : super(parent, appInjector, parent._node, parent._nodeAttrs, eventHandler, scope, + parent._animate); + + Object _getById(int keyId) { + switch(keyId) { + case TEMPLATE_LOADER_KEY_ID: return _templateLoader; + case SHADOW_ROOT_KEY_ID: return _shadowRoot; + default: return super._getById(keyId); + } + } + + _getDirectiveByKey(Key k, int visType, int d) => + super._getDirectiveByKey(k, visType + VISIBILITY_COMPONENT_OFFSET, d); +} + +class ShadowlessComponentDirectiveInjector extends ComponentDirectiveInjector { + final ContentPort _contentPort; + + ShadowlessComponentDirectiveInjector(DirectiveInjector parent, Injector appInjector, + EventHandler eventHandler, Scope scope, + templateLoader, shadowRoot, this._contentPort) + : super(parent, appInjector, eventHandler, scope, templateLoader, shadowRoot); + + Object _getById(int keyId) { + switch(keyId) { + case CONTENT_PORT_KEY_ID: return _contentPort; + default: return super._getById(keyId); + } + } +} + +class ShadowDomComponentDirectiveInjector extends ComponentDirectiveInjector { + ShadowDomComponentDirectiveInjector(DirectiveInjector parent, Injector appInjector, + Scope scope, templateLoader, shadowRoot) + : super(parent, appInjector, new ShadowRootEventHandler(shadowRoot, + parent.getByKey(EXPANDO_KEY), + parent.getByKey(EXCEPTION_HANDLER_KEY)), + scope, templateLoader, shadowRoot); + + ElementProbe get elementProbe { + if (_elementProbe == null) { + ElementProbe parentProbe = + parent is DirectiveInjector ? parent.elementProbe : parent.getByKey(ELEMENT_PROBE_KEY); + _elementProbe = new ElementProbe(parentProbe, _shadowRoot, this, appInjector, scope); + } + return _elementProbe; + } +} + +class DefaultDirectiveInjector extends DirectiveInjector { + DefaultDirectiveInjector(Injector appInjector): super._default(appInjector); + + Object getByKey(Key key, [int depth = 0]) => appInjector.getByKey(key, depth); + _getDirectiveByKey(Key key, int visType, int depth) => appInjector.getByKey(key, depth); + _getById(int keyId) { + switch (keyId) { + case CONTENT_PORT_KEY_ID: return null; + default: throw new NoProviderError(DirectiveInjector._KEYS[keyId]); + } + } +} diff --git a/lib/core_dom/directive_map.dart b/lib/core_dom/directive_map.dart index d1728f909..8beb6cae0 100644 --- a/lib/core_dom/directive_map.dart +++ b/lib/core_dom/directive_map.dart @@ -1,18 +1,47 @@ part of angular.core.dom_internal; +class DirectiveTypeTuple { + final Directive directive; + final Type type; + DirectiveTypeTuple(this.directive, this.type); +} + @Injectable() -class DirectiveMap extends AnnotationsMap { +class DirectiveMap { + final Map> map = new HashMap>(); DirectiveSelectorFactory _directiveSelectorFactory; FormatterMap _formatters; DirectiveSelector _selector; + + DirectiveMap(Injector injector, + this._formatters, + MetadataExtractor metadataExtractor, + this._directiveSelectorFactory) { + (injector as ModuleInjector).types.forEach((type) { + metadataExtractor(type) + .where((annotation) => annotation is Directive) + .forEach((Directive directive) { + map.putIfAbsent(directive.selector, () => []).add(new DirectiveTypeTuple(directive, type)); + }); + }); + } + DirectiveSelector get selector { if (_selector != null) return _selector; return _selector = _directiveSelectorFactory.selector(this, _formatters); } - DirectiveMap(Injector injector, - this._formatters, - MetadataExtractor metadataExtractor, - this._directiveSelectorFactory) - : super(injector, metadataExtractor); + List operator[](String key) { + var value = map[key]; + if (value == null) throw 'No Directive selector $key found!'; + return value; + } + + void forEach(fn(K, Type)) { + map.forEach((_, types) { + types.forEach((tuple) { + fn(tuple.directive, tuple.type); + }); + }); + } } diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart index ffe415da8..64e586be5 100644 --- a/lib/core_dom/element_binder.dart +++ b/lib/core_dom/element_binder.dart @@ -21,16 +21,6 @@ class TemplateElementBinder extends ElementBinder { null, null, onEvents, bindAttrs, childMode); String toString() => "[TemplateElementBinder template:$template]"; - - _registerViewFactory(node, parentInjector, nodeModule) { - assert(templateViewFactory != null); - nodeModule - ..bindByKey(VIEW_PORT_KEY, toFactory: (_) => - new ViewPort(node, parentInjector.getByKey(ANIMATE_KEY))) - ..bindByKey(VIEW_FACTORY_KEY, toValue: templateViewFactory) - ..bindByKey(BOUND_VIEW_FACTORY_KEY, toFactory: (Injector injector) => - templateViewFactory.bind(injector)); - } } @@ -77,7 +67,7 @@ class ElementBinder { void _bindTwoWay(tasks, AST ast, scope, directiveScope, controller, AST dstAST) { - var taskId = tasks.registerTask(); + var taskId = (tasks != null) ? tasks.registerTask() : 0; var viewOutbound = false; var viewInbound = false; @@ -86,7 +76,7 @@ class ElementBinder { viewOutbound = true; scope.rootScope.runAsync(() => viewOutbound = false); var value = dstAST.parsedExp.assign(controller, inboundValue); - tasks.completeTask(taskId); + if (tasks != null) tasks.completeTask(taskId); return value; } }); @@ -96,18 +86,18 @@ class ElementBinder { viewInbound = true; scope.rootScope.runAsync(() => viewInbound = false); ast.parsedExp.assign(scope.context, outboundValue); - tasks.completeTask(taskId); + if (tasks != null) tasks.completeTask(taskId); } }); } } _bindOneWay(tasks, ast, scope, AST dstAST, controller) { - var taskId = tasks.registerTask(); + var taskId = (tasks != null) ? tasks.registerTask() : 0; scope.watchAST(ast, (v, _) { dstAST.parsedExp.assign(controller, v); - tasks.completeTask(taskId); + if (tasks != null) tasks.completeTask(taskId); }); } @@ -118,7 +108,8 @@ class ElementBinder { void _createAttrMappings(directive, scope, List mappings, nodeAttrs, tasks) { Scope directiveScope; // Only created if there is a two-way binding in the element. - mappings.forEach((MappingParts p) { + for(var i = 0; i < mappings.length; i++) { + MappingParts p = mappings[i]; var attrName = p.attrName; var attrValueAST = p.attrValueAST; AST dstAST = p.dstAST; @@ -142,20 +133,20 @@ class ElementBinder { } else { _bindOneWay(tasks, bindAttr, scope, dstAST, directive); } - return; + continue; } switch (p.mode) { case '@': // string - var taskId = tasks.registerTask(); + var taskId = (tasks != null) ? tasks.registerTask() : 0; nodeAttrs.observe(attrName, (value) { dstAST.parsedExp.assign(directive, value); - tasks.completeTask(taskId); + if (tasks != null) tasks.completeTask(taskId); }); break; case '<=>': // two-way - if (nodeAttrs[attrName] == null) return; + if (nodeAttrs[attrName] == null) continue; if (directiveScope == null) { directiveScope = scope.createChild(directive); } @@ -164,13 +155,12 @@ class ElementBinder { break; case '=>': // one-way - if (nodeAttrs[attrName] == null) return; - _bindOneWay(tasks, attrValueAST, scope, - dstAST, directive); + if (nodeAttrs[attrName] == null) continue; + _bindOneWay(tasks, attrValueAST, scope, dstAST, directive); break; case '=>!': // one-way, one-time - if (nodeAttrs[attrName] == null) return; + if (nodeAttrs[attrName] == null) continue; var watch; var lastOneTimeValue; @@ -193,23 +183,23 @@ class ElementBinder { _bindCallback(dstAST.parsedExp, directive, nodeAttrs[attrName], scope); break; } - }); + } } - void _link(nodeInjector, probe, scope, nodeAttrs) { - _usableDirectiveRefs.forEach((DirectiveRef ref) { - var directive = nodeInjector.getByKey(ref.typeKey); - if (probe != null) { - probe.directives.add(directive); - } + void _link(DirectiveInjector directiveInjector, Scope scope, nodeAttrs) { + for(var i = 0; i < _usableDirectiveRefs.length; i++) { + DirectiveRef ref = _usableDirectiveRefs[i]; + var key = ref.typeKey; + if (identical(key, TEXT_MUSTACHE_KEY) || identical(key, ATTR_MUSTACHE_KEY)) continue; + var directive = directiveInjector.getByKey(ref.typeKey); if (ref.annotation is Controller) { - scope.context[(ref.annotation as Controller).publishAs] = directive; + scope.parentScope.context[(ref.annotation as Controller).publishAs] = directive; } - var tasks = new _TaskList(directive is AttachAware ? () { + var tasks = directive is AttachAware ? new _TaskList(() { if (scope.isAttached) directive.attach(); - } : null); + }) : null; if (ref.mappings.isNotEmpty) { if (nodeAttrs == null) nodeAttrs = new _AnchorAttrs(ref); @@ -226,100 +216,78 @@ class ElementBinder { }); } - tasks.doneRegistering(); + if (tasks != null) tasks.doneRegistering(); if (directive is DetachAware) { scope.on(ScopeEvent.DESTROY).listen((_) => directive.detach()); } - }); + } } - void _createDirectiveFactories(DirectiveRef ref, nodeModule, node, nodesAttrsDirectives, nodeAttrs, - visibility) { - if (ref.type == TextMustache) { - nodeModule.bind(TextMustache, toFactory: (Injector injector) { - return new TextMustache(node, ref.valueAST, injector.getByKey(SCOPE_KEY)); - }); - } else if (ref.type == AttrMustache) { - if (nodesAttrsDirectives.isEmpty) { - nodeModule.bind(AttrMustache, toFactory: (Injector injector) { - var scope = injector.getByKey(SCOPE_KEY); - for (var ref in nodesAttrsDirectives) { - new AttrMustache(nodeAttrs, ref.value, ref.valueAST, scope); - } - }); - } - nodesAttrsDirectives.add(ref); + void _createDirectiveFactories(DirectiveRef ref, DirectiveInjector nodeInjector, node, + nodeAttrs) { + if (ref.typeKey == TEXT_MUSTACHE_KEY) { + new TextMustache(node, ref.valueAST, nodeInjector.scope); + } else if (ref.typeKey == ATTR_MUSTACHE_KEY) { + new AttrMustache(nodeAttrs, ref.value, ref.valueAST, nodeInjector.scope); } else if (ref.annotation is Component) { assert(ref == componentData.ref); - nodeModule.bindByKey(ref.typeKey, toFactory: componentData.factory.call(node), visibility: visibility); + BoundComponentFactory boundComponentFactory = componentData.factory; + Function componentFactory = boundComponentFactory.call(node); + nodeInjector.bindByKey(ref.typeKey, + (p) => Function.apply(componentFactory, p), + boundComponentFactory.callArgs, ref.annotation.visibility); } else { - nodeModule.bindByKey(ref.typeKey, visibility: visibility); + nodeInjector.bindByKey(ref.typeKey, ref.factory, ref.paramKeys, ref.annotation.visibility); } } - // Overridden in TemplateElementBinder - void _registerViewFactory(node, parentInjector, nodeModule) { - nodeModule..bindByKey(VIEW_PORT_KEY, toValue: null) - ..bindByKey(VIEW_FACTORY_KEY, toValue: null) - ..bindByKey(BOUND_VIEW_FACTORY_KEY, toValue: null); - } - - - Injector bind(View view, Injector parentInjector, dom.Node node) { - Injector nodeInjector; - Scope scope = parentInjector.getByKey(SCOPE_KEY); + DirectiveInjector bind(View view, Scope scope, + DirectiveInjector parentInjector, Injector appInjector, + dom.Node node, EventHandler eventHandler, Animate animate) { var nodeAttrs = node is dom.Element ? new NodeAttrs(node) : null; - ElementProbe probe; var directiveRefs = _usableDirectiveRefs; if (!hasDirectivesOrEvents) return parentInjector; - var nodesAttrsDirectives = []; - var nodeModule = new Module() - ..bindByKey(NG_ELEMENT_KEY) - ..bindByKey(VIEW_KEY, toValue: view) - ..bindByKey(ELEMENT_KEY, toValue: node) - ..bindByKey(NODE_KEY, toValue: node) - ..bindByKey(NODE_ATTRS_KEY, toValue: nodeAttrs); - - if (_config.elementProbeEnabled) { - nodeModule.bindByKey(ELEMENT_PROBE_KEY, toFactory: (_) => probe); + DirectiveInjector nodeInjector; + if (this is TemplateElementBinder) { + nodeInjector = new TemplateDirectiveInjector(parentInjector, appInjector, + node, nodeAttrs, eventHandler, scope, animate, + (this as TemplateElementBinder).templateViewFactory); + } else { + nodeInjector = new DirectiveInjector(parentInjector, appInjector, + node, nodeAttrs, eventHandler, scope, animate); } - directiveRefs.forEach((DirectiveRef ref) { + for(var i = 0; i < directiveRefs.length; i++) { + DirectiveRef ref = directiveRefs[i]; Directive annotation = ref.annotation; - var visibility = ref.annotation.visibility; if (ref.annotation is Controller) { - scope = scope.createChild(new PrototypeMap(scope.context)); - nodeModule.bind(Scope, toValue: scope); + scope = nodeInjector.scope = scope.createChild(new PrototypeMap(scope.context)); + scope.context['CTRL'] = true; } - - _createDirectiveFactories(ref, nodeModule, node, nodesAttrsDirectives, nodeAttrs, - visibility); + _createDirectiveFactories(ref, nodeInjector, node, nodeAttrs); if (ref.annotation.module != null) { - nodeModule.install(ref.annotation.module()); + DirectiveBinderFn config = ref.annotation.module; + if (config != null) config(nodeInjector); } - }); - - _registerViewFactory(node, parentInjector, nodeModule); + } - nodeInjector = parentInjector.createChild([nodeModule]); if (_config.elementProbeEnabled) { - probe = _expando[node] = - new ElementProbe(parentInjector.getByKey(ELEMENT_PROBE_KEY), - node, nodeInjector, scope); - scope.on(ScopeEvent.DESTROY).listen((_) { - _expando[node] = null; - }); + _expando[node] = nodeInjector.elementProbe; + // TODO(misko): pretty sure that clearing Expando is not necessary. Remove? + scope.on(ScopeEvent.DESTROY).listen((_) => _expando[node] = null); } - _link(nodeInjector, probe, scope, nodeAttrs); + _link(nodeInjector, scope, nodeAttrs); - onEvents.forEach((event, value) { - view.registerEvent(EventHandler.attrNameToEventName(event)); - }); + if (onEvents.isNotEmpty) { + onEvents.forEach((event, value) { + view.registerEvent(EventHandler.attrNameToEventName(event)); + }); + } return nodeInjector; } diff --git a/lib/core_dom/module_internal.dart b/lib/core_dom/module_internal.dart index da16a2cb8..3f5ebe9fc 100644 --- a/lib/core_dom/module_internal.dart +++ b/lib/core_dom/module_internal.dart @@ -12,11 +12,12 @@ import 'package:perf_api/perf_api.dart'; import 'package:angular/cache/module.dart'; import 'package:angular/core/annotation.dart'; -import 'package:angular/core/annotation_src.dart' show SHADOW_DOM_INJECTOR_NAME; import 'package:angular/core/module_internal.dart'; import 'package:angular/core/parser/parser.dart'; import 'package:angular/core_dom/dom_util.dart' as util; import 'package:angular/core_dom/static_keys.dart'; +import 'package:angular/core_dom/directive_injector.dart'; +export 'package:angular/core_dom/directive_injector.dart' show DirectiveInjector; import 'package:angular/change_detection/watch_group.dart' show Watch, PrototypeMap; import 'package:angular/change_detection/ast_parser.dart'; @@ -58,11 +59,11 @@ class CoreDomModule extends Module { bind(ElementProbe, toValue: null); // Default to a unlimited-sized TemplateCache - bind(TemplateCache, toFactory: (i) { + bind(TemplateCache, toFactory: (CacheRegister register) { var templateCache = new TemplateCache(); - i.getByKey(CACHE_REGISTER_KEY).registerCache("TemplateCache", templateCache); + register.registerCache("TemplateCache", templateCache); return templateCache; - }); + }, inject: [CACHE_REGISTER_KEY]); bind(dom.NodeTreeSanitizer, toImplementation: NullTreeSanitizer); bind(TextMustache); @@ -71,7 +72,7 @@ class CoreDomModule extends Module { bind(Compiler, toImplementation: TaggingCompiler); bind(CompilerConfig); - bind(ComponentFactory, toFactory: (i) => i.getByKey(SHADOW_DOM_COMPONENT_FACTORY_KEY)); + bind(ComponentFactory, inject: [SHADOW_DOM_COMPONENT_FACTORY_KEY]); bind(ShadowDomComponentFactory); bind(TranscludingComponentFactory); bind(Content); diff --git a/lib/core_dom/selector.dart b/lib/core_dom/selector.dart index d6a19d976..7794694dc 100644 --- a/lib/core_dom/selector.dart +++ b/lib/core_dom/selector.dart @@ -41,9 +41,9 @@ class DirectiveSelector { } if ((match = _CONTAINS_REGEXP.firstMatch(selector)) != null) { - textSelector.add(new _ContainsSelector(annotation, match[1])); + textSelector.add(new _ContainsSelector(selector, match[1])); } else if ((match = _ATTR_CONTAINS_REGEXP.firstMatch(selector)) != null) { - attrSelector.add(new _ContainsSelector(annotation, match[1])); + attrSelector.add(new _ContainsSelector(selector, match[1])); } else if ((selectorParts = _splitCss(selector, type)) != null){ elementSelector.addDirective(selectorParts, new _Directive(type, annotation)); } else { @@ -97,12 +97,12 @@ class DirectiveSelector { // this directive is matched on any attribute name, and so // we need to pass the name to the directive by prefixing it to // the value. Yes it is a bit of a hack. - _directives[selectorRegExp.annotation].forEach((type) { + _directives[selectorRegExp.selector].forEach((DirectiveTypeTuple tuple) { // Pre-compute the AST to watch this value. String expression = _interpolate(value); AST valueAST = _astParser(expression, formatters: _formatters); builder.addDirective(new DirectiveRef( - node, type, selectorRegExp.annotation, new Key(type), attrName, valueAST)); + node, tuple.type, tuple.directive, new Key(tuple.type), attrName, valueAST)); }); } } @@ -135,13 +135,13 @@ class DirectiveSelector { for (var k = 0; k < textSelector.length; k++) { var selectorRegExp = textSelector[k]; if (selectorRegExp.regexp.hasMatch(value)) { - _directives[selectorRegExp.annotation].forEach((type) { + _directives[selectorRegExp.selector].forEach((tuple) { // Pre-compute the AST to watch this value. String expression = _interpolate(value); var valueAST = _astParser(expression, formatters: _formatters); - builder.addDirective(new DirectiveRef(node, type, - selectorRegExp.annotation, new Key(type), value, valueAST)); + builder.addDirective(new DirectiveRef(node, tuple.type, + tuple.directive, new Key(tuple.type), value, valueAST)); }); } } @@ -187,10 +187,10 @@ class _Directive { } class _ContainsSelector { - final Directive annotation; + final String selector; final RegExp regexp; - _ContainsSelector(this.annotation, String regexp) + _ContainsSelector(this.selector, String regexp) : regexp = new RegExp(regexp); } diff --git a/lib/core_dom/shadow_dom_component_factory.dart b/lib/core_dom/shadow_dom_component_factory.dart index 480db034a..78439ab1e 100644 --- a/lib/core_dom/shadow_dom_component_factory.dart +++ b/lib/core_dom/shadow_dom_component_factory.dart @@ -8,7 +8,8 @@ abstract class ComponentFactory { * A Component factory with has been bound to a specific component type. */ abstract class BoundComponentFactory { - FactoryFn call(dom.Element element); + List get callArgs; + Factory call(dom.Element element); static async.Future _viewFuture( Component component, ViewCache viewCache, DirectiveMap directives) { @@ -117,19 +118,20 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { ); } - FactoryFn call(dom.Element element) { - return (Injector injector) { - Scope scope = injector.getByKey(SCOPE_KEY); - NgBaseCss baseCss = _component.useNgBaseCss ? injector.getByKey(NG_BASE_CSS_KEY) : null; - + List get callArgs => _CALL_ARGS; + static final _CALL_ARGS = [DIRECTIVE_INJECTOR_KEY, INJECTOR_KEY, SCOPE_KEY, NG_BASE_CSS_KEY, + EVENT_HANDLER_KEY]; + Function call(dom.Element element) { + return (DirectiveInjector injector, Injector appInjector, Scope scope, NgBaseCss baseCss, + EventHandler eventHandler) { var shadowDom = element.createShadowRoot() ..applyAuthorStyles = _component.applyAuthorStyles ..resetStyleInheritance = _component.resetStyleInheritance; - var shadowScope = scope.createChild({}); // Isolate + var shadowScope = scope.createChild({'SHADOW': true}); // Isolate async.Future> cssFuture; - if (baseCss != null) { + if (_component.useNgBaseCss == true) { cssFuture = async.Future.wait( [async.Future.wait(baseCss.urls.map(_styleFuture)), _styleElementsFuture]) .then((twoLists) { @@ -140,7 +142,7 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { cssFuture = _styleElementsFuture; } - Injector shadowInjector; + ComponentDirectiveInjector shadowInjector; TemplateLoader templateLoader = new TemplateLoader( cssFuture.then((Iterable cssList) { @@ -153,7 +155,7 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { return _viewFuture.then((ViewFactory viewFactory) { if (shadowScope.isAttached) { shadowDom.nodes.addAll( - viewFactory(shadowInjector).nodes); + viewFactory.call(shadowInjector.scope, shadowInjector, appInjector).nodes); } return shadowDom; }); @@ -162,27 +164,13 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory { })); var probe; - var shadowModule = new Module() - ..bindByKey(_ref.typeKey) - ..bindByKey(NG_ELEMENT_KEY) - ..bindByKey(EVENT_HANDLER_KEY, toImplementation: ShadowRootEventHandler) - ..bindByKey(SCOPE_KEY, toValue: shadowScope) - ..bindByKey(TEMPLATE_LOADER_KEY, toValue: templateLoader) - ..bindByKey(SHADOW_ROOT_KEY, toValue: shadowDom); - - if (_f.config.elementProbeEnabled) { - shadowModule.bindByKey(ELEMENT_PROBE_KEY, toFactory: (_) => probe); - } - - shadowInjector = injector.createChild([shadowModule], name: SHADOW_DOM_INJECTOR_NAME); + shadowInjector = new ShadowDomComponentDirectiveInjector(injector, appInjector, shadowScope, + templateLoader, shadowDom); + shadowInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility); if (_f.config.elementProbeEnabled) { - probe = _f.expando[shadowDom] = - new ElementProbe(injector.getByKey(ELEMENT_PROBE_KEY), - shadowDom, shadowInjector, shadowScope); - shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) { - _f.expando[shadowDom] = null; - }); + probe = _f.expando[shadowDom] = shadowInjector.elementProbe; + shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) => _f.expando[shadowDom] = null); } var controller = shadowInjector.getByKey(_ref.typeKey); diff --git a/lib/core_dom/static_keys.dart b/lib/core_dom/static_keys.dart index 63fde5af0..8c770c9f7 100644 --- a/lib/core_dom/static_keys.dart +++ b/lib/core_dom/static_keys.dart @@ -11,28 +11,30 @@ export 'package:angular/core/static_keys.dart'; // Keys used to call Injector.getByKey and Module.bindByKey -Key ANIMATE_KEY = new Key(Animate); -Key BOUND_VIEW_FACTORY_KEY = new Key(BoundViewFactory); -Key CACHE_REGISTER_KEY = new Key(CacheRegister); -Key COMPILER_KEY = new Key(Compiler); -Key COMPONENT_CSS_REWRITER_KEY = new Key(ComponentCssRewriter); -Key DIRECTIVE_MAP_KEY = new Key(DirectiveMap); -Key ELEMENT_KEY = new Key(dom.Element); -Key ELEMENT_PROBE_KEY = new Key(ElementProbe); -Key EVENT_HANDLER_KEY = new Key(EventHandler); -Key HTTP_KEY = new Key(Http); -Key NG_ELEMENT_KEY = new Key(NgElement); -Key NODE_ATTRS_KEY = new Key(NodeAttrs); -Key NODE_KEY = new Key(dom.Node); -Key NODE_TREE_SANITIZER_KEY = new Key(dom.NodeTreeSanitizer); -Key SHADOW_DOM_COMPONENT_FACTORY_KEY = new Key(ShadowDomComponentFactory); -Key SHADOW_ROOT_KEY = new Key(dom.ShadowRoot); -Key TEMPLATE_CACHE_KEY = new Key(TemplateCache); -Key TEMPLATE_LOADER_KEY = new Key(TemplateLoader); -Key TEXT_MUSTACHE_KEY = new Key(TextMustache); -Key VIEW_CACHE_KEY = new Key(ViewCache); -Key VIEW_FACTORY_KEY = new Key(ViewFactory); -Key VIEW_KEY = new Key(View); -Key VIEW_PORT_KEY = new Key(ViewPort); -Key WEB_PLATFORM_KEY = new Key(WebPlatform); -Key WINDOW_KEY = new Key(dom.Window); +final Key ANIMATE_KEY = new Key(Animate); +final Key BOUND_VIEW_FACTORY_KEY = new Key(BoundViewFactory); +final Key CACHE_REGISTER_KEY = new Key(CacheRegister); +final Key COMPILER_KEY = new Key(Compiler); +final Key COMPONENT_CSS_REWRITER_KEY = new Key(ComponentCssRewriter); +final Key DIRECTIVE_MAP_KEY = new Key(DirectiveMap); +final Key ELEMENT_KEY = new Key(dom.Element); +final Key ELEMENT_PROBE_KEY = new Key(ElementProbe); +final Key EVENT_HANDLER_KEY = new Key(EventHandler); +final Key HTTP_KEY = new Key(Http); +final Key NG_ELEMENT_KEY = new Key(NgElement); +final Key NODE_ATTRS_KEY = new Key(NodeAttrs); +final Key NODE_KEY = new Key(dom.Node); +final Key NODE_TREE_SANITIZER_KEY = new Key(dom.NodeTreeSanitizer); +final Key SHADOW_DOM_COMPONENT_FACTORY_KEY = new Key(ShadowDomComponentFactory); +final Key SHADOW_ROOT_KEY = new Key(dom.ShadowRoot); +final Key TEMPLATE_CACHE_KEY = new Key(TemplateCache); +final Key TEMPLATE_LOADER_KEY = new Key(TemplateLoader); +final Key TEXT_MUSTACHE_KEY = new Key(TextMustache); +final Key ATTR_MUSTACHE_KEY = new Key(AttrMustache); +final Key VIEW_CACHE_KEY = new Key(ViewCache); +final Key VIEW_FACTORY_KEY = new Key(ViewFactory); +final Key VIEW_KEY = new Key(View); +final Key VIEW_PORT_KEY = new Key(ViewPort); +final Key WEB_PLATFORM_KEY = new Key(WebPlatform); +final Key WINDOW_KEY = new Key(dom.Window); +final Key EXPANDO_KEY = new Key(Expando); diff --git a/lib/core_dom/tagging_view_factory.dart b/lib/core_dom/tagging_view_factory.dart index 5eabd5a8b..c6a8bbb16 100644 --- a/lib/core_dom/tagging_view_factory.dart +++ b/lib/core_dom/tagging_view_factory.dart @@ -9,11 +9,13 @@ class TaggingViewFactory implements ViewFactory { TaggingViewFactory(this.templateNodes, this.elementBinders, this._perf); - BoundViewFactory bind(Injector injector) => new BoundViewFactory(this, injector); + @deprecated + BoundViewFactory bind(DirectiveInjector directiveInjector, Injector appInjector) => + new BoundViewFactory(this, directiveInjector, appInjector); - static Key _EVENT_HANDLER_KEY = new Key(EventHandler); - - View call(Injector injector, [List nodes /* TODO: document fragment */]) { + View call(Scope scope, DirectiveInjector directiveInjector, Injector appInjector, + [List nodes /* TODO: document fragment */]) { + assert(scope != null); var lastTag = ngViewTag.makeCurrent(); if (nodes == null) { nodes = cloneElements(templateNodes); @@ -21,8 +23,12 @@ class TaggingViewFactory implements ViewFactory { var timerId; try { assert((timerId = _perf.startTimer('ng.view')) != false); - var view = new View(nodes, injector.getByKey(_EVENT_HANDLER_KEY)); - _link(view, nodes, injector); + Animate animate = appInjector.getByKey(ANIMATE_KEY); + EventHandler eventHandler = directiveInjector == null + ? appInjector.getByKey(EVENT_HANDLER_KEY) + : directiveInjector.getByKey(EVENT_HANDLER_KEY); + var view = new View(nodes, scope, eventHandler); + _link(view, scope, nodes, eventHandler, animate, directiveInjector, appInjector); return view; } finally { lastTag.makeCurrent(); @@ -30,27 +36,36 @@ class TaggingViewFactory implements ViewFactory { } } - void _bindTagged(TaggedElementBinder tagged, int elementBinderIndex, Injector rootInjector, - List elementInjectors, View view, boundNode) { + void _bindTagged(TaggedElementBinder tagged, int elementBinderIndex, + DirectiveInjector rootInjector, Injector appInjector, + List elementInjectors, View view, boundNode, Scope scope, + EventHandler eventHandler, Animate animate) { var binder = tagged.binder; - var parentInjector = tagged.parentBinderOffset == -1 ? - rootInjector : - elementInjectors[tagged.parentBinderOffset]; - assert(parentInjector != null); + DirectiveInjector parentInjector = + tagged.parentBinderOffset == -1 ? rootInjector : elementInjectors[tagged.parentBinderOffset]; - var elementInjector = elementInjectors[elementBinderIndex] = - binder != null ? binder.bind(view, parentInjector, boundNode) : parentInjector; + var elementInjector; + if (binder == null) { + elementInjector = parentInjector; + } else { + elementInjector = binder.bind(view, scope, parentInjector, appInjector, boundNode, eventHandler, animate); + // TODO(misko): Remove this after we remove controllers. No controllers -> 1to1 Scope:View. + scope = elementInjector.scope; + } + elementInjectors[elementBinderIndex] = elementInjector; if (tagged.textBinders != null) { for (var k = 0; k < tagged.textBinders.length; k++) { TaggedTextBinder taggedText = tagged.textBinders[k]; - taggedText.binder.bind(view, elementInjector, boundNode.childNodes[taggedText.offsetIndex]); + var childNode = boundNode.childNodes[taggedText.offsetIndex]; + taggedText.binder.bind(view, scope, elementInjector, appInjector, childNode, eventHandler, animate); } } } - View _link(View view, List nodeList, Injector rootInjector) { - var elementInjectors = new List(elementBinders.length); + View _link(View view, Scope scope, List nodeList, EventHandler eventHandler, + Animate animate, DirectiveInjector rootInjector, Injector appInjector) { + var elementInjectors = new List(elementBinders.length); var directiveDefsByName = {}; var elementBinderIndex = 0; @@ -71,20 +86,23 @@ class TaggingViewFactory implements ViewFactory { // querySelectorAll doesn't return the node itself if (node.classes.contains('ng-binding')) { var tagged = elementBinders[elementBinderIndex]; - _bindTagged(tagged, elementBinderIndex, rootInjector, elementInjectors, view, node); + _bindTagged(tagged, elementBinderIndex, rootInjector, appInjector, + elementInjectors, view, node, scope, eventHandler, animate); elementBinderIndex++; } for (int j = 0; j < elts.length; j++, elementBinderIndex++) { TaggedElementBinder tagged = elementBinders[elementBinderIndex]; - _bindTagged(tagged, elementBinderIndex, rootInjector, elementInjectors, view, elts[j]); + _bindTagged(tagged, elementBinderIndex, rootInjector, appInjector, + elementInjectors, view, elts[j], scope, eventHandler, animate); } } else if (node.nodeType == dom.Node.TEXT_NODE || node.nodeType == dom.Node.COMMENT_NODE) { TaggedElementBinder tagged = elementBinders[elementBinderIndex]; assert(tagged.binder != null || tagged.isTopLevel); if (tagged.binder != null) { - _bindTagged(tagged, elementBinderIndex, rootInjector, elementInjectors, view, node); + _bindTagged(tagged, elementBinderIndex, rootInjector, appInjector, + elementInjectors, view, node, scope, eventHandler, animate); } elementBinderIndex++; } else { diff --git a/lib/core_dom/transcluding_component_factory.dart b/lib/core_dom/transcluding_component_factory.dart index 53907aa38..550b000c0 100644 --- a/lib/core_dom/transcluding_component_factory.dart +++ b/lib/core_dom/transcluding_component_factory.dart @@ -96,25 +96,24 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory { _directives); } - FactoryFn call(dom.Node node) { + List get callArgs => _CALL_ARGS; + static var _CALL_ARGS = [ DIRECTIVE_INJECTOR_KEY, INJECTOR_KEY, SCOPE_KEY, + VIEW_CACHE_KEY, HTTP_KEY, TEMPLATE_CACHE_KEY, + DIRECTIVE_MAP_KEY, NG_BASE_CSS_KEY, EVENT_HANDLER_KEY]; + Function call(dom.Node node) { // CSS is not supported. assert(_component.cssUrls == null || _component.cssUrls.isEmpty); var element = node as dom.Element; - return (Injector injector) { + return (DirectiveInjector injector, Injector appInjector, Scope scope, + ViewCache viewCache, Http http, TemplateCache templateCache, + DirectiveMap directives, NgBaseCss baseCss, EventHandler eventHandler) { - var childInjector; + DirectiveInjector childInjector; var childInjectorCompleter; // Used if the ViewFuture is available before the childInjector. var component = _component; - Scope scope = injector.getByKey(SCOPE_KEY); - ViewCache viewCache = injector.getByKey(VIEW_CACHE_KEY); - Http http = injector.getByKey(HTTP_KEY); - TemplateCache templateCache = injector.getByKey(TEMPLATE_CACHE_KEY); - DirectiveMap directives = injector.getByKey(DIRECTIVE_MAP_KEY); - NgBaseCss baseCss = injector.getByKey(NG_BASE_CSS_KEY); - var contentPort = new ContentPort(element); // Append the component's template as children @@ -124,12 +123,14 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory { elementFuture = _viewFuture.then((ViewFactory viewFactory) { contentPort.pullNodes(); if (childInjector != null) { - element.nodes.addAll(viewFactory(childInjector).nodes); + element.nodes.addAll( + viewFactory.call(childInjector.scope, childInjector, appInjector).nodes); return element; } else { childInjectorCompleter = new async.Completer(); return childInjectorCompleter.future.then((childInjector) { - element.nodes.addAll(viewFactory(childInjector).nodes); + element.nodes.addAll( + viewFactory.call(childInjector.scope, childInjector, appInjector).nodes); return element; }); } @@ -139,26 +140,18 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory { } TemplateLoader templateLoader = new TemplateLoader(elementFuture); - Scope shadowScope = scope.createChild({}); + Scope shadowScope = scope.createChild({'SHADOW': true}); - var probe; - var childModule = new Module() - ..bind(_ref.type) - ..bind(NgElement) - ..bind(ContentPort, toValue: contentPort) - ..bind(Scope, toValue: shadowScope) - ..bind(TemplateLoader, toValue: templateLoader) - ..bind(dom.ShadowRoot, toValue: new ShadowlessShadowRoot(element)); + childInjector = new ShadowlessComponentDirectiveInjector(injector, appInjector, + eventHandler, shadowScope, templateLoader, new ShadowlessShadowRoot(element), + contentPort); + childInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility); - if (_f.config.elementProbeEnabled) { - childModule.bind(ElementProbe, toFactory: (_) => probe); - } - childInjector = injector.createChild([childModule], name: SHADOW_DOM_INJECTOR_NAME); if (childInjectorCompleter != null) { childInjectorCompleter.complete(childInjector); } - var controller = childInjector.get(_ref.type); + var controller = childInjector.getByKey(_ref.typeKey); shadowScope.context[component.publishAs] = controller; BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope); return controller; diff --git a/lib/core_dom/view.dart b/lib/core_dom/view.dart index c53309f7d..7e949094b 100644 --- a/lib/core_dom/view.dart +++ b/lib/core_dom/view.dart @@ -12,10 +12,11 @@ part of angular.core.dom_internal; * */ class View { + final Scope scope; final List nodes; final EventHandler eventHandler; - View(this.nodes, this.eventHandler); + View(this.nodes, this.scope, this.eventHandler); void registerEvent(String eventName) { eventHandler.register(eventName); @@ -27,32 +28,47 @@ class View { * [placeholder] node that is used as the insertion point for view nodes. */ class ViewPort { + final DirectiveInjector directiveInjector; + final Injector appInjector; + final Scope scope; final dom.Node placeholder; final Animate _animate; final _views = []; - ViewPort(this.placeholder, this._animate); + ViewPort(this.directiveInjector, this.appInjector, this.scope, this.placeholder, this._animate); - void insert(View view, { View insertAfter }) { - dom.Node previousNode = _lastNode(insertAfter); - _viewsInsertAfter(view, insertAfter); + View insertNew(ViewFactory viewFactory, { View insertAfter, Scope viewScope}) { + if (viewScope == null) viewScope = scope.createChild(new PrototypeMap(scope.context)); + View view = viewFactory.call(viewScope, directiveInjector, appInjector); + return insert(view, insertAfter: insertAfter); + } - _animate.insert(view.nodes, placeholder.parentNode, - insertBefore: previousNode.nextNode); + View insert(View view, { View insertAfter }) { + scope.rootScope.domWrite(() { + dom.Node previousNode = _lastNode(insertAfter); + _viewsInsertAfter(view, insertAfter); + _animate.insert(view.nodes, placeholder.parentNode, insertBefore: previousNode.nextNode); + }); + return view; } - void remove(View view) { + View remove(View view) { + view.scope.destroy(); _views.remove(view); - _animate.remove(view.nodes); + scope.rootScope.domWrite(() { + _animate.remove(view.nodes); + }); + return view; } - void move(View view, { View moveAfter }) { + View move(View view, { View moveAfter }) { dom.Node previousNode = _lastNode(moveAfter); _views.remove(view); _viewsInsertAfter(view, moveAfter); - - _animate.move(view.nodes, placeholder.parentNode, - insertBefore: previousNode.nextNode); + scope.rootScope.domWrite(() { + _animate.move(view.nodes, placeholder.parentNode, insertBefore: previousNode.nextNode); + }); + return view; } void _viewsInsertAfter(View view, View insertAfter) { diff --git a/lib/core_dom/view_factory.dart b/lib/core_dom/view_factory.dart index 2d5e382d6..f6c1272a9 100644 --- a/lib/core_dom/view_factory.dart +++ b/lib/core_dom/view_factory.dart @@ -9,20 +9,23 @@ part of angular.core.dom_internal; * * The BoundViewFactory needs [Scope] to be created. */ +@deprecated class BoundViewFactory { ViewFactory viewFactory; - Injector injector; + Injector appInjector; + DirectiveInjector directiveInjector; - BoundViewFactory(this.viewFactory, this.injector); + BoundViewFactory(this.viewFactory, this.directiveInjector, this.appInjector); - View call(Scope scope) => - viewFactory(injector.createChild([new Module()..bindByKey(SCOPE_KEY, toValue: scope)])); + View call(Scope scope) => viewFactory(scope, directiveInjector, appInjector); } abstract class ViewFactory implements Function { - BoundViewFactory bind(Injector injector); + @deprecated + BoundViewFactory bind(DirectiveInjector directiveInjector, Injector appInjector); - View call(Injector injector, [List elements]); + View call(Scope scope, DirectiveInjector directiveInjector, Injector parentInjector, + [List elements]); } /** @@ -41,24 +44,29 @@ class WalkingViewFactory implements ViewFactory { eb is ElementBinderTreeRef)); } - BoundViewFactory bind(Injector injector) => - new BoundViewFactory(this, injector); + BoundViewFactory bind(DirectiveInjector directiveInjector, Injector appInjector) => + new BoundViewFactory(this, directiveInjector, appInjector); - View call(Injector injector, [List nodes]) { + View call(Scope scope, DirectiveInjector directiveInjector, Injector appInjector, [List nodes]) { if (nodes == null) nodes = cloneElements(templateElements); var timerId; try { assert((timerId = _perf.startTimer('ng.view')) != false); - var view = new View(nodes, injector.getByKey(EVENT_HANDLER_KEY)); - _link(view, nodes, elementBinders, injector); + EventHandler eventHandler = directiveInjector == null + ? appInjector.getByKey(EVENT_HANDLER_KEY) + : directiveInjector.getByKey(EVENT_HANDLER_KEY); + Animate animate = appInjector.getByKey(ANIMATE_KEY); + var view = new View(nodes, scope, eventHandler); + _link(view, scope, nodes, elementBinders, eventHandler, animate, directiveInjector, appInjector); return view; } finally { assert(_perf.stopTimer(timerId) != false); } } - View _link(View view, List nodeList, List elementBinders, - Injector parentInjector) { + View _link(View view, Scope scope, List nodeList, List elementBinders, + EventHandler eventHandler, Animate animate, + DirectiveInjector directiveInjector, Injector appInjector) { var preRenderedIndexOffset = 0; var directiveDefsByName = {}; @@ -90,17 +98,22 @@ class WalkingViewFactory implements ViewFactory { parentNode = new dom.DivElement()..append(node); } - var childInjector = binder != null ? - binder.bind(view, parentInjector, node) : - parentInjector; + DirectiveInjector childInjector; + if (binder == null) { + childInjector = directiveInjector; + } else { + childInjector = binder.bind(view, scope, directiveInjector, appInjector, node, eventHandler, animate); + // TODO(misko): Remove this after we remove controllers. No controllers -> 1to1 Scope:View. + if (childInjector != appInjector) scope = childInjector.scope; + } if (fakeParent) { // extract the node from the parentNode. nodeList[nodeListIndex] = parentNode.nodes[0]; } if (tree.subtrees != null) { - _link(view, node.nodes, tree.subtrees, childInjector); + _link(view, scope, node.nodes, tree.subtrees, eventHandler, animate, childInjector, appInjector); } } finally { assert(_perf.stopTimer(timerId) != false); @@ -190,9 +203,11 @@ String _html(obj) { class ElementProbe { final ElementProbe parent; final dom.Node element; - final Injector injector; + final Injector appInjector; + final DirectiveInjector directiveInjector; final Scope scope; - final directives = []; - ElementProbe(this.parent, this.element, this.injector, this.scope); + List get directives => directiveInjector.directives; + + ElementProbe(this.parent, this.element, this.directiveInjector, this.appInjector, this.scope); } diff --git a/lib/directive/module.dart b/lib/directive/module.dart index b0accbf27..2ec6f5219 100644 --- a/lib/directive/module.dart +++ b/lib/directive/module.dart @@ -24,6 +24,7 @@ import 'package:angular/core/annotation.dart'; import 'package:angular/core/module_internal.dart'; import 'package:angular/core/parser/parser.dart'; import 'package:angular/core_dom/module_internal.dart'; +import 'package:angular/core_dom/directive_injector.dart'; import 'package:angular/utils.dart'; import 'package:angular/change_detection/watch_group.dart'; import 'package:angular/change_detection/change_detection.dart'; @@ -68,7 +69,7 @@ class DirectiveModule extends Module { bind(NgBind, toValue: null); bind(NgBindTemplate, toValue: null); bind(NgBindHtml, toValue: null); - bind(dom.NodeValidator, toFactory: (_) => new dom.NodeValidatorBuilder.common()); + bind(dom.NodeValidator, toFactory: () => new dom.NodeValidatorBuilder.common()); bind(NgClass, toValue: null); bind(NgClassOdd, toValue: null); bind(NgClassEven, toValue: null); diff --git a/lib/directive/ng_base_css.dart b/lib/directive/ng_base_css.dart index 60afe0adc..74e41406d 100644 --- a/lib/directive/ng_base_css.dart +++ b/lib/directive/ng_base_css.dart @@ -8,7 +8,9 @@ part of angular.directive; * # Example *
*/ -@Decorator(selector: '[ng-base-css]') +@Decorator( + selector: '[ng-base-css]', + visibility: Visibility.CHILDREN) class NgBaseCss { List _urls = const []; diff --git a/lib/directive/ng_control.dart b/lib/directive/ng_control.dart index d49316ded..36350f73b 100644 --- a/lib/directive/ng_control.dart +++ b/lib/directive/ng_control.dart @@ -39,7 +39,7 @@ abstract class NgControl implements AttachAware, DetachAware { */ final infoStates = new Map>(); - NgControl(NgElement this._element, Injector injector, + NgControl(NgElement this._element, DirectiveInjector injector, Animate this._animate) : _parentControl = injector.parent.getByKey(NG_CONTROL_KEY); diff --git a/lib/directive/ng_form.dart b/lib/directive/ng_form.dart index f86237009..b54b9d501 100644 --- a/lib/directive/ng_form.dart +++ b/lib/directive/ng_form.dart @@ -19,9 +19,8 @@ part of angular.directive; module: NgForm.module, map: const { 'ng-form': '@name' }) class NgForm extends NgControl { - static final Module _module = new Module() - ..bind(NgControl, toFactory: (i) => i.getByKey(NG_FORM_KEY)); - static module() => _module; + static module(DirectiveBinder binder) => + binder.bind(NgControl, inject: NG_FORM_KEY, visibility: Visibility.CHILDREN); final Scope _scope; @@ -35,7 +34,7 @@ class NgForm extends NgControl { * * [element] - The form DOM element. * * [injector] - An instance of Injector. */ - NgForm(this._scope, NgElement element, Injector injector, Animate animate) : + NgForm(this._scope, NgElement element, DirectiveInjector injector, Animate animate) : super(element, injector, animate) { if (!element.node.attributes.containsKey('action')) { diff --git a/lib/directive/ng_if.dart b/lib/directive/ng_if.dart index 562bfa181..981bf9a3b 100644 --- a/lib/directive/ng_if.dart +++ b/lib/directive/ng_if.dart @@ -4,46 +4,29 @@ part of angular.directive; * Base class for NgIf and NgUnless. */ abstract class _NgUnlessIfAttrDirectiveBase { - final BoundViewFactory _boundViewFactory; + final ViewFactory _viewFactory; final ViewPort _viewPort; final Scope _scope; View _view; - /** - * The new child scope. This child scope is recreated whenever the `ng-if` - * subtree is inserted into the DOM and destroyed when it's removed from the - * DOM. Refer - * https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-prototypical-Inheritance prototypical inheritance - */ - Scope _childScope; - - _NgUnlessIfAttrDirectiveBase(this._boundViewFactory, this._viewPort, - this._scope); + _NgUnlessIfAttrDirectiveBase(this._viewFactory, this._viewPort, this._scope) { + assert(_viewFactory != null); + } // Override in subclass. void set condition(value); void _ensureViewExists() { if (_view == null) { - _childScope = _scope.createChild(new PrototypeMap(_scope.context)); - _view = _boundViewFactory(_childScope); - var view = _view; - _scope.rootScope.domWrite(() { - _viewPort.insert(view); - }); + _view = _viewPort.insertNew(_viewFactory); } } void _ensureViewDestroyed() { if (_view != null) { - var view = _view; - _scope.rootScope.domWrite(() { - _viewPort.remove(view); - }); - _childScope.destroy(); + _viewPort.remove(_view); _view = null; - _childScope = null; } } } @@ -94,9 +77,8 @@ abstract class _NgUnlessIfAttrDirectiveBase { selector:'[ng-if]', map: const {'.': '=>condition'}) class NgIf extends _NgUnlessIfAttrDirectiveBase { - NgIf(BoundViewFactory boundViewFactory, - ViewPort viewPort, - Scope scope): super(boundViewFactory, viewPort, scope); + NgIf(ViewFactory viewFactory, ViewPort viewPort, Scope scope) + : super(viewFactory, viewPort, scope); void set condition(value) { if (toBool(value)) { @@ -156,9 +138,8 @@ class NgIf extends _NgUnlessIfAttrDirectiveBase { map: const {'.': '=>condition'}) class NgUnless extends _NgUnlessIfAttrDirectiveBase { - NgUnless(BoundViewFactory boundViewFactory, - ViewPort viewPort, - Scope scope): super(boundViewFactory, viewPort, scope); + NgUnless(ViewFactory viewFactory, ViewPort viewPort, Scope scope) + : super(viewFactory, viewPort, scope); void set condition(value) { if (!toBool(value)) { diff --git a/lib/directive/ng_include.dart b/lib/directive/ng_include.dart index be450524f..6ef25a32b 100644 --- a/lib/directive/ng_include.dart +++ b/lib/directive/ng_include.dart @@ -23,13 +23,15 @@ class NgInclude { final dom.Element element; final Scope scope; final ViewCache viewCache; - final Injector injector; + final Injector appInjector; + final DirectiveInjector directiveInjector; final DirectiveMap directives; View _view; Scope _scope; - NgInclude(this.element, this.scope, this.viewCache, this.injector, this.directives); + NgInclude(this.element, this.scope, this.viewCache, + this.directiveInjector, this.appInjector, this.directives); _cleanUp() { if (_view == null) return; @@ -42,11 +44,10 @@ class NgInclude { _scope = null; } - _updateContent(createView) { + _updateContent(ViewFactory viewFactory) { // create a new scope _scope = scope.createChild(new PrototypeMap(scope.context)); - _view = createView(injector.createChild([new Module() - ..bind(Scope, toValue: _scope)])); + _view = viewFactory(_scope, directiveInjector, appInjector); _view.nodes.forEach((node) => element.append(node)); } diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart index 2ce09b0e8..c68b693c3 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -44,7 +44,7 @@ class NgModel extends NgControl implements AttachAware { Watch _watch; bool _watchCollection; - NgModel(this._scope, NgElement element, Injector injector, NodeAttrs attrs, + NgModel(this._scope, NgElement element, DirectiveInjector injector, NodeAttrs attrs, Animate animate) : super(element, injector, animate) { @@ -599,8 +599,9 @@ class NgBindTypeForDateLike { @Decorator(selector: 'input[type=week][ng-model]', module: InputDateLike.moduleFactory) class InputDateLike { - static Module moduleFactory() => new Module()..bind(NgBindTypeForDateLike, - toFactory: (Injector i) => new NgBindTypeForDateLike(i.getByKey(ELEMENT_KEY))); + static void moduleFactory(DirectiveBinder binder) + => binder.bind(NgBindTypeForDateLike, + toFactory: (dom.Element e) => new NgBindTypeForDateLike(e), inject: [ELEMENT_KEY]); final dom.InputElement inputElement; final NgModel ngModel; final NgModelOptions ngModelOptions; @@ -694,8 +695,7 @@ final _uidCounter = new _UidCounter(); @Decorator(selector: 'input[type=radio][ng-model][ng-value]') @Decorator(selector: 'option[ng-value]') class NgValue { - static Module _module = new Module()..bind(NgValue); - static Module moduleFactory() => _module; + static module(DirectiveBinder binder) => binder.bind(NgValue, visibility: Visibility.LOCAL); final dom.Element element; var _value; @@ -777,7 +777,7 @@ class NgFalseValue { */ @Decorator( selector: 'input[type=radio][ng-model]', - module: NgValue.moduleFactory) + module: NgValue.module) class InputRadio { final dom.RadioButtonInputElement radioButtonElement; final NgModel ngModel; diff --git a/lib/directive/ng_model_select.dart b/lib/directive/ng_model_select.dart index 1e8e6e047..93fd87259 100644 --- a/lib/directive/ng_model_select.dart +++ b/lib/directive/ng_model_select.dart @@ -18,7 +18,8 @@ part of angular.directive; * */ @Decorator( - selector: 'select[ng-model]') + selector: 'select[ng-model]', + visibility: Visibility.CHILDREN) class InputSelect implements AttachAware { final expando = new Expando(); final dom.SelectElement _selectElement; @@ -50,7 +51,9 @@ class InputSelect implements AttachAware { _model.watchCollection = true; _mode = new _MultipleSelectionMode(expando, _selectElement, _model); } - _mode.onModelChange(_model.viewValue); + _scope.rootScope.domRead(() { + _mode.onModelChange(_model.viewValue); + }); }); _selectElement.onChange.listen((event) => _mode.onViewChange(event)); @@ -89,7 +92,7 @@ class InputSelect implements AttachAware { * provides [ng-value] which allows binding to any expression. * */ -@Decorator(selector: 'option', module: NgValue.moduleFactory) +@Decorator(selector: 'option', module: NgValue.module) class OptionValue implements AttachAware, DetachAware { final InputSelect _inputSelectDirective; @@ -129,10 +132,10 @@ class _SelectMode { destroy() {} get _options => select.querySelectorAll('option'); - _forEachOption(fn, [quiteOnReturn = false]) { + _forEachOption(fn, [quitOnReturn = false]) { for (var i = 0; i < _options.length; i++) { var retValue = fn(_options[i], i); - if (quiteOnReturn && retValue != null) return retValue; + if (quitOnReturn && retValue != null) return retValue; } return null; } diff --git a/lib/directive/ng_repeat.dart b/lib/directive/ng_repeat.dart index 194316c2f..bdec4298b 100644 --- a/lib/directive/ng_repeat.dart +++ b/lib/directive/ng_repeat.dart @@ -136,7 +136,6 @@ class NgRepeat { _onCollectionChange(changes); } else if (_rows != null) { _rows.forEach((row) { - row.scope.destroy(); _viewPort.remove(row.view); }); _rows = null; @@ -172,7 +171,6 @@ class NgRepeat { removeFn((CollectionChangeItem removal) { var index = removal.previousIndex; var row = _rows[index]; - row.scope.destroy(); _viewPort.remove(row.view); leftInDom.removeAt(domLength - 1 - index); }); diff --git a/lib/directive/ng_switch.dart b/lib/directive/ng_switch.dart index c9aec1275..94028231d 100644 --- a/lib/directive/ng_switch.dart +++ b/lib/directive/ng_switch.dart @@ -75,7 +75,6 @@ class NgSwitch { currentViews ..forEach((_ViewScopePair pair) { pair.port.remove(pair.view); - pair.scope.destroy(); }) ..clear(); diff --git a/lib/introspection.dart b/lib/introspection.dart index 7293dd45d..c04ed9205 100644 --- a/lib/introspection.dart +++ b/lib/introspection.dart @@ -8,6 +8,7 @@ import 'package:di/di.dart'; import 'package:angular/introspection_js.dart'; import 'package:angular/core/module_internal.dart'; import 'package:angular/core_dom/module_internal.dart'; +import 'package:angular/core_dom/directive_injector.dart' show DirectiveInjector; /** * Return the [ElementProbe] object for the closest [Element] in the hierarchy. @@ -47,7 +48,8 @@ ElementProbe ngProbe(nodeOrSelector) { * application from the browser's REPL, unit or end-to-end tests. The function * is not intended to be called from Angular application. */ -Injector ngInjector(nodeOrSelector) => ngProbe(nodeOrSelector).injector; +Injector ngInjector(nodeOrSelector) => ngProbe(nodeOrSelector).appInjector; +DirectiveInjector directiveInjector(nodeOrSelector) => ngProbe(nodeOrSelector).directiveInjector; /** diff --git a/lib/introspection_js.dart b/lib/introspection_js.dart index b3b99f867..07ce9431e 100644 --- a/lib/introspection_js.dart +++ b/lib/introspection_js.dart @@ -24,7 +24,7 @@ void publishToJavaScript() { _jsInjector(ngInjector(nodeOrSelector))) ..['ngScope'] = new js.JsFunction.withThis((_, nodeOrSelector) => _jsScope(ngScope(nodeOrSelector), - ngProbe(nodeOrSelector).injector.getByKey(SCOPE_STATS_CONFIG_KEY))) + ngProbe(nodeOrSelector).appInjector.getByKey(SCOPE_STATS_CONFIG_KEY))) ..['ngQuery'] = new js.JsFunction.withThis((_, dom.Node node, String selector, [String containsText]) => new js.JsArray.from(ngQuery(node, selector, containsText))); } @@ -32,13 +32,14 @@ void publishToJavaScript() { js.JsObject _jsProbe(ElementProbe probe) { return new js.JsObject.jsify({ "element": probe.element, - "injector": _jsInjector(probe.injector), - "scope": _jsScope(probe.scope, probe.injector.getByKey(SCOPE_STATS_CONFIG_KEY)), + "injector": _jsInjector(probe.appInjector), + "directiveInjector": _jsInjector(probe.directiveInjector), + "scope": _jsScope(probe.scope, probe.directiveInjector.getByKey(SCOPE_STATS_CONFIG_KEY)), "directives": probe.directives.map((directive) => _jsDirective(directive)) })..['_dart_'] = probe; } -js.JsObject _jsInjector(Injector injector) => +js.JsObject _jsInjector(injector) => new js.JsObject.jsify({"get": injector.get})..['_dart_'] = injector; js.JsObject _jsScope(Scope scope, ScopeStatsConfig config) { diff --git a/lib/mock/module.dart b/lib/mock/module.dart index ea3407337..e56387610 100644 --- a/lib/mock/module.dart +++ b/lib/mock/module.dart @@ -21,6 +21,7 @@ import 'dart:js' as js; import 'package:angular/angular.dart'; import 'package:angular/core/module_internal.dart'; import 'package:angular/core_dom/module_internal.dart'; +import 'package:angular/core_dom/directive_injector.dart'; import 'package:angular/core/parser/parser.dart'; import 'package:angular/mock/static_keys.dart'; import 'package:di/di.dart'; @@ -62,8 +63,8 @@ class AngularMockModule extends Module { bind(MockHttpBackend); bind(Element, toValue: document.body); bind(Node, toValue: document.body); - bind(HttpBackend, toFactory: (Injector i) => i.getByKey(MOCK_HTTP_BACKEND_KEY)); - bind(VmTurnZone, toFactory: (_) { + bind(HttpBackend, inject:[MOCK_HTTP_BACKEND_KEY]); + bind(VmTurnZone, toFactory: () { return new VmTurnZone() ..onError = (e, s, LongStackTrace ls) => dump('EXCEPTION: $e\n$s\n$ls'); }); diff --git a/lib/mock/probe.dart b/lib/mock/probe.dart index 09bc60a81..f68d34098 100644 --- a/lib/mock/probe.dart +++ b/lib/mock/probe.dart @@ -10,13 +10,15 @@ part of angular.mock; * rootScope.myProbe.directive(SomeAttrDirective); */ @Decorator(selector: '[probe]') +@deprecated class Probe implements DetachAware { final Scope scope; - final Injector injector; + final DirectiveInjector directiveInjector; + final Injector appInjector; final Element element; String _probeName; - Probe(this.scope, this.injector, this.element) { + Probe(this.scope, this.directiveInjector, this.appInjector, this.element) { _probeName = element.attributes['probe']; scope.rootScope.context[_probeName] = this; } @@ -28,6 +30,6 @@ class Probe implements DetachAware { /** * Retrieve a Directive at the current element. */ - directive(Type type) => injector.get(type); + directive(Type type) => directiveInjector.get(type); } diff --git a/lib/mock/test_bed.dart b/lib/mock/test_bed.dart index 54578868b..fc02f71fa 100644 --- a/lib/mock/test_bed.dart +++ b/lib/mock/test_bed.dart @@ -35,10 +35,7 @@ class TestBed { * An option [scope] parameter can be supplied to link it with non root scope. */ Element compile(html, {Scope scope, DirectiveMap directives}) { - var injector = this.injector; - if (scope != null) { - injector = injector.createChild([new Module()..bind(Scope, toValue: scope)]); - } + if (scope == null) scope = rootScope; if (html is String) { rootElements = toNodeList(html); } else if (html is Node) { @@ -52,7 +49,7 @@ class TestBed { if (directives == null) { directives = injector.getByKey(DIRECTIVE_MAP_KEY); } - rootView = compiler(rootElements, directives)(injector, rootElements); + rootView = compiler(rootElements, directives)(scope, null, injector, rootElements); return rootElement; } diff --git a/lib/mock/test_injection.dart b/lib/mock/test_injection.dart index 33d7b8f2f..a2c004698 100644 --- a/lib/mock/test_injection.dart +++ b/lib/mock/test_injection.dart @@ -3,26 +3,26 @@ library angular.mock.test_injection; import 'package:angular/application_factory.dart'; import 'package:angular/mock/module.dart'; import 'package:di/di.dart'; -import 'package:di/dynamic_injector.dart'; +import 'dart:mirrors'; _SpecInjector _currentSpecInjector = null; class _SpecInjector { - DynamicInjector moduleInjector; - DynamicInjector injector; + Injector moduleInjector; + Injector injector; dynamic injectiorCreateLocation; final modules = []; final initFns = []; _SpecInjector() { var moduleModule = new Module() - ..bind(Module, toFactory: (Injector injector) => addModule(new Module())); - moduleInjector = new DynamicInjector(modules: [moduleModule]); + ..bind(Module, toFactory: () => addModule(new Module())); + moduleInjector = new ModuleInjector([moduleModule]); } addModule(module) { if (injector != null) { - throw ["Injector already crated, can not add more modules."]; + throw ["Injector already created, can not add more modules."]; } modules.add(module); return module; @@ -34,7 +34,7 @@ class _SpecInjector { } try { if (fnOrModule is Function) { - var initFn = moduleInjector.invoke(fnOrModule); + var initFn = _invoke(moduleInjector, fnOrModule); if (initFn is Function) initFns.add(initFn); } else if (fnOrModule is Module) { addModule(fnOrModule); @@ -50,12 +50,12 @@ class _SpecInjector { try { if (injector == null) { injectiorCreateLocation = declarationStack; - injector = new DynamicInjector(modules: modules); // Implicit injection is disabled. + injector = new ModuleInjector(modules); // Implicit injection is disabled. initFns.forEach((fn) { - injector.invoke(fn); + _invoke(injector, fn); }); } - injector.invoke(fn); + _invoke(injector, fn); } catch (e, s) { throw "$e\n$s\nDECLARED AT:$declarationStack"; } @@ -65,6 +65,25 @@ class _SpecInjector { injector = null; injectiorCreateLocation = null; } + + _invoke(Injector injector, Function fn) { + ClosureMirror cm = reflect(fn); + MethodMirror mm = cm.function; + List args = mm.parameters.map((ParameterMirror parameter) { + var metadata = parameter.metadata; + Key key = new Key( + (parameter.type as ClassMirror).reflectedType, + metadata.isEmpty ? null : metadata.first.type.reflectedType); + try { + return injector.getByKey(key); + } on NoProviderError catch (e) { + (e as NoProviderError).appendKey(key); + rethrow; + } + }).toList(); + + return cm.apply(args).reflectee; + } } /** diff --git a/lib/playback/playback_http.dart b/lib/playback/playback_http.dart index 7efe1d2f6..4872f0c62 100644 --- a/lib/playback/playback_http.dart +++ b/lib/playback/playback_http.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:convert' show JSON; import 'dart:html'; +import 'package:di/di.dart' show Injectable; import 'package:angular/core_dom/module_internal.dart'; import 'package:angular/core/annotation_src.dart'; import 'package:angular/mock/http_backend.dart' as mock; diff --git a/lib/routing/module.dart b/lib/routing/module.dart index 3d497ae32..9e479dab6 100644 --- a/lib/routing/module.dart +++ b/lib/routing/module.dart @@ -142,10 +142,10 @@ part 'ng_bind_route.dart'; class RoutingModule extends Module { RoutingModule({bool usePushState: true}) { bind(NgRoutingUsePushState); - bind(Router, toFactory: (injector) { - var useFragment = !injector.getByKey(NG_ROUTING_USE_PUSH_STATE_KEY).usePushState; - return new Router(useFragment: useFragment, windowImpl: injector.getByKey(WINDOW_KEY)); - }); + bind(Router, toFactory: (NgRoutingUsePushState state, Window window) { + var useFragment = !state.usePushState; + return new Router(useFragment: useFragment, windowImpl: window); + }, inject: [NG_ROUTING_USE_PUSH_STATE_KEY, WINDOW_KEY]); bind(NgRoutingHelper); bind(RouteProvider, toValue: null); bind(RouteInitializer, toValue: null); diff --git a/lib/routing/ng_bind_route.dart b/lib/routing/ng_bind_route.dart index 935a65ee2..52c786626 100644 --- a/lib/routing/ng_bind_route.dart +++ b/lib/routing/ng_bind_route.dart @@ -29,13 +29,10 @@ part of angular.routing; class NgBindRoute implements RouteProvider { String routeName; final Router _router; - final Injector _injector; + final DirectiveInjector _injector; - static final Module _module = new Module() - ..bind(RouteProvider, toFactory: (i) => i.getByKey(NG_BIND_ROUTE_KEY), - visibility: Directive.CHILDREN_VISIBILITY); - - static Module module() => _module; + static void module(DirectiveBinder binder) + => binder.bind(RouteProvider, inject: NG_BIND_ROUTE_KEY, visibility: Visibility.CHILDREN); // We inject NgRoutingHelper to force initialization of routing. NgBindRoute(this._router, this._injector, NgRoutingHelper _); diff --git a/lib/routing/ng_view.dart b/lib/routing/ng_view.dart index 02b9c4783..d7830c31a 100644 --- a/lib/routing/ng_view.dart +++ b/lib/routing/ng_view.dart @@ -55,14 +55,13 @@ part of angular.routing; selector: 'ng-view', module: NgView.module) class NgView implements DetachAware, RouteProvider { - static final Module _module = new Module() - ..bind(RouteProvider, toFactory: (i) => i.getByKey(NG_VIEW_KEY)); - - static Module module() => _module; + static void module(DirectiveBinder binder) + => binder.bind(RouteProvider, inject: NG_VIEW_KEY, visibility: Visibility.CHILDREN); final NgRoutingHelper _locationService; final ViewCache _viewCache; - final Injector _injector; + final Injector _appInjector; + final DirectiveInjector _dirInjector; final Element _element; final Scope _scope; RouteHandle _route; @@ -71,11 +70,12 @@ class NgView implements DetachAware, RouteProvider { Scope _childScope; Route _viewRoute; - NgView(this._element, this._viewCache, Injector injector, Router router, this._scope) - : _injector = injector, - _locationService = injector.getByKey(NG_ROUTING_HELPER_KEY) + NgView(this._element, this._viewCache, DirectiveInjector dirInjector, this._appInjector, + Router router, this._scope) + : _dirInjector = dirInjector, + _locationService = dirInjector.getByKey(NG_ROUTING_HELPER_KEY) { - RouteProvider routeProvider = injector.parent.getByKey(NG_VIEW_KEY); + RouteProvider routeProvider = dirInjector.parent.getByKey(NG_VIEW_KEY); _route = routeProvider != null ? routeProvider.route.newHandle() : router.root.newHandle(); @@ -106,19 +106,18 @@ class NgView implements DetachAware, RouteProvider { _cleanUp(); }); - var viewInjector = modules == null ? - _injector : - forceNewDirectivesAndFormatters(_injector, modules); + Injector viewInjector = modules == null ? + _appInjector : + forceNewDirectivesAndFormatters(_appInjector, modules); var newDirectives = viewInjector.getByKey(DIRECTIVE_MAP_KEY); var viewFuture = viewDef.templateHtml != null ? new Future.value(_viewCache.fromHtml(viewDef.templateHtml, newDirectives)) : _viewCache.fromUrl(viewDef.template, newDirectives); - viewFuture.then((viewFactory) { + viewFuture.then((ViewFactory viewFactory) { _cleanUp(); _childScope = _scope.createChild(new PrototypeMap(_scope.context)); - _view = viewFactory( - viewInjector.createChild([new Module()..bind(Scope, toValue: _childScope)])); + _view = viewFactory(_childScope, _dirInjector, viewInjector); _view.nodes.forEach((elm) => _element.append(elm)); }); } diff --git a/lib/tools/expression_extractor.dart b/lib/tools/expression_extractor.dart index 006ba83e1..e4029d13e 100644 --- a/lib/tools/expression_extractor.dart +++ b/lib/tools/expression_extractor.dart @@ -10,7 +10,6 @@ import 'package:angular/tools/io_impl.dart'; import 'package:angular/tools/common.dart'; import 'package:di/di.dart'; -import 'package:di/dynamic_injector.dart'; import 'package:angular/core/parser/parser.dart'; import 'package:angular/tools/parser_getter_setter/generator.dart'; diff --git a/lib/tools/transformer/expression_generator.dart b/lib/tools/transformer/expression_generator.dart index 020e5d585..aeeb5f8ea 100644 --- a/lib/tools/transformer/expression_generator.dart +++ b/lib/tools/transformer/expression_generator.dart @@ -2,7 +2,9 @@ library angular.tools.transformer.expression_generator; import 'dart:async'; import 'package:analyzer/src/generated/element.dart'; +import 'package:angular/cache/module.dart'; import 'package:angular/core/parser/parser.dart'; +import 'package:angular/core/parser/lexer.dart'; import 'package:angular/tools/html_extractor.dart'; import 'package:angular/tools/parser_getter_setter/generator.dart'; import 'package:angular/tools/source_crawler.dart'; @@ -12,7 +14,7 @@ import 'package:angular/tools/transformer/referenced_uris.dart'; import 'package:barback/barback.dart'; import 'package:code_transformers/resolver.dart'; import 'package:di/di.dart'; -import 'package:di/dynamic_injector.dart'; +import 'package:di/di_dynamic.dart' as di; import 'package:path/path.dart' as path; /** @@ -28,6 +30,7 @@ class ExpressionGenerator extends Transformer with ResolverTransformer { ExpressionGenerator(this.options, Resolvers resolvers) { this.resolvers = resolvers; + di.setupModuleTypeReflector(); } Future applyResolver(Transform transform, Resolver resolver) { @@ -46,10 +49,12 @@ class ExpressionGenerator extends Transformer with ResolverTransformer { .forEach(htmlExtractor.parseHtml) .then((_) { var module = new Module() + ..install(new CacheModule()) ..bind(Parser, toImplementation: DynamicParser) - ..bind(ParserBackend, toImplementation: DartGetterSetterGen); - var injector = - new DynamicInjector(modules: [module], allowImplicitInjection: true); + ..bind(ParserBackend, toImplementation: DartGetterSetterGen) + ..bind(Lexer) + ..bind(_ParserGetterSetter); + var injector = new ModuleInjector([module]); injector.get(_ParserGetterSetter).generateParser( htmlExtractor.expressions.toList(), outputBuffer); diff --git a/lib/tools/transformer/options.dart b/lib/tools/transformer/options.dart index 3a730075a..fc6560a17 100644 --- a/lib/tools/transformer/options.dart +++ b/lib/tools/transformer/options.dart @@ -1,6 +1,6 @@ library angular.tools.transformer.options; -import 'package:di/transformer/options.dart' as di; +import 'package:di/transformer.dart' as di show TransformOptions; /** Options used by Angular transformers */ class TransformOptions { diff --git a/lib/tools/transformer/static_angular_generator.dart b/lib/tools/transformer/static_angular_generator.dart index 284b9924f..69f130eea 100644 --- a/lib/tools/transformer/static_angular_generator.dart +++ b/lib/tools/transformer/static_angular_generator.dart @@ -51,9 +51,6 @@ class StaticAngularGenerator extends Transformer with ResolverTransformer { _addImport(transaction, unit, '${generatedFilePrefix}_static_metadata.dart', 'generated_static_metadata'); - _addImport(transaction, unit, - '${generatedFilePrefix}_static_injector.dart', - 'generated_static_injector'); var printer = transaction.commit(); var url = id.path.startsWith('lib/') @@ -82,7 +79,6 @@ class _NgDynamicToStaticVisitor extends GeneralizingAstVisitor { var args = m.argumentList; transaction.edit(args.beginToken.offset + 1, args.end - 1, - 'generated_static_injector.factories, ' 'generated_static_metadata.typeAnnotations, ' 'generated_static_expressions.getters, ' 'generated_static_expressions.setters, ' diff --git a/lib/transformer.dart b/lib/transformer.dart index c884a6c16..5c68cbb91 100644 --- a/lib/transformer.dart +++ b/lib/transformer.dart @@ -9,8 +9,7 @@ import 'package:angular/tools/transformer/html_dart_references_generator.dart'; import 'package:angular/tools/transformer/options.dart'; import 'package:barback/barback.dart'; import 'package:code_transformers/resolver.dart'; -import 'package:di/transformer/injector_generator.dart' show InjectorGenerator; -import 'package:di/transformer/options.dart' as di; +import 'package:di/transformer.dart' as di; import 'package:path/path.dart' as path; @@ -34,7 +33,7 @@ class AngularTransformerGroup implements TransformerGroup { TransformOptions _parseSettings(Map args) { // Default angular annotations for injectable types var annotations = [ - 'angular.core.annotation_src.Injectable', + 'di.annotations.Injectable', 'angular.core.annotation_src.Decorator', 'angular.core.annotation_src.Controller', 'angular.core.annotation_src.Component', @@ -120,10 +119,10 @@ Map _readStringMapValue(Map args, String name) { List> _createPhases(TransformOptions options) { var resolvers = new Resolvers(options.sdkDirectory); return [ - [new HtmlDartReferencesGenerator(options)], - [new _SerialTransformer([ + [ new HtmlDartReferencesGenerator(options) ], + [ new di.InjectorGenerator(options.diOptions, resolvers) ], + [ new _SerialTransformer([ new ExpressionGenerator(options, resolvers), - new InjectorGenerator(options.diOptions, resolvers), new MetadataGenerator(options, resolvers), new StaticAngularGenerator(options, resolvers) ])] diff --git a/pubspec.lock b/pubspec.lock index 3cdc8c6dd..06bb9e669 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -32,7 +32,7 @@ packages: di: description: di source: hosted - version: "1.0.0" + version: "2.0.0-alpha.1" guinness: description: guinness source: hosted diff --git a/pubspec.yaml b/pubspec.yaml index fe4f30bc1..2f4a390c8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,12 +14,12 @@ environment: sdk: '>=1.4.0' dependencies: args: '>=0.10.0 < 0.11.0' - analyzer: '>=0.13.0 <0.14.0' - barback: '>=0.11.1 <0.14.0' + analyzer: '>=0.13.0 <0.17.0' + barback: '>=0.13.0 <0.17.0' browser: '>=0.10.0 <0.11.0' code_transformers: '>=0.1.3 <0.2.0' collection: '>=0.9.1 <1.0.0' - di: '>=1.0.0 <2.0.0' + di: '>=2.0.0-alpha.1' html5lib: '>=0.10.0 <0.11.0' intl: '>=0.8.7 <0.10.0' perf_api: '>=0.0.8 <0.1.0' diff --git a/scripts/git/validate-commit-msg.js b/scripts/git/validate-commit-msg.js index b00b6bf59..a8fd3cbca 100755 --- a/scripts/git/validate-commit-msg.js +++ b/scripts/git/validate-commit-msg.js @@ -23,7 +23,8 @@ var TYPES = { refactor: true, test: true, chore: true, - revert: true + revert: true, + perf: true }; diff --git a/test/_specs.dart b/test/_specs.dart index 69e55a03f..5573b9263 100644 --- a/test/_specs.dart +++ b/test/_specs.dart @@ -2,6 +2,7 @@ library ng_specs; import 'dart:html' hide Animation; +import 'package:di/di_dynamic.dart'; import 'package:angular/angular.dart'; import 'package:angular/mock/module.dart'; @@ -14,7 +15,6 @@ export 'package:guinness/guinness_html.dart'; export 'package:mock/mock.dart'; export 'package:di/di.dart'; -export 'package:di/dynamic_injector.dart'; export 'package:angular/angular.dart'; export 'package:angular/application.dart'; export 'package:angular/introspection.dart'; @@ -114,6 +114,7 @@ _removeNgBinding(node) { } main() { + setupModuleTypeReflector(); gns.beforeEach(setUpInjector, priority:3); gns.afterEach(tearDownInjector); diff --git a/test/angular_spec.dart b/test/angular_spec.dart index 4ad13f144..a344c2d7f 100644 --- a/test/angular_spec.dart +++ b/test/angular_spec.dart @@ -104,13 +104,15 @@ main() { "angular.core.annotation_src.DetachAware", "angular.core.annotation_src.Directive", "angular.core.annotation_src.DirectiveAnnotation", + "angular.core.annotation_src.DirectiveBinder", + "angular.core.annotation_src.DirectiveBinderFn", "angular.core.annotation_src.Formatter", - "angular.core.annotation_src.Injectable", "angular.core.annotation_src.NgAttr", "angular.core.annotation_src.NgCallback", "angular.core.annotation_src.NgOneWay", "angular.core.annotation_src.NgOneWayOneTime", "angular.core.annotation_src.NgTwoWay", + "angular.core.annotation_src.Visibility", "angular.core.dom_internal.Animate", "angular.core.dom_internal.Animation", "angular.core.dom_internal.AnimationResult", @@ -225,11 +227,13 @@ main() { "angular.formatter_internal.OrderBy", "angular.formatter_internal.Stringify", "angular.formatter_internal.Uppercase", + "angular.introspection.directiveInjector", "angular.introspection.ngDirectives", "angular.introspection.ngInjector", "angular.introspection.ngProbe", "angular.introspection.ngQuery", "angular.introspection.ngScope", + "angular.node_injector.DirectiveInjector", "angular.routing.NgBindRoute", "angular.routing.ngRoute", "angular.routing.NgRouteCfg", @@ -247,14 +251,24 @@ main() { "change_detection.AvgStopwatch", "change_detection.FieldGetterFactory", "di.CircularDependencyError", - "di.FactoryFn", + "di.BaseError", + "di.Binding", + "di.DynamicReflectorError", + "di.Factory", + "di.annotations.Injectable", + "di.annotations.Injectables", "di.Injector", - "di.InvalidBindingError", "di.key.Key", + "di.key.key", "di.Module", + "di.ModuleInjector", + "di.NullReflector", + "di.NullReflectorError", "di.NoProviderError", - "di.TypeFactory", - "di.Visibility", + "di.NoParentError", + "di.RootInjector", + "di.ResolvingError", + "di.TypeReflector", "route.client.Routable", "route.client.Route", "route.client.RouteEnterEvent", diff --git a/test/core/annotation_src_spec.dart b/test/core/annotation_src_spec.dart index 2bfeafce3..dec1b2c9b 100644 --- a/test/core/annotation_src_spec.dart +++ b/test/core/annotation_src_spec.dart @@ -40,7 +40,7 @@ void main() => describe('annotations', () { applyAuthorStyles: true, resetStyleInheritance: true, publishAs: '', - module: (){}, + module: (i){}, map: {}, selector: '', visibility: Directive.LOCAL_VISIBILITY, @@ -63,7 +63,7 @@ void main() => describe('annotations', () { children: 'xxx', map: {}, selector: '', - module: (){}, + module: (i){}, visibility: Directive.LOCAL_VISIBILITY, exportExpressions: [], exportExpressionAttrs: [] @@ -84,7 +84,7 @@ void main() => describe('annotations', () { children: 'xxx', map: {}, selector: '', - module: (){}, + module: (i){}, visibility: Directive.LOCAL_VISIBILITY, exportExpressions: [], exportExpressionAttrs: [] diff --git a/test/core/core_directive_spec.dart b/test/core/core_directive_spec.dart index ab4cd3149..12632dc14 100644 --- a/test/core/core_directive_spec.dart +++ b/test/core/core_directive_spec.dart @@ -11,11 +11,11 @@ void main() { }); it('should extract attr map from annotated component', (DirectiveMap directives) { - var annotations = directives.annotationsFor(AnnotatedIoComponent); - expect(annotations.length).toEqual(1); - expect(annotations[0] is Component).toBeTruthy(); + var tuples = directives['annotated-io']; + expect(tuples.length).toEqual(1); + expect(tuples[0].directive is Component).toBeTruthy(); - Component annotation = annotations[0]; + Component annotation = tuples[0].directive; expect(annotation.selector).toEqual('annotated-io'); expect(annotation.visibility).toEqual(Directive.LOCAL_VISIBILITY); expect(annotation.exportExpressions).toEqual(['exportExpressions']); @@ -77,11 +77,11 @@ void main() { }); it("should extract attr map from annotated component which inherits other component", (DirectiveMap directives) { - var annotations = directives.annotationsFor(Sub); - expect(annotations.length).toEqual(1); - expect(annotations[0] is Directive).toBeTruthy(); + var tupls = directives['[sub]']; + expect(tupls.length).toEqual(1); + expect(tupls[0].directive is Directive).toBeTruthy(); - Directive annotation = annotations[0]; + Directive annotation = tupls[0].directive; expect(annotation.selector).toEqual('[sub]'); expect(annotation.map).toEqual({ "foo": "=>foo", @@ -112,7 +112,7 @@ class NullParser implements Parser { 'foo': '=>foo' }) class AnnotatedIoComponent { - static module() => new Module()..bind(String, toFactory: (i) => i.get(AnnotatedIoComponent), + static module(i) => i.bind(String, toFactory: (i) => i.get(AnnotatedIoComponent), visibility: Directive.LOCAL_VISIBILITY); AnnotatedIoComponent(Scope scope) { diff --git a/test/core/parser/parser_spec.dart b/test/core/parser/parser_spec.dart index 60ecd68dc..f3badbb68 100644 --- a/test/core/parser/parser_spec.dart +++ b/test/core/parser/parser_spec.dart @@ -445,7 +445,7 @@ main() { }); xdescribe('reserved words', () { - iit('should support reserved words in member get access', () { + it('should support reserved words in member get access', () { for (String reserved in RESERVED_WORDS) { expect(parser("o.$reserved").eval({ 'o': new Object() })).toEqual(null); expect(parser("o.$reserved").eval({ 'o': { reserved: reserved }})).toEqual(reserved); @@ -1128,10 +1128,10 @@ main() { it('should parse formatters', () { expect(() { eval("1|nonexistent"); - }).toThrow('No Formatter: nonexistent found!'); + }).toThrow('No formatter \'nonexistent\' found!'); expect(() { eval("1|nonexistent", formatters); - }).toThrow('No Formatter: nonexistent found!'); + }).toThrow('No formatter \'nonexistent\' found!'); context['offset'] = 3; expect(eval("'abcd'|substring:1:offset")).toEqual("bc"); @@ -1142,12 +1142,12 @@ main() { var expression = parser("'World'|hello"); expect(() { expression.eval({}, formatters); - }).toThrow('No Formatter: hello found!'); + }).toThrow('No formatter \'hello\' found!'); var module = new Module() + ..bind(FormatterMap) ..bind(HelloFormatter); - var childInjector = injector.createChild([module], - forceNewInstances: [FormatterMap]); + var childInjector = injector.createChild([module]); var newFormatters = childInjector.get(FormatterMap); expect(expression.eval({}, newFormatters)).toEqual('Hello, World!'); diff --git a/test/core/registry_spec.dart b/test/core/registry_spec.dart deleted file mode 100644 index 1a00d2af0..000000000 --- a/test/core/registry_spec.dart +++ /dev/null @@ -1,64 +0,0 @@ -library registry_spec; - -import '../_specs.dart'; -import 'package:angular/application_factory.dart'; - -main() { - describe('RegistryMap', () { - it('should allow for multiple registry keys to be added', () { - var module = new Module() - ..bind(MyMap) - ..bind(A1) - ..bind(A2); - - var injector = applicationFactory().addModule(module).createInjector(); - expect(() { - injector.get(MyMap); - }).not.toThrow(); - }); - - it('should iterate over all types', () { - var module = new Module() - ..bind(MyMap) - ..bind(A1); - - var injector = applicationFactory().addModule(module).createInjector(); - var keys = []; - var types = []; - var map = injector.get(MyMap); - map.forEach((k, t) { keys.add(k); types.add(t); }); - expect(keys).toEqual([new MyAnnotation('A'), new MyAnnotation('B')]); - expect(types).toEqual([A1, A1]); - }); - - it('should safely ignore typedefs', () { - var module = new Module() - ..bind(MyMap) - ..bind(MyTypedef, toValue: (String _) => null); - - var injector = applicationFactory().addModule(module).createInjector(); - expect(() => injector.get(MyMap), isNot(throws)); - }); - }); -} - -typedef void MyTypedef(String arg); - -class MyMap extends AnnotationMap { - MyMap(Injector injector, MetadataExtractor metadataExtractor) - : super(injector, metadataExtractor); -} - - -class MyAnnotation { - final String name; - - const MyAnnotation(String this.name); - - toString() => name; - get hashCode => name.hashCode; - operator==(other) => this.name == other.name; -} - -@MyAnnotation('A') @MyAnnotation('B') class A1 {} -@MyAnnotation('A') class A2 {} diff --git a/test/core/templateurl_spec.dart b/test/core/templateurl_spec.dart index 693127947..544281f24 100644 --- a/test/core/templateurl_spec.dart +++ b/test/core/templateurl_spec.dart @@ -64,7 +64,7 @@ void main() { var element = e('
ignore
'); zone.run(() { - compile([element], directives)(injector, [element]); + compile([element], directives)(rootScope, null, injector, [element]); }); backend.flush(); @@ -94,7 +94,7 @@ void main() { backend.expectGET('simple.html').respond(200, '
Simple!
'); var element = es('
ignore
'); - compile(element, directives)(injector, element); + compile(element, directives)(rootScope, null, injector, element); microLeap(); backend.flush(); @@ -116,7 +116,7 @@ void main() { 'ignore' 'ignore' '
'); - compile(element, directives)(injector, element); + compile(element, directives)(rootScope, null, injector, element); microLeap(); backend.flush(); @@ -137,7 +137,7 @@ void main() { ..expectGET('simple.html').respond(200, '
Simple!
'); var element = e('
ignore
'); - compile([element], directives)(injector, [element]); + compile([element], directives)(rootScope, null, injector, [element]); microLeap(); backend.flush(); @@ -157,7 +157,7 @@ void main() { MockHttpBackend backend, DirectiveMap directives) { var element = es('
ignore
'); backend.expectGET('simple.css').respond(200, '.hello{}'); - compile(element, directives)(injector, element); + compile(element, directives)(rootScope, null, injector, element); microLeap(); backend.flush(); @@ -170,7 +170,7 @@ void main() { MockHttpBackend backend, DirectiveMap directives) { var element = es('
ignore
'); backend.expectGET('simple.css').respond(500, 'some error'); - compile(element, directives)(injector, element); + compile(element, directives)(rootScope, null, injector, element); microLeap(); backend.flush(); @@ -187,7 +187,7 @@ void main() { MockHttpBackend backend, DirectiveMap directives) { var element = es('
ignore
'); backend.expectGET('simple.css').respond(200, '.hello{}'); - compile(element, directives)(injector, element); + compile(element, directives)(rootScope, null, injector, element); microLeap(); backend.flush(); @@ -203,7 +203,7 @@ void main() { ..expectGET('simple.html').respond(200, '
Simple!
'); var element = es('ignore'); - compile(element, directives)(injector, element); + compile(element, directives)(rootScope, null, injector, element); microLeap(); backend.flush(); @@ -228,7 +228,7 @@ void main() { ..expectGET('simple.html').respond(200, '
Simple!
'); var element = e('
ignore
'); - compile([element], directives)(injector, [element]); + compile([element], directives)(rootScope, null, injector, [element]); microLeap(); backend.flush(); @@ -252,14 +252,14 @@ void main() { }); it('should load css from the style cache for the second component', async(inject( - (Http http, Compiler compile, MockHttpBackend backend, + (Http http, Compiler compile, MockHttpBackend backend, RootScope rootScope, DirectiveMap directives, Injector injector) { backend ..expectGET('simple.css').respond(200, '.hello{}') ..expectGET('simple.html').respond(200, '
Simple!
'); var element = e('
ignore
'); - compile([element], directives)(injector, [element]); + compile([element], directives)(rootScope, null, injector, [element]); microLeap(); backend.flush(); @@ -270,7 +270,7 @@ void main() { ); var element2 = e('
ignore
'); - compile([element2], directives)(injector, [element2]); + compile([element2], directives)(rootScope, null, injector, [element2]); microLeap(); diff --git a/test/core_dom/compiler_spec.dart b/test/core_dom/compiler_spec.dart index 97c19d325..624974399 100644 --- a/test/core_dom/compiler_spec.dart +++ b/test/core_dom/compiler_spec.dart @@ -1,6 +1,7 @@ library compiler_spec; import '../_specs.dart'; +import 'package:angular/core_dom/directive_injector.dart'; forBothCompilers(fn) { @@ -374,7 +375,7 @@ void main() { var simpleElement = _.rootElement.querySelector('simple'); expect(simpleElement).toHaveText('INNER(innerText)'); var simpleProbe = ngProbe(simpleElement); - var simpleComponent = simpleProbe.injector.get(SimpleComponent); + var simpleComponent = simpleProbe.directiveInjector.getByKey(new Key(SimpleComponent)); expect(simpleComponent.scope.context['name']).toEqual('INNER'); var shadowRoot = simpleElement.shadowRoot; @@ -629,8 +630,8 @@ void main() { it('should expose PublishModuleDirectiveSuperType as PublishModuleDirectiveSuperType', () { _.compile(r'
'); - var probe = _.rootScope.context['publishModuleProbe']; - var directive = probe.injector.get(PublishModuleDirectiveSuperType); + Probe probe = _.rootScope.context['publishModuleProbe']; + var directive = probe.directiveInjector.get(PublishModuleDirectiveSuperType); expect(directive is PublishModuleAttrDirective).toBeTruthy(); }); @@ -658,7 +659,7 @@ void main() { scope.context['logger'] = logger; scope.context['once'] = null; var elts = es('{{logger("inner")}}'); - compile(elts, _.injector.get(DirectiveMap))(_.injector.createChild([new Module()..bind(Scope, toValue: scope)]), elts); + compile(elts, _.injector.get(DirectiveMap))(scope, null, _.injector, elts); expect(logger).toEqual(['new']); expect(logger).toEqual(['new']); @@ -690,7 +691,7 @@ void main() { backend.whenGET('foo.html').respond('
WORKED
'); var elts = es(''); var scope = _.rootScope.createChild({}); - compile(elts, _.injector.get(DirectiveMap))(_.injector.createChild([new Module()..bind(Scope, toValue: scope)]), elts); + compile(elts, _.injector.get(DirectiveMap))(scope, null, _.injector, elts); expect(logger).toEqual(['SimpleAttachComponent']); scope.destroy(); @@ -722,8 +723,9 @@ void main() { describe('invalid components', () { it('should throw a useful error message for missing selectors', () { Module module = new Module() + ..bind(DirectiveMap) ..bind(MissingSelector); - var injector = _.injector.createChild([module], forceNewInstances: [Compiler, DirectiveMap]); + var injector = _.injector.createChild([module]); var c = injector.get(Compiler); var directives = injector.get(DirectiveMap); expect(() { @@ -734,8 +736,9 @@ void main() { it('should throw a useful error message for invalid selector', () { Module module = new Module() + ..bind(DirectiveMap) ..bind(InvalidSelector); - var injector = _.injector.createChild([module], forceNewInstances: [Compiler, DirectiveMap]); + var injector = _.injector.createChild([module]); var c = injector.get(Compiler); var directives = injector.get(DirectiveMap); @@ -876,7 +879,24 @@ void main() { expect(log.result()).toEqual('Ignore; TabComponent-0; LocalAttrDirective-0; PaneComponent-1; LocalAttrDirective-0'); })); - it('should reuse controllers for transclusions', async((Logger log) { + /* + This test is dissabled becouse I (misko) thinks it has no real use case. It is easier + to understand in terms of ng-repeat + + + + + + + Should pane be allowed to get a hold of ng-repeat? Right now ng-repeat injector is + to the side and is not in any of the parents of the pane. Making an injector a + parent of pane would put the ng-repeat between tabs and pane and it would break + the DirectChild between tabs and pane. + + It is not clear to me (misko) that there is a use case for getting hold of the + tranrscluding directive such a ng-repeat. + */ + xit('should reuse controllers for transclusions', async((Logger log) { _.compile('
view
'); microLeap(); @@ -915,6 +935,7 @@ void main() { _.compile('
{{name}}
'); _.rootScope.apply(); expect(_.rootScope.context['name']).toEqual('cover me'); + expect(_.rootScope.context['myCtrl'] is MyController).toEqual(true); expect(_.rootElement.text).toEqual('MyController'); }); }); @@ -974,7 +995,8 @@ class LocalAttrDirective { @Decorator( selector: '[simple-transclude-in-attach]', - visibility: Directive.CHILDREN_VISIBILITY, children: Directive.TRANSCLUDE_CHILDREN) + visibility: Visibility.LOCAL, + children: Directive.TRANSCLUDE_CHILDREN) class SimpleTranscludeInAttachAttrDirective { SimpleTranscludeInAttachAttrDirective(ViewPort viewPort, BoundViewFactory boundViewFactory, Logger log, RootScope scope) { scope.runAsync(() { @@ -1023,12 +1045,10 @@ class PublishModuleDirectiveSuperType { selector: '[publish-types]', module: PublishModuleAttrDirective.module) class PublishModuleAttrDirective implements PublishModuleDirectiveSuperType { - static Module _module = new Module() - ..bind(PublishModuleDirectiveSuperType, toFactory: (i) => i.get(PublishModuleAttrDirective)); - static module() => _module; + static module(i) => i.bind(PublishModuleDirectiveSuperType, inject: PublishModuleAttrDirective); - static Injector _injector; - PublishModuleAttrDirective(Injector injector) { + static DirectiveInjector _injector; + PublishModuleAttrDirective(DirectiveInjector injector) { _injector = injector; } } diff --git a/test/core_dom/directive_injector_spec.dart b/test/core_dom/directive_injector_spec.dart new file mode 100644 index 000000000..59d0aadbb --- /dev/null +++ b/test/core_dom/directive_injector_spec.dart @@ -0,0 +1,122 @@ +library directive_injector_spec; + +import '../_specs.dart'; +import 'package:angular/core_dom/directive_injector.dart'; + +void main() { + describe('DirectiveInjector', () { + + var appInjector = new ModuleInjector([new Module()..bind(_Root)]); + var div = new DivElement(); + var span = new SpanElement(); + var eventHandler = new EventHandler(null, null, null); + + describe('base', () { + DirectiveInjector injector; + Scope scope; + Animate animate; + + addDirective(Type type, [Visibility visibility]) { + if (visibility == null) visibility = Visibility.LOCAL; + var reflector = Module.DEFAULT_REFLECTOR; + injector.bindByKey( + new Key(type), + reflector.factoryFor(type), + reflector.parameterKeysFor(type), + visibility); + } + + beforeEach((Scope _scope, Animate _animate) { + scope = _scope; + animate = _animate; + injector = new DirectiveInjector(null, appInjector, div, new NodeAttrs(div), eventHandler, scope, animate); + }); + + it('should return basic types', () { + expect(injector.parent is DefaultDirectiveInjector).toBe(true); + expect(injector.appInjector).toBe(appInjector); + expect(injector.scope).toBe(scope); + expect(injector.get(Injector)).toBe(appInjector); + expect(injector.get(DirectiveInjector)).toBe(injector); + expect(injector.get(Scope)).toBe(scope); + expect(injector.get(Node)).toBe(div); + expect(injector.get(Element)).toBe(div); + expect((injector.get(NodeAttrs) as NodeAttrs).element).toBe(div); + expect(injector.get(EventHandler)).toBe(eventHandler); + expect(injector.get(Animate)).toBe(animate); + expect((injector.get(ElementProbe) as ElementProbe).element).toBe(div); + }); + + it('should instantiate types', () { + addDirective(_Type9); + addDirective(_Type8); + addDirective(_Type7); + addDirective(_Type5); + addDirective(_Type6); + addDirective(_Type0); + addDirective(_Type1); + addDirective(_Type2); + addDirective(_Type3); + addDirective(_Type4); + expect(() => addDirective(_TypeA)) + .toThrow('Maximum number of directives per element reached.'); + var root = injector.get(_Root); + expect((injector.get(_Type9) as _Type9).type8.type7.type6.type5.type4.type3.type2.type1.type0.root) + .toBe(root); + expect(() => injector.get(_TypeA)).toThrow('No provider found for _TypeA'); + }); + + describe('Visibility', () { + DirectiveInjector childInjector; + DirectiveInjector leafInjector; + + beforeEach(() { + childInjector = new DirectiveInjector(injector, appInjector, span, null, null, null, null); + leafInjector = new DirectiveInjector(childInjector, appInjector, span, null, null, null, null); + }); + + it('should not allow reseting visibility', () { + addDirective(_Type0, Visibility.LOCAL); + expect(() => addDirective(_Type0, Visibility.DIRECT_CHILD)).toThrow( + 'Can not set Visibility: DIRECT_CHILD on _Type0, it alread has Visibility: LOCAL'); + }); + + it('should allow child injector to see types declared at parent injector', () { + addDirective(_Children, Visibility.CHILDREN); + _Children t = injector.get(_Children); + expect(childInjector.get(_Children)).toBe(t); + expect(leafInjector.get(_Children)).toBe(t); + }); + + it('should hide parent injector types when local visibility', () { + addDirective(_Local, Visibility.LOCAL); + _Local t = injector.getByKey(_LOCAL); + expect(() => childInjector.get(_LOCAL)).toThrow(); + expect(() => leafInjector.get(_LOCAL)).toThrow(); + }); + }); + }); + }); +} + +var _CHILDREN = new Key(_Local); +var _LOCAL = new Key(_Local); +var _TYPE0 = new Key(_Local); + +class _Children{} +class _Local{} +class _Direct{} +class _Any{} +class _Root{ } +class _Type0{ final _Root root; _Type0(this.root); } +class _Type1{ final _Type0 type0; _Type1(this.type0); } +class _Type2{ final _Type1 type1; _Type2(this.type1); } +class _Type3{ final _Type2 type2; _Type3(this.type2); } +class _Type4{ final _Type3 type3; _Type4(this.type3); } +class _Type5{ final _Type4 type4; _Type5(this.type4); } +class _Type6{ final _Type5 type5; _Type6(this.type5); } +class _Type7{ final _Type6 type6; _Type7(this.type6); } +class _Type8{ final _Type7 type7; _Type8(this.type7); } +class _Type9{ final _Type8 type8; _Type9(this.type8); } +class _TypeA{ final _Type9 type9; _TypeA(this.type9); } + diff --git a/test/core_dom/event_handler_spec.dart b/test/core_dom/event_handler_spec.dart index 43b3b9149..d4e077681 100644 --- a/test/core_dom/event_handler_spec.dart +++ b/test/core_dom/event_handler_spec.dart @@ -19,7 +19,7 @@ class FooController { class BarComponent { var invoked = false; BarComponent(RootScope scope) { - scope.context['ctrl'] = this; + scope.context['barComponent'] = this; } } @@ -85,7 +85,7 @@ main() { var shadowRoot = e.shadowRoot; var span = shadowRoot.querySelector('span'); span.dispatchEvent(new CustomEvent('abc')); - var ctrl = _.rootScope.context['ctrl']; + BarComponent ctrl = _.rootScope.context['barComponent']; expect(ctrl.invoked).toEqual(true); })); @@ -102,7 +102,8 @@ main() { document.querySelector('[on-abc]').dispatchEvent(new Event('abc')); var shadowRoot = document.querySelector('bar').shadowRoot; var shadowRootScope = _.getScope(shadowRoot); - expect(shadowRootScope.context['ctrl'].invoked).toEqual(false); + BarComponent ctrl = shadowRootScope.context['ctrl']; + expect(ctrl.invoked).toEqual(false); var fooScope = _.getScope(document.querySelector('[foo]')); expect(fooScope.context['ctrl'].invoked).toEqual(true); diff --git a/test/core_dom/http_spec.dart b/test/core_dom/http_spec.dart index e4abe56de..237207c3c 100644 --- a/test/core_dom/http_spec.dart +++ b/test/core_dom/http_spec.dart @@ -10,7 +10,8 @@ var CACHED_VALUE = 'cached_value'; class FakeCache extends UnboundedCache { get(x) => x == 'f' ? new HttpResponse(200, CACHED_VALUE) : null; put(_,__) => null; - + void clear() {} + int get length => 0; } class SubstringRewriter extends UrlRewriter { diff --git a/test/core_dom/mustache_spec.dart b/test/core_dom/mustache_spec.dart index ba47f09b6..4b091e58f 100644 --- a/test/core_dom/mustache_spec.dart +++ b/test/core_dom/mustache_spec.dart @@ -18,7 +18,7 @@ main() { var template = compile(element, directives); rootScope.context['name'] = 'OK'; - var view = template(injector); + var view = template(rootScope, null, injector); element = view.nodes; @@ -27,7 +27,7 @@ main() { })); describe('observe/flush phase', () { - it('should first only when then value has settled', async((Logger log) { + it('should fire only when then value has settled', async((Logger log) { _.compile('
'); _.rootScope.apply(); @@ -53,7 +53,7 @@ main() { rootScope.context['name'] = 'OK'; rootScope.context['age'] = 23; - var view = template(injector); + var view = template(rootScope, null, injector); element = view.nodes[0]; @@ -72,7 +72,7 @@ main() { rootScope.context['line1'] = 'L1'; rootScope.context['line2'] = 'L2'; - var view = template(injector); + var view = template(rootScope, null, injector); element = view.nodes[0]; @@ -87,7 +87,7 @@ main() { { var element = es('
{{"World" | hello}}
'); var template = compile(element, directives); - var view = template(injector); + var view = template(rootScope, null, injector); rootScope.apply(); element = view.nodes; diff --git a/test/core_dom/selector_spec.dart b/test/core_dom/selector_spec.dart index 35c8785a9..524359a4c 100644 --- a/test/core_dom/selector_spec.dart +++ b/test/core_dom/selector_spec.dart @@ -2,34 +2,47 @@ library angular.dom.selector_spec; import '../_specs.dart'; -@Decorator(selector:'b') class _BElement{} -@Decorator(selector:'.b') class _BClass{} -@Decorator(selector:'[directive]') class _DirectiveAttr{} -@Decorator(selector:'[wildcard-*]') class _WildcardDirectiveAttr{} -@Decorator(selector:'[directive=d][foo=f]') class _DirectiveFooAttr{} -@Decorator(selector:'b[directive]') class _BElementDirectiveAttr{} -@Decorator(selector:'[directive=value]') class _DirectiveValueAttr{} -@Decorator(selector:'b[directive=value]') class _BElementDirectiveValue{} -@Decorator(selector:':contains(/abc/)') class _ContainsAbc{} -@Decorator(selector:'[*=/xyz/]') class _AttributeContainsXyz{} - -@Component(selector:'component') class _Component{} -@Decorator(selector:'[attribute]') class _Attribute{} -@Decorator(selector:'[structural]', - children: Directive.TRANSCLUDE_CHILDREN) - class _Structural{} - -@Decorator(selector:'[ignore-children]', - children: Directive.IGNORE_CHILDREN) - class _IgnoreChildren{} - -@Decorator(selector: '[my-model][required]') -@Decorator(selector: '[my-model][my-required]') - class _TwoDirectives {} - -@Decorator(selector: '[two-directives]') class _OneOfTwoDirectives {} -@Decorator(selector: '[two-directives]') class _TwoOfTwoDirectives {} - +const _aBElement = const Decorator(selector:'b'); +const _aBClass = const Decorator(selector:'.b'); +const _aDirectiveAttr = const Decorator(selector:'[directive]'); +const _aWildcardDirectiveAttr = const Decorator(selector:'[wildcard-*]'); +const _aDirectiveFooAttr = const Decorator(selector:'[directive=d][foo=f]'); +const _aBElementDirectiveAttr = const Decorator(selector:'b[directive]'); +const _aDirectiveValueAttr = const Decorator(selector:'[directive=value]'); +const _aBElementDirectiveValue = const Decorator(selector:'b[directive=value]'); +const _aContainsAbc = const Decorator(selector:':contains(/abc/)'); +const _aAttributeContainsXyz = const Decorator(selector:'[*=/xyz/]'); +const _aAttribute = const Decorator(selector:'[attribute]'); +const _aCComponent = const Component(selector:'component'); +const _aStructural = const Decorator(selector:'[structural]', + children: Directive.TRANSCLUDE_CHILDREN); +const _aIgnoreChildren = const Decorator(selector:'[ignore-children]', + children: Directive.IGNORE_CHILDREN); +const _aTwoDirectives0 = const Decorator(selector: '[my-model][required]'); +const _aTwoDirectives1 = const Decorator(selector: '[my-model][my-required]'); +const _aOneOfTwoDirectives = const Decorator(selector: '[two-directives]'); +const _aTwoOfTwoDirectives = const Decorator(selector: '[two-directives]'); + + +@_aBElement class _BElement{} +@_aBClass class _BClass{} +@_aDirectiveAttr class _DirectiveAttr{} +@_aWildcardDirectiveAttr class _WildcardDirectiveAttr{} +@_aDirectiveFooAttr class _DirectiveFooAttr{} +@_aBElementDirectiveAttr class _BElementDirectiveAttr{} +@_aDirectiveValueAttr class _DirectiveValueAttr{} +@_aBElementDirectiveValue class _BElementDirectiveValue{} +@_aContainsAbc class _ContainsAbc{} +@_aAttributeContainsXyz class _AttributeContainsXyz{} +@_aCComponent class _CComponent{} +@_aAttribute class _Attribute{} +@_aStructural class _Structural{} +@_aIgnoreChildren class _IgnoreChildren{} +@_aOneOfTwoDirectives class _OneOfTwoDirectives {} +@_aTwoOfTwoDirectives class _TwoOfTwoDirectives {} + +@_aTwoDirectives0 +@_aTwoDirectives1 class _TwoDirectives {} main() { describe('Selector', () { @@ -51,7 +64,7 @@ main() { ..bind(_BElementDirectiveValue) ..bind(_ContainsAbc) ..bind(_AttributeContainsXyz) - ..bind(_Component) + ..bind(_CComponent) ..bind(_Attribute) ..bind(_Structural) ..bind(_IgnoreChildren) @@ -69,14 +82,14 @@ main() { expect( selector(element = e('')), toEqualsDirectiveInfos([ - { "selector": 'b', "value": null, "element": element} + { "selector": 'b', "value": null, "element": element, "annotation": _aBElement} ])); }); it('should match directive on class', () { expect(selector(element = e('
')), toEqualsDirectiveInfos([ - { "selector": '.b', "value": null, "element": element} + { "selector": '.b', "value": null, "element": element, "annotation": _aBClass} ])); }); @@ -84,39 +97,39 @@ main() { expect(selector(element = e('
')), toEqualsDirectiveInfos([ { "selector": '[directive]', "value": 'abc', "element": element, - "name": 'directive' }])); + "name": 'directive', "annotation": _aDirectiveAttr }])); expect(selector(element = e('
')), toEqualsDirectiveInfos([ { "selector": '[directive]', "value": '', "element": element, - "name": 'directive' }])); + "name": 'directive', "annotation": _aDirectiveAttr }])); }); it('should match directive on element[attribute]', () { expect(selector(element = e('')), toEqualsDirectiveInfos([ - { "selector": 'b', "value": null, "element": element}, - { "selector": '[directive]', "value": 'abc', "element": element}, - { "selector": 'b[directive]', "value": 'abc', "element": element} + { "selector": 'b', "value": null, "element": element, "annotation": _aBElement}, + { "selector": '[directive]', "value": 'abc', "element": element, "annotation": _aDirectiveAttr}, + { "selector": 'b[directive]', "value": 'abc', "element": element, "annotation": _aBElementDirectiveAttr} ])); }); it('should match directive on [attribute=value]', () { expect(selector(element = e('
')), toEqualsDirectiveInfos([ - { "selector": '[directive]', "value": 'value', "element": element}, - { "selector": '[directive=value]', "value": 'value', "element": element} + { "selector": '[directive]', "value": 'value', "element": element, "annotation": _aDirectiveAttr}, + { "selector": '[directive=value]', "value": 'value', "element": element, "annotation": _aDirectiveValueAttr} ])); }); it('should match directive on element[attribute=value]', () { expect(selector(element = e('
')), toEqualsDirectiveInfos([ - { "selector": 'b', "value": null, "element": element, "name": null}, - { "selector": '[directive]', "value": 'value', "element": element}, - { "selector": '[directive=value]', "value": 'value', "element": element}, - { "selector": 'b[directive]', "value": 'value', "element": element}, - { "selector": 'b[directive=value]', "value": 'value', "element": element} + { "selector": 'b', "value": null, "element": element, "name": null, "annotation": _aBElement}, + { "selector": '[directive]', "value": 'value', "element": element, "annotation": _aDirectiveAttr}, + { "selector": '[directive=value]', "value": 'value', "element": element, "annotation": _aDirectiveValueAttr}, + { "selector": 'b[directive]', "value": 'value', "element": element, "annotation": _aBElementDirectiveAttr}, + { "selector": 'b[directive=value]', "value": 'value', "element": element, "annotation": _aBElementDirectiveValue} ])); }); @@ -126,7 +139,9 @@ main() { { "selector": '[*=/xyz/]', "value": 'attr', "ast": '"before-xyz-after"', - "element": element, "name": 'attr'} + "element": element, + "name": 'attr', + "annotation": _aAttributeContainsXyz} ])); }); @@ -134,7 +149,7 @@ main() { expect(selector(element = e('
')), toEqualsDirectiveInfos([ { "selector": '[wildcard-*]', "value": 'ignored', - "element": element, "name": 'wildcard-match'} + "element": element, "name": 'wildcard-match', "annotation": _aWildcardDirectiveAttr} ])); }); @@ -144,23 +159,23 @@ main() { expect(eb, toEqualsDirectiveInfos( null, - template: {"selector": "[structural]", "value": "", "element": element})); + template: {"selector": "[structural]", "value": "", "element": element, "annotation": _aStructural})); expect(eb.templateBinder, toEqualsDirectiveInfos( [ - { "selector": "[attribute]", "value": "", "element": element }, - { "selector": "[ignore-children]", "value": "", "element": element } + { "selector": "[attribute]", "value": "", "element": element, "annotation": _aAttribute }, + { "selector": "[ignore-children]", "value": "", "element": element, "annotation": _aIgnoreChildren } ], - component: { "selector": "component", "value": null, "element": element })); + component: { "selector": "component", "value": null, "element": element, "annotation": _aCComponent })); })); it('should match on multiple directives', () { expect(selector(element = e('
')), toEqualsDirectiveInfos([ - { "selector": '[directive]', "value": 'd', "element": element}, - { "selector": '[directive=d][foo=f]', "value": 'f', "element": element} + { "selector": '[directive]', "value": 'd', "element": element, "annotation": _aDirectiveAttr}, + { "selector": '[directive=d][foo=f]', "value": 'f', "element": element, "annotation": _aDirectiveFooAttr} ])); }); @@ -187,8 +202,8 @@ main() { it('should match an two directives with the same selector', () { expect(selector(element = e('
')), toEqualsDirectiveInfos([ - { "selector": '[two-directives]', "value": '', "element": element}, - { "selector": '[two-directives]', "value": '', "element": element} + { "selector": '[two-directives]', "value": '', "element": element, "annotation": _aOneOfTwoDirectives}, + { "selector": '[two-directives]', "value": '', "element": element, "annotation": _aTwoOfTwoDirectives} ])); }); @@ -252,10 +267,11 @@ class DirectiveInfosMatcher extends Matcher { Description describe(Description description) => description..add(expected.toString()); - bool _refMatches(directiveRef, expectedMap) => + bool _refMatches(DirectiveRef directiveRef, Map expectedMap) => directiveRef.element == expectedMap['element'] && directiveRef.annotation.selector == expectedMap['selector'] && directiveRef.value == expectedMap['value'] && + (expectedMap['annotation'] == null || directiveRef.annotation == expectedMap['annotation']) && (directiveRef.valueAST == null || directiveRef.valueAST.expression == expectedMap['ast']); diff --git a/test/core_dom/view_spec.dart b/test/core_dom/view_spec.dart index 7fae0a95e..0b3386555 100644 --- a/test/core_dom/view_spec.dart +++ b/test/core_dom/view_spec.dart @@ -63,7 +63,7 @@ class BFormatter { main() { var viewFactoryFactory = (a,b,c,d) => new WalkingViewFactory(a,b,c,d); describe('View', () { - var anchor; + ViewPort viewPort; Element rootElement; var viewCache; @@ -77,32 +77,36 @@ main() { beforeEach((Injector injector, Profiler perf) { rootElement.innerHtml = ''; - anchor = new ViewPort(rootElement.childNodes[0], + var scope = injector.get(Scope); + viewPort = new ViewPort(null, injector, scope, rootElement.childNodes[0], injector.get(Animate)); - a = (viewFactoryFactory(es('Aa'), [], perf, expando))(injector); - b = (viewFactoryFactory(es('Bb'), [], perf, expando))(injector); + a = (viewFactoryFactory(es('Aa'), [], perf, expando))(scope, null, injector); + b = (viewFactoryFactory(es('Bb'), [], perf, expando))(scope, null, injector); }); describe('insertAfter', () { - it('should insert block after anchor view', () { - anchor.insert(a); + it('should insert block after anchor view', (RootScope scope) { + viewPort.insert(a); + scope.flush(); expect(rootElement).toHaveHtml('Aa'); }); - it('should insert multi element view after another multi element view', () { - anchor.insert(a); - anchor.insert(b, insertAfter: a); + it('should insert multi element view after another multi element view', (RootScope scope) { + viewPort.insert(a); + viewPort.insert(b, insertAfter: a); + scope.flush(); expect(rootElement).toHaveHtml('AaBb'); }); - it('should insert multi element view before another multi element view', () { - anchor.insert(b); - anchor.insert(a); + it('should insert multi element view before another multi element view', (RootScope scope) { + viewPort.insert(b); + viewPort.insert(a); + scope.flush(); expect(rootElement).toHaveHtml('AaBb'); }); @@ -110,20 +114,23 @@ main() { describe('remove', () { - beforeEach(() { - anchor.insert(a); - anchor.insert(b, insertAfter: a); + beforeEach((RootScope scope) { + viewPort.insert(a); + viewPort.insert(b, insertAfter: a); + scope.flush(); expect(rootElement.text).toEqual('AaBb'); }); - it('should remove the last view', () { - anchor.remove(b); + it('should remove the last view', (RootScope scope) { + viewPort.remove(b); + scope.flush(); expect(rootElement).toHaveHtml('Aa'); }); - it('should remove child views from parent pseudo black', () { - anchor.remove(a); + it('should remove child views from parent pseudo black', (RootScope scope) { + viewPort.remove(a); + scope.flush(); expect(rootElement).toHaveHtml('Bb'); }); @@ -176,16 +183,18 @@ main() { describe('moveAfter', () { - beforeEach(() { - anchor.insert(a); - anchor.insert(b, insertAfter: a); + beforeEach((RootScope scope) { + viewPort.insert(a); + viewPort.insert(b, insertAfter: a); + scope.flush(); expect(rootElement.text).toEqual('AaBb'); }); - it('should move last to middle', () { - anchor.move(a, moveAfter: b); + it('should move last to middle', (RootScope scope) { + viewPort.move(a, moveAfter: b); + scope.flush(); expect(rootElement).toHaveHtml('BbAa'); }); }); @@ -193,13 +202,13 @@ main() { describe('deferred', () { - it('should load directives/formatters from the child injector', () { + it('should load directives/formatters from the child injector', (RootScope scope) { Module rootModule = new Module() ..bind(Probe) ..bind(Log) ..bind(AFormatter) ..bind(ADirective) - ..bind(Node, toFactory: (injector) => document.body); + ..bind(Node, toFactory: () => document.body); Injector rootInjector = applicationFactory() .addModule(rootModule) @@ -209,7 +218,7 @@ main() { Compiler compiler = rootInjector.get(Compiler); DirectiveMap directives = rootInjector.get(DirectiveMap); - compiler(es('{{\'a\' | formatterA}}'), directives)(rootInjector); + compiler(es('{{\'a\' | formatterA}}'), directives)(rootScope, null, rootInjector); rootScope.apply(); expect(log.log, equals(['AFormatter', 'ADirective'])); @@ -222,8 +231,9 @@ main() { var childInjector = forceNewDirectivesAndFormatters(rootInjector, [childModule]); DirectiveMap newDirectives = childInjector.get(DirectiveMap); + var scope = childInjector.get(Scope); compiler(es('{{\'a\' | formatterA}}' - '{{\'b\' | formatterB}}'), newDirectives)(childInjector); + '{{\'b\' | formatterB}}'), newDirectives)(scope, null, childInjector); rootScope.apply(); expect(log.log, equals(['AFormatter', 'ADirective', 'BFormatter', 'ADirective', 'BDirective'])); diff --git a/test/directive/ng_bind_html_spec.dart b/test/directive/ng_bind_html_spec.dart index e1a0ad665..e1a8c2220 100644 --- a/test/directive/ng_bind_html_spec.dart +++ b/test/directive/ng_bind_html_spec.dart @@ -9,7 +9,7 @@ main() { it('should sanitize and set innerHtml and sanitize and set html', (Scope scope, Injector injector, Compiler compiler, DirectiveMap directives) { var element = es('
'); - compiler(element, directives)(injector, element); + compiler(element, directives)(scope, null, injector, element); scope.context['htmlVar'] = 'Google!'; scope.apply(); // Sanitization removes the href attribute on the tag. @@ -18,7 +18,7 @@ main() { describe('injected NodeValidator', () { beforeEachModule((Module module) { - module.bind(dom.NodeValidator, toFactory: (_) { + module.bind(dom.NodeValidator, toFactory: () { final validator = new NodeValidatorBuilder(); validator.allowNavigation(new AnyUriPolicy()); validator.allowTextElements(); @@ -28,7 +28,7 @@ main() { it('should use injected NodeValidator and override default sanitize behavior', (Scope scope, Injector injector, Compiler compiler, DirectiveMap directives) { var element = es('
'); - compiler(element, directives)(injector, element); + compiler(element, directives)(scope, null, injector, element); scope.context['htmlVar'] = '
Google!'; scope.apply(); // Sanitation allows href attributes per injected sanitizer. diff --git a/test/directive/ng_bind_spec.dart b/test/directive/ng_bind_spec.dart index af65f7f9d..e0d3351d5 100644 --- a/test/directive/ng_bind_spec.dart +++ b/test/directive/ng_bind_spec.dart @@ -10,7 +10,7 @@ main() { it('should set.text', (Scope scope, Injector injector, Compiler compiler, DirectiveMap directives) { var element = e('
'); - compiler([element], directives)(injector, [element]); + compiler([element], directives)(scope, null, injector, [element]); scope.context['a'] = "abc123"; scope.apply(); expect(element.text).toEqual('abc123'); diff --git a/test/directive/ng_form_spec.dart b/test/directive/ng_form_spec.dart index 1821f217a..e4f181a3c 100644 --- a/test/directive/ng_form_spec.dart +++ b/test/directive/ng_form_spec.dart @@ -147,7 +147,7 @@ void main() { _.compile('
'); scope.apply(); - expect(scope.context['formProbe'].injector.get(NgControl) is NgForm).toBeTruthy(); + expect(scope.context['formProbe'].directiveInjector.get(NgControl) is NgForm).toBeTruthy(); }); it('should add and remove the correct flags when set to valid and to invalid', @@ -722,9 +722,9 @@ void main() { }); it('should be resolvable by injector if configured by user.', - (Injector injector, Compiler compiler, DirectiveMap directives) { + (Scope scope, Injector injector, Compiler compiler, DirectiveMap directives) { var element = es('
'); - expect(() => compiler(element, directives)(injector, element)) + expect(() => compiler(element, directives)(scope, null, injector, element)) .not.toThrow(); }); }); diff --git a/test/directive/ng_if_spec.dart b/test/directive/ng_if_spec.dart index 0f84d59d8..35364d19a 100644 --- a/test/directive/ng_if_spec.dart +++ b/test/directive/ng_if_spec.dart @@ -28,7 +28,7 @@ main() { logger = _logger; compile = (html, [applyFn]) { element = e(html); - compiler([element], _directives)(injector, [element]); + compiler([element], _directives)(scope, null, injector, [element]); scope.apply(applyFn); }; directives = _directives; diff --git a/test/directive/ng_model_spec.dart b/test/directive/ng_model_spec.dart index 20b87665b..6f60de6a5 100644 --- a/test/directive/ng_model_spec.dart +++ b/test/directive/ng_model_spec.dart @@ -36,6 +36,7 @@ bool isBrowser(String pattern) => dom.window.navigator.userAgent.indexOf(pattern void main() { describe('ng-model', () { TestBed _; + DirectiveInjector dirInjector; beforeEachModule((Module module) { module @@ -44,7 +45,10 @@ void main() { ..bind(CountingValidator); }); - beforeEach((TestBed tb) => _ = tb); + beforeEach((TestBed tb) { + _ = tb; + dirInjector = new DirectiveInjector(null, _.injector, null, null, null, null, null); + }); describe('type="text" like', () { it('should update input value from model', () { @@ -94,7 +98,7 @@ void main() { var ngModelOptions = new NgModelOptions(); nodeAttrs['ng-model'] = 'model'; - var model = new NgModel(scope, ngElement, i.createChild([new Module()]), + var model = new NgModel(scope, ngElement, dirInjector, nodeAttrs, new Animate()); dom.querySelector('body').append(element); var input = new InputTextLike(element, model, scope, ngModelOptions); @@ -374,7 +378,7 @@ void main() { var ngModelOptions = new NgModelOptions(); nodeAttrs['ng-model'] = 'model'; - var model = new NgModel(scope, ngElement, i.createChild([new Module()]), + var model = new NgModel(scope, ngElement, dirInjector, nodeAttrs, new Animate()); dom.querySelector('body').append(element); var input = new InputTextLike(element, model, scope, ngModelOptions); @@ -466,7 +470,7 @@ void main() { var ngModelOptions = new NgModelOptions(); nodeAttrs['ng-model'] = 'model'; - var model = new NgModel(scope, ngElement, i.createChild([new Module()]), + var model = new NgModel(scope, ngElement, dirInjector, nodeAttrs, new Animate()); dom.querySelector('body').append(element); var input = new InputTextLike(element, model, scope, ngModelOptions); @@ -566,7 +570,7 @@ void main() { var ngModelOptions = new NgModelOptions(); nodeAttrs['ng-model'] = 'model'; - var model = new NgModel(scope, ngElement, i.createChild([new Module()]), + var model = new NgModel(scope, ngElement, dirInjector, nodeAttrs, new Animate()); dom.querySelector('body').append(element); var input = new InputTextLike(element, model, scope, ngModelOptions); @@ -777,7 +781,7 @@ void main() { var ngModelOptions = new NgModelOptions(); nodeAttrs['ng-model'] = 'model'; - var model = new NgModel(scope, ngElement, i.createChild([new Module()]), + var model = new NgModel(scope, ngElement, dirInjector, nodeAttrs, new Animate()); dom.querySelector('body').append(element); var input = new InputTextLike(element, model, scope, ngModelOptions); @@ -1116,7 +1120,7 @@ void main() { expect(_.rootScope.context['model']).toEqual('abc'); element.innerHtml = 'def'; - var input = ngInjector(element).get(ContentEditable); + var input = directiveInjector(element).get(ContentEditable); input.processValue(); expect(_.rootScope.context['model']).toEqual('def'); }); diff --git a/test/directive/ng_non_bindable_spec.dart b/test/directive/ng_non_bindable_spec.dart index a2ec3fb3d..6c1283991 100644 --- a/test/directive/ng_non_bindable_spec.dart +++ b/test/directive/ng_non_bindable_spec.dart @@ -19,7 +19,7 @@ main() { ' {{a}}' + ' ' + '
'); - compiler([element], directives)(injector, [element]); + compiler([element], directives)(scope, null, injector, [element]); scope.context['a'] = "one"; scope.context['b'] = "two"; scope.apply(); diff --git a/test/directive/ng_repeat_spec.dart b/test/directive/ng_repeat_spec.dart index 4b35f9528..d42327dcf 100644 --- a/test/directive/ng_repeat_spec.dart +++ b/test/directive/ng_repeat_spec.dart @@ -20,12 +20,12 @@ main() { scope = rootScope; compile = (html, [scope]) { element = e(html); - var viewFactory = compiler([element], _directives); - var blockInjector = injector; + ViewFactory viewFactory = compiler([element], _directives); + Injector blockInjector = injector; if (scope != null) { - viewFactory.bind(injector)(scope); + viewFactory.bind(null, injector)(scope); } else { - viewFactory(injector, [element]); + viewFactory(rootScope, null, injector, [element]); } return element; }; @@ -35,7 +35,7 @@ main() { it(r'should set create a list of items', (Scope scope, Compiler compiler, Injector injector) { var element = es('
{{item}}
'); ViewFactory viewFactory = compiler(element, directives); - View view = viewFactory(injector, element); + View view = viewFactory(scope, null, injector, element); scope.context['items'] = ['a', 'b']; scope.apply(); expect(element).toHaveText('ab'); @@ -50,7 +50,7 @@ main() { }); var element = es('
{{item}}
'); ViewFactory viewFactory = compiler(element, directives); - View view = viewFactory(injector, element); + View view = viewFactory(scope, null, injector, element); scope.apply(); expect(element).toHaveText('ab'); }); @@ -60,7 +60,7 @@ main() { (Scope scope, Compiler compiler, Injector injector) { var element = es('
{{item}}
'); ViewFactory viewFactory = compiler(element, directives); - View view = viewFactory(injector, element); + View view = viewFactory(scope, null, injector, element); scope.context['items'] = ['a', 'b'].map((i) => i); // makes an iterable scope.apply(); expect(element).toHaveText('ab'); @@ -531,23 +531,20 @@ main() { }); it(r'should not move blocks when elements only added or removed', - inject((Injector injector) { + inject((Injector injector, Scope rootScope, Compiler compiler, + DirectiveMap _directives, ExceptionHandler exceptionHandler) { var throwOnMove = new MockAnimate(); var child = injector.createChild( [new Module()..bind(Animate, toValue: throwOnMove)]); - child.invoke((Injector injector, Scope rootScope, Compiler compiler, - DirectiveMap _directives) { - exceptionHandler = injector.get(ExceptionHandler); - scope = rootScope; - compile = (html) { - element = e(html); - var viewFactory = compiler([element], _directives); - viewFactory(injector, [element]); - return element; - }; - directives = _directives; - }); + scope = rootScope; + compile = (html) { + element = e(html); + var viewFactory = compiler([element], _directives); + viewFactory(scope, null, child, [element]); + return element; + }; + directives = _directives; element = compile( '
    ' diff --git a/test/formatter/currency_spec.dart b/test/formatter/currency_spec.dart index a488c107f..f747959fa 100644 --- a/test/formatter/currency_spec.dart +++ b/test/formatter/currency_spec.dart @@ -8,7 +8,7 @@ void main() { var currency; beforeEach((FormatterMap map, Injector injector) { - currency = injector.get(map[new Formatter(name: 'currency')]); + currency = injector.get(map['currency']); }); diff --git a/test/formatter/date_spec.dart b/test/formatter/date_spec.dart index 124dcb2e4..f207b1e58 100644 --- a/test/formatter/date_spec.dart +++ b/test/formatter/date_spec.dart @@ -13,7 +13,7 @@ void main() { var date; beforeEach((FormatterMap map, Injector injector) { - date = injector.get(map[new Formatter(name: 'date')]); + date = injector.get(map['date']); }); it('should ignore falsy inputs', () { diff --git a/test/formatter/filter_spec.dart b/test/formatter/filter_spec.dart index 0c614912a..dfc168979 100644 --- a/test/formatter/filter_spec.dart +++ b/test/formatter/filter_spec.dart @@ -52,7 +52,7 @@ main() { var filter; beforeEach((Injector injector, FormatterMap filterMap) { - filter = injector.get(filterMap[new Formatter(name: 'filter')]); + filter = injector.get(filterMap['filter']); }); it('should formatter by string', () { diff --git a/test/formatter/number_spec.dart b/test/formatter/number_spec.dart index cf4889bdd..782418724 100644 --- a/test/formatter/number_spec.dart +++ b/test/formatter/number_spec.dart @@ -8,7 +8,7 @@ void main() { var number; beforeEach((FormatterMap map, Injector injector) { - number = injector.get(map[new Formatter(name: 'number')]); + number = injector.get(map['number']); }); diff --git a/test/introspection_spec.dart b/test/introspection_spec.dart index d8ff35c74..b70d0223e 100644 --- a/test/introspection_spec.dart +++ b/test/introspection_spec.dart @@ -10,8 +10,8 @@ void main() { it('should retrieve ElementProbe', (TestBed _) { _.compile('
    '); ElementProbe probe = ngProbe(_.rootElement); - expect(probe.injector.parent).toBe(_.injector); - expect(ngInjector(_.rootElement).parent).toBe(_.injector); + expect(probe.directiveInjector.appInjector).toBe(_.injector); + expect(ngInjector(_.rootElement)).toBe(_.injector); expect(probe.directives[0] is NgBind).toBe(true); expect(ngDirectives(_.rootElement)[0] is NgBind).toBe(true); expect(probe.scope).toBe(_.rootScope); @@ -42,7 +42,7 @@ void main() { ElementProbe probe = ngProbe('[ng-show]'); expect(probe).toBeDefined(); - expect(probe.injector.get(NgShow) is NgShow).toEqual(true); + expect(probe.directiveInjector.get(NgShow) is NgShow).toEqual(true); _.rootElement.remove(); }); diff --git a/test/io/expression_extractor_spec.dart b/test/io/expression_extractor_spec.dart index f9ce6a0c4..d8b09da74 100644 --- a/test/io/expression_extractor_spec.dart +++ b/test/io/expression_extractor_spec.dart @@ -1,7 +1,6 @@ library ng.tool.expression_extractor_spec; import 'package:di/di.dart'; -import 'package:di/dynamic_injector.dart'; import 'package:angular/tools/common.dart'; import 'package:angular/tools/io.dart'; import 'package:angular/tools/io_impl.dart'; @@ -17,8 +16,7 @@ void main() { Iterable _extractExpressions(file) { Module module = new Module(); - Injector injector = new DynamicInjector(modules: [module], - allowImplicitInjection: true); + Injector injector = new ModuleInjector([module]); IoService ioService = new IoServiceImpl(); var sourceCrawler = new SourceCrawlerImpl(['packages/']); diff --git a/test/routing/ng_bind_route_spec.dart b/test/routing/ng_bind_route_spec.dart index a3351b863..4de361806 100644 --- a/test/routing/ng_bind_route_spec.dart +++ b/test/routing/ng_bind_route_spec.dart @@ -20,14 +20,14 @@ main() { it('should inject null RouteProvider when no ng-bind-route', async(() { Element root = _.compile('
    '); - expect(_.rootScope.context['routeProbe'].injector.get(RouteProvider)).toBeNull(); + expect(_.rootScope.context['routeProbe'].directiveInjector.get(RouteProvider)).toBeNull(); })); it('should inject RouteProvider with correct flat route', async(() { Element root = _.compile( '
    '); - expect(_.rootScope.context['routeProbe'].injector.get(RouteProvider).routeName) + expect(_.rootScope.context['routeProbe'].directiveInjector.get(RouteProvider).routeName) .toEqual('library'); })); @@ -39,14 +39,14 @@ main() { '
    ' '
' '
'); - expect(_.rootScope.context['routeProbe'].injector.get(RouteProvider).route.name) + expect(_.rootScope.context['routeProbe'].directiveInjector.get(RouteProvider).route.name) .toEqual('all'); })); it('should expose NgBindRoute as RouteProvider', async(() { Element root = _.compile( '
'); - expect(_.rootScope.context['routeProbe'].injector.get(RouteProvider) is NgBindRoute).toBeTruthy(); + expect(_.rootScope.context['routeProbe'].directiveInjector.get(RouteProvider) is NgBindRoute).toBeTruthy(); })); }); diff --git a/test/routing/ng_view_spec.dart b/test/routing/ng_view_spec.dart index f026b2c41..5a5629a74 100644 --- a/test/routing/ng_view_spec.dart +++ b/test/routing/ng_view_spec.dart @@ -49,8 +49,9 @@ main() { router.route('/foo'); microLeap(); _.rootScope.apply(); + Probe probe = _.rootScope.context['p']; - expect(_.rootScope.context['p'].injector.get(RouteProvider) is NgView).toBeTruthy(); + expect(probe.directiveInjector.get(RouteProvider) is NgView).toBeTruthy(); })); diff --git a/test/routing/routing_spec.dart b/test/routing/routing_spec.dart index bbc63cd31..993a1293b 100644 --- a/test/routing/routing_spec.dart +++ b/test/routing/routing_spec.dart @@ -16,7 +16,7 @@ main() { router = new Router(useFragment: false, windowImpl: new MockWindow()); m ..install(new AngularMockModule()) - ..bind(RouteInitializerFn, toFactory: (_) => initRoutes) + ..bind(RouteInitializerFn, toFactory: () => initRoutes) ..bind(Router, toValue: router); }); diff --git a/test/tools/transformer/static_angular_generator_spec.dart b/test/tools/transformer/static_angular_generator_spec.dart index 8346c6a34..506467c75 100644 --- a/test/tools/transformer/static_angular_generator_spec.dart +++ b/test/tools/transformer/static_angular_generator_spec.dart @@ -43,12 +43,11 @@ import 'package:angular/application_factory_static.dart'; import 'package:di/di.dart' show Module; import 'main_static_expressions.dart' as generated_static_expressions; import 'main_static_metadata.dart' as generated_static_metadata; -import 'main_static_injector.dart' as generated_static_injector; class MyModule extends Module {} main() { - var app = staticApplicationFactory(generated_static_injector.factories, generated_static_metadata.typeAnnotations, generated_static_expressions.getters, generated_static_expressions.setters, generated_static_expressions.symbols) + var app = staticApplicationFactory(generated_static_metadata.typeAnnotations, generated_static_expressions.getters, generated_static_expressions.setters, generated_static_expressions.symbols) .addModule(new MyModule()) .run(); } @@ -80,12 +79,11 @@ import 'package:angular/application_factory_static.dart' as ng; import 'package:di/di.dart' show Module; import 'main_static_expressions.dart' as generated_static_expressions; import 'main_static_metadata.dart' as generated_static_metadata; -import 'main_static_injector.dart' as generated_static_injector; class MyModule extends Module {} main() { - var app = ng.staticApplicationFactory(generated_static_injector.factories, generated_static_metadata.typeAnnotations, generated_static_expressions.getters, generated_static_expressions.setters, generated_static_expressions.symbols) + var app = ng.staticApplicationFactory(generated_static_metadata.typeAnnotations, generated_static_expressions.getters, generated_static_expressions.setters, generated_static_expressions.symbols) .addModule(new MyModule()) .run(); } From 4fe8dd271ce8297cd7b6740385b3c059f516bb33 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sat, 28 Jun 2014 19:00:18 -0700 Subject: [PATCH 2/3] fixup! perf(View): Improve View instantiation speed and memory consumption. --- lib/core_dom/directive_map.dart | 1 + test/core_dom/compiler_spec.dart | 38 +++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/core_dom/directive_map.dart b/lib/core_dom/directive_map.dart index 8beb6cae0..12a195c5a 100644 --- a/lib/core_dom/directive_map.dart +++ b/lib/core_dom/directive_map.dart @@ -4,6 +4,7 @@ class DirectiveTypeTuple { final Directive directive; final Type type; DirectiveTypeTuple(this.directive, this.type); + toString() => '@$directive#$type'; } @Injectable() diff --git a/test/core_dom/compiler_spec.dart b/test/core_dom/compiler_spec.dart index 624974399..5e23d501a 100644 --- a/test/core_dom/compiler_spec.dart +++ b/test/core_dom/compiler_spec.dart @@ -86,7 +86,9 @@ void main() { ..bind(TwoOfTwoDirectives) ..bind(MyController) ..bind(MyParentController) - ..bind(MyChildController); + ..bind(MyChildController) + ..bind(SameNameDecorator) + ..bind(SameNameTransclude); }); beforeEach(inject((TestBed tb) => _ = tb)); @@ -938,6 +940,16 @@ void main() { expect(_.rootScope.context['myCtrl'] is MyController).toEqual(true); expect(_.rootElement.text).toEqual('MyController'); }); + + it('should allow multiple directives with the same selector of different type', (DirectiveMap map) { + _.compile('
'); + _.rootScope.apply(); + SameNameTransclude transclude = _.rootScope.context['sameTransclude']; + SameNameDecorator decorator = _.rootScope.context['sameDecorator']; + + expect(transclude.valueTransclude).toEqual('worked'); + expect(decorator.valueDecorator).toEqual('worked'); + }); }); })); } @@ -1348,3 +1360,27 @@ class OneTimeDecorator { OneTimeDecorator(this.log); set value(v) => log(v); } + +@Decorator( + selector: '[same-name]', + children: Directive.TRANSCLUDE_CHILDREN, + map: const { '.': '@valueTransclude' } +) +class SameNameTransclude { + var valueTransclude; + SameNameTransclude(ViewPort port, ViewFactory factory, RootScope scope) { + port.insertNew(factory); + scope.context['sameTransclude'] = this; + } +} + +@Decorator( + selector: '[same-name]', + map: const { 'same-name': '@valueDecorator' } +) +class SameNameDecorator { + var valueDecorator; + SameNameDecorator(RootScope scope) { + scope.context['sameDecorator'] = this; + } +} From 3bfbff85abd8379bd26f6d034ff8ed9038a28eaa Mon Sep 17 00:00:00 2001 From: Anting Shen Date: Fri, 27 Jun 2014 14:47:55 -0700 Subject: [PATCH 3/3] bind arg checking refactor --- lib/core_dom/directive_injector.dart | 32 ++++++++++----------------- pubspec.yaml | 2 +- test/angular_spec.dart | 33 ++++++++++++---------------- 3 files changed, 26 insertions(+), 41 deletions(-) diff --git a/lib/core_dom/directive_injector.dart b/lib/core_dom/directive_injector.dart index c99cdc2de..5d6f8aacf 100644 --- a/lib/core_dom/directive_injector.dart +++ b/lib/core_dom/directive_injector.dart @@ -137,6 +137,8 @@ class DirectiveInjector implements DirectiveBinder { } } + static Binding _temp_binding = new Binding(); + DirectiveInjector(parent, appInjector, this._node, this._nodeAttrs, this._eventHandler, this.scope, this._animate) : appInjector = appInjector, @@ -150,32 +152,20 @@ class DirectiveInjector implements DirectiveBinder { scope = null, _animate = null; - bind(key, {Function toFactory, Factory toFactoryPos, inject, - Visibility visibility: Visibility.LOCAL}) { + bind(key, {dynamic toValue: DEFAULT_VALUE, + Function toFactory: DEFAULT_VALUE, + Factory toFactoryPos: DEFAULT_VALUE, + Type toImplementation, inject: const[], + Visibility visibility: Visibility.LOCAL}) { if (key == null) throw 'Key is required'; if (key is! Key) key = new Key(key); - if (toFactory != null && toFactoryPos != null) { - throw "Can not have both toFactory and toFactoryPos."; - } + if (inject is! List) inject = [inject]; - if (inject == null) { - if (toFactory != null) throw "Can not have toFactory without inject"; - inject = Module.DEFAULT_REFLECTOR.parameterKeysFor(key.type); - toFactoryPos = Module.DEFAULT_REFLECTOR.factoryFor(key.type); - } else { - if (inject is! List) inject = [inject]; - for(var i=0; i < inject.length; i++) { - if (inject[i] is! Key) inject[i] = new Key(inject[i]); - } - } + _temp_binding.bind(key, Module.DEFAULT_REFLECTOR, toValue: toValue, toFactory: toFactory, + toFactoryPos: toFactoryPos, toImplementation: toImplementation, inject: inject); - if (toFactory != null && toFactoryPos == null) { - toFactoryPos = (List args) => Function.apply(toFactory, args); - } - if (toFactoryPos == null) toFactoryPos = _IDENTITY; - bindByKey(key, toFactoryPos, inject, visibility); + bindByKey(key, _temp_binding.factory, _temp_binding.parameterKeys, visibility); } - static Function _IDENTITY = (args) => args[0]; bindByKey(Key key, Factory factory, List parameterKeys, [Visibility visibility]) { if (visibility == null) visibility = Visibility.LOCAL; diff --git a/pubspec.yaml b/pubspec.yaml index 2f4a390c8..d988c78a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: browser: '>=0.10.0 <0.11.0' code_transformers: '>=0.1.3 <0.2.0' collection: '>=0.9.1 <1.0.0' - di: '>=2.0.0-alpha.1' + di: '>=2.0.0-alpha.3' html5lib: '>=0.10.0 <0.11.0' intl: '>=0.8.7 <0.10.0' perf_api: '>=0.0.8 <0.1.0' diff --git a/test/angular_spec.dart b/test/angular_spec.dart index a344c2d7f..aef6763ae 100644 --- a/test/angular_spec.dart +++ b/test/angular_spec.dart @@ -250,25 +250,22 @@ main() { "angular.watch_group.Watch", "change_detection.AvgStopwatch", "change_detection.FieldGetterFactory", - "di.CircularDependencyError", - "di.BaseError", - "di.Binding", - "di.DynamicReflectorError", - "di.Factory", - "di.annotations.Injectable", - "di.annotations.Injectables", - "di.Injector", "di.key.Key", "di.key.key", - "di.Module", - "di.ModuleInjector", - "di.NullReflector", - "di.NullReflectorError", - "di.NoProviderError", - "di.NoParentError", - "di.RootInjector", - "di.ResolvingError", - "di.TypeReflector", + "di.injector.Injector", + "di.injector.ModuleInjector", + "di.module.Module", + "di.module.Binding", + "di.module.DEFAULT_VALUE", + "di.module.Factory", + "di.reflector.TypeReflector", + "di.errors.ResolvingError", + "di.errors.CircularDependencyError", + "di.errors.NoProviderError", + "di.errors.DynamicReflectorError", + "di.errors.NoGeneratedTypeFactoryError", + "di.annotations.Injectable", + "di.annotations.Injectables", "route.client.Routable", "route.client.Route", "route.client.RouteEnterEvent", @@ -276,8 +273,6 @@ main() { "route.client.RouteEvent", "route.client.RouteHandle", "route.client.RouteImpl", - "route.client.RoutePreLeaveEvent", - "route.client.RoutePreLeaveEventHandler", "route.client.RouteLeaveEvent", "route.client.RouteLeaveEventHandler", "route.client.RoutePreEnterEvent",