From 50098767fc188426adc7c2f696df00b659b4799f Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Tue, 7 Apr 2015 20:54:20 -0700 Subject: [PATCH] refactor(render): use render layer fully MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces angular2/src/core/compiler/ViewFactory which extracts ProtoView.instantiate and replaces ViewPool. Note: This is a work in progress commit to unblock other commits. There will be follow ups to add unit tests, remove TODOs, … --- karma-dart.conf.js | 2 + modules/angular2/core.js | 2 +- modules/angular2/src/core/application.js | 38 +- .../angular2/src/core/compiler/compiler.js | 63 +- .../src/core/compiler/element_binder.js | 9 - .../src/core/compiler/element_injector.js | 45 +- .../angular2/src/core/compiler/ng_element.js | 34 + .../core/compiler/private_component_loader.js | 18 +- .../compiler/private_component_location.js | 21 +- .../src/core/compiler/proto_view_factory.js | 29 +- .../shadow_dom_emulation/content_tag.js | 88 -- .../shadow_dom_emulation/light_dom.js | 140 ---- .../src/core/compiler/shadow_dom_strategy.js | 114 --- modules/angular2/src/core/compiler/view.js | 391 ++------- .../src/core/compiler/view_container.js | 119 +-- .../src/core/compiler/view_factory.js | 109 +++ .../angular2/src/core/compiler/view_pool.js | 26 - modules/angular2/src/core/dom/element.js | 16 - modules/angular2/src/directives/class.js | 2 +- modules/angular2/src/directives/switch.js | 3 +- modules/angular2/src/render/api.js | 6 +- .../src/render/dom/compiler/compiler.js | 13 +- .../src/render/dom/direct_dom_renderer.js | 10 +- .../src/render/dom/view/proto_view_builder.js | 26 +- modules/angular2/src/render/dom/view/view.js | 21 +- .../src/render/dom/view/view_container.js | 16 +- .../src/render/dom/view/view_factory.js | 31 +- modules/angular2/src/services/ruler.js | 2 +- modules/angular2/src/test_lib/test_bed.js | 29 +- .../angular2/src/test_lib/test_injector.js | 19 +- modules/angular2/src/test_lib/utils.js | 9 +- .../test/core/compiler/compiler_spec.js | 8 +- .../core/compiler/element_injector_spec.js | 123 +-- .../test/core/compiler/integration_spec.js | 497 ++++++----- .../compiler/private_component_loader_spec.js | 2 +- .../compiler/shadow_dom/content_tag_spec.js | 47 -- .../compiler/shadow_dom/light_dom_spec.js | 219 ----- .../shadow_dom_emulation_integration_spec.js | 384 --------- .../core/compiler/shadow_dom_strategy_spec.js | 130 --- .../test/core/compiler/view_container_spec.js | 251 ------ .../test/core/compiler/view_pool_spec.js | 46 - .../angular2/test/core/compiler/view_spec.js | 787 ------------------ .../angular2/test/directives/class_spec.js | 32 +- modules/angular2/test/directives/for_spec.js | 40 +- modules/angular2/test/directives/if_spec.js | 68 +- .../test/directives/non_bindable_spec.js | 8 +- .../angular2/test/directives/switch_spec.js | 28 +- .../angular2/test/forms/integration_spec.js | 8 +- .../test/render/dom/integration_testbed.js | 4 +- .../shadow_dom_emulation_integration_spec.js | 2 +- modules/angular2/test/services/ruler_spec.js | 19 +- .../change_detection_benchmark.js | 6 +- .../src/compiler/compiler_benchmark.js | 23 +- .../element_injector_benchmark.js | 8 +- .../src/largetable/largetable_benchmark.js | 91 +- .../src/naive_infinite_scroll/index.js | 85 +- modules/benchmarks/src/tree/tree_benchmark.js | 87 +- .../examples/src/hello_world/index_static.js | 86 +- test-init.dart | 5 + test-main.dart | 2 - 60 files changed, 1206 insertions(+), 3341 deletions(-) create mode 100644 modules/angular2/src/core/compiler/ng_element.js delete mode 100644 modules/angular2/src/core/compiler/shadow_dom_emulation/content_tag.js delete mode 100644 modules/angular2/src/core/compiler/shadow_dom_emulation/light_dom.js delete mode 100644 modules/angular2/src/core/compiler/shadow_dom_strategy.js create mode 100644 modules/angular2/src/core/compiler/view_factory.js delete mode 100644 modules/angular2/src/core/compiler/view_pool.js delete mode 100644 modules/angular2/src/core/dom/element.js delete mode 100644 modules/angular2/test/core/compiler/shadow_dom/content_tag_spec.js delete mode 100644 modules/angular2/test/core/compiler/shadow_dom/light_dom_spec.js delete mode 100644 modules/angular2/test/core/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js delete mode 100644 modules/angular2/test/core/compiler/shadow_dom_strategy_spec.js delete mode 100644 modules/angular2/test/core/compiler/view_container_spec.js delete mode 100644 modules/angular2/test/core/compiler/view_pool_spec.js delete mode 100644 modules/angular2/test/core/compiler/view_spec.js create mode 100644 test-init.dart diff --git a/karma-dart.conf.js b/karma-dart.conf.js index 1ffe1b8257..1756386e65 100644 --- a/karma-dart.conf.js +++ b/karma-dart.conf.js @@ -8,6 +8,8 @@ module.exports = function(config) { frameworks: ['dart-unittest'], files: [ + // Init and configure guiness. + {pattern: 'test-init.dart', included: true}, // Unit test files needs to be included. // Karma-dart generates `__adapter_unittest.dart` that imports these files. {pattern: 'modules/*/test/**/*_spec.js', included: true}, diff --git a/modules/angular2/core.js b/modules/angular2/core.js index cad9b91e40..3a6f970937 100644 --- a/modules/angular2/core.js +++ b/modules/angular2/core.js @@ -14,5 +14,5 @@ export * from './src/core/compiler/private_component_location'; export * from './src/core/compiler/view'; export * from './src/core/compiler/view_container'; -export * from './src/core/dom/element'; +export * from './src/core/compiler/ng_element'; diff --git a/modules/angular2/src/core/application.js b/modules/angular2/src/core/application.js index 176689c50f..920b2f3cbf 100644 --- a/modules/angular2/src/core/application.js +++ b/modules/angular2/src/core/application.js @@ -9,12 +9,12 @@ import {ExceptionHandler} from './exception_handler'; import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; import {TemplateResolver} from './compiler/template_resolver'; import {DirectiveMetadataReader} from './compiler/directive_metadata_reader'; -import {DirectiveBinding} from './compiler/element_injector'; import {List, ListWrapper} from 'angular2/src/facade/collection'; import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone'; import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle'; -import {ShadowDomStrategy, NativeShadowDomStrategy, EmulatedUnscopedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; +import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy'; +import {EmulatedUnscopedShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy'; import {XHR} from 'angular2/src/services/xhr'; import {XHRImpl} from 'angular2/src/services/xhr_impl'; import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager'; @@ -27,6 +27,13 @@ import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner'; import {Component} from 'angular2/src/core/annotations/annotations'; import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_component_loader'; import {TestabilityRegistry, Testability} from 'angular2/src/core/testability/testability'; +import {ViewFactory, VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_factory'; +import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; +import {Renderer} from 'angular2/src/render/api'; +import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; +import * as rc from 'angular2/src/render/dom/compiler/compiler'; +import * as rvf from 'angular2/src/render/dom/view/view_factory'; + import { appViewToken, appChangeDetectorToken, @@ -60,7 +67,7 @@ function _injectorBindings(appComponentType): List { return element; }, [appComponentAnnotatedTypeToken, appDocumentToken]), bind(appViewToken).toAsyncFactory((changeDetection, compiler, injector, appElement, - appComponentAnnotatedType, strategy, eventManager, testability, registry) => { + appComponentAnnotatedType, testability, registry, viewFactory) => { // We need to do this here to ensure that we create Testability and // it's ready on the window for users. @@ -73,18 +80,18 @@ function _injectorBindings(appComponentType): List { } return compiler.compileRoot( appElement, - DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation) + appComponentAnnotatedType.type ).then( (appProtoView) => { // The light Dom of the app element is not considered part of // the angular application. Thus the context and lightDomInjector are // empty. - var view = appProtoView.instantiate(null, eventManager); - view.hydrate(injector, null, null, new Object(), null); + var view = viewFactory.getView(appProtoView); + view.hydrate(injector, null, new Object(), null); return view; }); }, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken, - ShadowDomStrategy, EventManager, Testability, TestabilityRegistry]), + Testability, TestabilityRegistry, ViewFactory]), bind(appChangeDetectorToken).toFactory((rootView) => rootView.changeDetector, [appViewToken]), @@ -98,6 +105,23 @@ function _injectorBindings(appComponentType): List { bind(ShadowDomStrategy).toFactory( (styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head), [StyleUrlResolver, appDocumentToken]), + bind(Renderer).toClass(DirectDomRenderer), + bind(rc.Compiler).toClass(rc.DefaultCompiler), + // TODO(tbosch): We need an explicit factory here, as + // we are getting errors in dart2js with mirrors... + bind(rvf.ViewFactory).toFactory( + (capacity, eventManager, shadowDomStrategy) => new rvf.ViewFactory(capacity, eventManager, shadowDomStrategy), + [rvf.VIEW_POOL_CAPACITY, EventManager, ShadowDomStrategy] + ), + bind(rvf.VIEW_POOL_CAPACITY).toValue(100000), + ProtoViewFactory, + // TODO(tbosch): We need an explicit factory here, as + // we are getting errors in dart2js with mirrors... + bind(ViewFactory).toFactory( + (capacity) => new ViewFactory(capacity), + [VIEW_POOL_CAPACITY] + ), + bind(VIEW_POOL_CAPACITY).toValue(100000), Compiler, CompilerCache, TemplateResolver, diff --git a/modules/angular2/src/core/compiler/compiler.js b/modules/angular2/src/core/compiler/compiler.js index 6c382412e9..ed27b82e02 100644 --- a/modules/angular2/src/core/compiler/compiler.js +++ b/modules/angular2/src/core/compiler/compiler.js @@ -3,24 +3,16 @@ import {Type, isBlank, isPresent, BaseException, normalizeBlank, stringify} from import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection'; -import {ChangeDetection, Parser} from 'angular2/change_detection'; - import {DirectiveMetadataReader} from './directive_metadata_reader'; import {Component, Viewport, DynamicComponent, Decorator} from '../annotations/annotations'; import {ProtoView} from './view'; import {DirectiveBinding} from './element_injector'; import {TemplateResolver} from './template_resolver'; import {Template} from '../annotations/template'; -import {ShadowDomStrategy} from './shadow_dom_strategy'; import {ComponentUrlMapper} from './component_url_mapper'; import {ProtoViewFactory} from './proto_view_factory'; import {UrlResolver} from 'angular2/src/services/url_resolver'; -import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; -import {DefaultStepFactory} from 'angular2/src/render/dom/compiler/compile_step_factory'; -import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; - -import * as rc from 'angular2/src/render/dom/compiler/compiler'; import * as renderApi from 'angular2/src/render/api'; /** @@ -49,9 +41,7 @@ export class CompilerCache { } -// TODO(tbosch): rename this class to Compiler -// and remove the current Compiler when core uses the render views. -export class NewCompiler { +export class Compiler { _reader: DirectiveMetadataReader; _compilerCache:CompilerCache; _compiling:Map; @@ -80,19 +70,19 @@ export class NewCompiler { this._protoViewFactory = protoViewFactory; } - _bindDirective(directive) { - var meta = this._reader.read(directive); + _bindDirective(directiveTypeOrBinding) { + if (directiveTypeOrBinding instanceof DirectiveBinding) { + return directiveTypeOrBinding; + } + var meta = this._reader.read(directiveTypeOrBinding); return DirectiveBinding.createFromType(meta.type, meta.annotation); } // Create a rootView as if the compiler encountered . // Used for bootstrapping. - compileRoot(elementOrSelector, componentBinding:DirectiveBinding):Promise { + compileRoot(elementOrSelector, componentTypeOrBinding:any):Promise { return this._renderer.createRootProtoView(elementOrSelector, 'root').then( (rootRenderPv) => { - return this._compileNestedProtoViews(null, rootRenderPv, [componentBinding], true) - }).then( (rootProtoView) => { - rootProtoView.instantiateInPlace = true; - return rootProtoView; + return this._compileNestedProtoViews(null, rootRenderPv, [this._bindDirective(componentTypeOrBinding)], true); }); } @@ -266,40 +256,3 @@ export class NewCompiler { } } - -// TODO(tbosch): delete this class once we use the render views -/** - * The compiler loads and translates the html templates of components into - * nested ProtoViews. To decompose its functionality it uses - * the render compiler. - * - * @publicModule angular2/template - */ -@Injectable() -export class Compiler extends NewCompiler { - constructor(changeDetection:ChangeDetection, - templateLoader:TemplateLoader, - reader: DirectiveMetadataReader, - parser:Parser, - cache:CompilerCache, - shadowDomStrategy: ShadowDomStrategy, - templateResolver: TemplateResolver, - componentUrlMapper: ComponentUrlMapper, - urlResolver: UrlResolver) { - super( - reader, - cache, - templateResolver, - componentUrlMapper, - urlResolver, - new DirectDomRenderer( - new rc.Compiler( - new DefaultStepFactory(parser, shadowDomStrategy.render), - templateLoader - ), - null, shadowDomStrategy.render - ), - new ProtoViewFactory(changeDetection, shadowDomStrategy) - ); - } -} diff --git a/modules/angular2/src/core/compiler/element_binder.js b/modules/angular2/src/core/compiler/element_binder.js index 3c8e925cb6..dc82640c3f 100644 --- a/modules/angular2/src/core/compiler/element_binder.js +++ b/modules/angular2/src/core/compiler/element_binder.js @@ -8,11 +8,8 @@ export class ElementBinder { protoElementInjector:eiModule.ProtoElementInjector; componentDirective:DirectiveBinding; viewportDirective:DirectiveBinding; - textNodeIndices:List; - hasElementPropertyBindings:boolean; nestedProtoView: viewModule.ProtoView; events:StringMap; - contentTagSelector:string; parent:ElementBinder; index:int; distanceToParent:int; @@ -32,13 +29,7 @@ export class ElementBinder { this.distanceToParent = distanceToParent; // updated later when events are bound this.events = null; - // updated later when text nodes are bound - this.textNodeIndices = null; - // updated later when element properties are bound - this.hasElementPropertyBindings = false; // updated later, so we are able to resolve cycles this.nestedProtoView = null; - // updated later in the compilation pipeline - this.contentTagSelector = null; } } diff --git a/modules/angular2/src/core/compiler/element_injector.js b/modules/angular2/src/core/compiler/element_injector.js index 900728753f..1566d3a3cb 100644 --- a/modules/angular2/src/core/compiler/element_injector.js +++ b/modules/angular2/src/core/compiler/element_injector.js @@ -6,11 +6,10 @@ import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; import {EventEmitter, PropertySetter, Attribute} from 'angular2/src/core/annotations/di'; import * as viewModule from 'angular2/src/core/compiler/view'; import {ViewContainer} from 'angular2/src/core/compiler/view_container'; -import {NgElement} from 'angular2/src/core/dom/element'; +import {NgElement} from 'angular2/src/core/compiler/ng_element'; import {Directive, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations'; import {BindingPropagationConfig} from 'angular2/change_detection'; import * as pclModule from 'angular2/src/core/compiler/private_component_location'; -import {setterFactory} from 'angular2/src/render/dom/view/property_setter_factory'; var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10; @@ -268,8 +267,8 @@ export class ProtoElementInjector { } } - instantiate(parent:ElementInjector, host:ElementInjector):ElementInjector { - return new ElementInjector(this, parent, host); + instantiate(parent:ElementInjector):ElementInjector { + return new ElementInjector(this, parent); } directParent(): ProtoElementInjector { @@ -339,21 +338,12 @@ export class ElementInjector extends TreeNode { _privateComponent; _privateComponentBinding:DirectiveBinding; - constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector) { + constructor(proto:ProtoElementInjector, parent:ElementInjector) { super(parent); - if (isPresent(parent) && isPresent(host)) { - throw new BaseException('Only either parent or host is allowed'); - } - this._host = null; // needed to satisfy Dart - if (isPresent(parent)) { - this._host = parent._host; - } else { - this._host = host; - } - this._proto = proto; //we cannot call clearDirectives because fields won't be detected + this._host = null; this._preBuiltObjects = null; this._lightDomAppInjector = null; this._shadowDomAppInjector = null; @@ -371,6 +361,7 @@ export class ElementInjector extends TreeNode { } clearDirectives() { + this._host = null; this._preBuiltObjects = null; this._lightDomAppInjector = null; this._shadowDomAppInjector = null; @@ -406,7 +397,8 @@ export class ElementInjector extends TreeNode { this._constructionCounter = 0; } - instantiateDirectives(lightDomAppInjector:Injector, shadowDomAppInjector:Injector, preBuiltObjects:PreBuiltObjects) { + instantiateDirectives(lightDomAppInjector:Injector, host:ElementInjector, shadowDomAppInjector:Injector, preBuiltObjects:PreBuiltObjects) { + this._host = host; this._checkShadowDomAppInjector(shadowDomAppInjector); this._preBuiltObjects = preBuiltObjects; @@ -456,10 +448,6 @@ export class ElementInjector extends TreeNode { return pb !== _undefined && isPresent(pb); } - forElement(el):boolean { - return this._preBuiltObjects.element.domElement === el; - } - /** Gets the NgElement associated with this ElementInjector */ getNgElement() { return this._preBuiltObjects.element; @@ -538,6 +526,10 @@ export class ElementInjector extends TreeNode { return obj; } + getBoundElementIndex() { + return this._proto.index; + } + _getByDependency(dep:DirectiveDependency, requestor:Key) { if (isPresent(dep.eventEmitterName)) return this._buildEventEmitter(dep); if (isPresent(dep.propSetterName)) return this._buildPropSetter(dep); @@ -553,10 +545,12 @@ export class ElementInjector extends TreeNode { } _buildPropSetter(dep) { - var ngElement = this._getPreBuiltObjectByKeyId(StaticKeys.instance().ngElementId); - var domElement = ngElement.domElement; - var setter = setterFactory(dep.propSetterName); - return function(v) { setter(domElement, v) }; + var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId); + var renderer = view.proto.renderer; + var index = this._proto.index; + return function(v) { + renderer.setElementProperty(view.render, index, dep.propSetterName, v); + }; } _buildAttribute(dep): string { @@ -582,7 +576,6 @@ export class ElementInjector extends TreeNode { */ _getByKey(key:Key, depth:number, optional:boolean, requestor:Key) { var ei = this; - if (! this._shouldIncludeSelf(depth)) { depth -= ei._proto.distanceToParent; ei = ei._parent; @@ -631,7 +624,7 @@ export class ElementInjector extends TreeNode { if (keyId === staticKeys.bindingPropagationConfigId) return this._preBuiltObjects.bindingPropagationConfig; if (keyId === staticKeys.privateComponentLocationId) { - return new pclModule.PrivateComponentLocation(this, this._preBuiltObjects.element, this._preBuiltObjects.view); + return new pclModule.PrivateComponentLocation(this, this._preBuiltObjects.view); } //TODO add other objects as needed diff --git a/modules/angular2/src/core/compiler/ng_element.js b/modules/angular2/src/core/compiler/ng_element.js new file mode 100644 index 0000000000..af496c0d26 --- /dev/null +++ b/modules/angular2/src/core/compiler/ng_element.js @@ -0,0 +1,34 @@ +import {DOM} from 'angular2/src/dom/dom_adapter'; +import {normalizeBlank} from 'angular2/src/facade/lang'; +import * as viewModule from '../compiler/view'; +import {DirectDomViewRef} from 'angular2/src/render/dom/direct_dom_renderer'; + +/** + * Allows direct access to the underlying DOM element. + * + * Attention: NgElement will be replaced by a different concept + * for accessing an element in a way that is compatible with the render layer. + * + * @publicModule angular2/angular2 + */ +export class NgElement { + _view:viewModule.View; + _boundElementIndex:number; + + constructor(view, boundElementIndex) { + this._view = view; + this._boundElementIndex = boundElementIndex; + } + + // TODO(tbosch): Here we expose the real DOM element. + // We need a more general way to read/write to the DOM element + // via a proper abstraction in the render layer + get domElement() { + var domViewRef:DirectDomViewRef = this._view.render; + return domViewRef.delegate.boundElements[this._boundElementIndex]; + } + + getAttribute(name:string) { + return normalizeBlank(DOM.getAttribute(this.domElement, name)); + } +} \ No newline at end of file diff --git a/modules/angular2/src/core/compiler/private_component_loader.js b/modules/angular2/src/core/compiler/private_component_loader.js index 7bb69157ab..49da82bd18 100644 --- a/modules/angular2/src/core/compiler/private_component_loader.js +++ b/modules/angular2/src/core/compiler/private_component_loader.js @@ -1,7 +1,6 @@ import {Compiler} from './compiler'; -import {ShadowDomStrategy} from './shadow_dom_strategy'; +import {ViewFactory} from './view_factory'; import {Injectable} from 'angular2/di'; -import {EventManager} from 'angular2/src/render/dom/events/event_manager'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; import {Component} from 'angular2/src/core/annotations/annotations'; import {PrivateComponentLocation} from './private_component_location'; @@ -11,17 +10,14 @@ import {Type, stringify, BaseException} from 'angular2/src/facade/lang'; @Injectable() export class PrivateComponentLoader { compiler:Compiler; - shadowDomStrategy:ShadowDomStrategy; - eventManager:EventManager; directiveMetadataReader:DirectiveMetadataReader; + viewFactory:ViewFactory; - constructor(compiler:Compiler, shadowDomStrategy:ShadowDomStrategy, - eventManager:EventManager, directiveMetadataReader:DirectiveMetadataReader) { + constructor(compiler:Compiler, directiveMetadataReader:DirectiveMetadataReader, viewFactory:ViewFactory) { this.compiler = compiler; - this.shadowDomStrategy = shadowDomStrategy; - this.eventManager = eventManager; this.directiveMetadataReader = directiveMetadataReader; + this.viewFactory = viewFactory; } load(type:Type, location:PrivateComponentLocation) { @@ -33,10 +29,10 @@ export class PrivateComponentLoader { return this.compiler.compile(type).then((componentProtoView) => { location.createComponent( + this.viewFactory, type, annotation, - componentProtoView, - this.eventManager, - this.shadowDomStrategy); + componentProtoView + ); }); } } diff --git a/modules/angular2/src/core/compiler/private_component_location.js b/modules/angular2/src/core/compiler/private_component_location.js index 86b7c01ff6..e8282be9da 100644 --- a/modules/angular2/src/core/compiler/private_component_location.js +++ b/modules/angular2/src/core/compiler/private_component_location.js @@ -1,33 +1,28 @@ import {Directive} from 'angular2/src/core/annotations/annotations' -import {NgElement} from 'angular2/src/core/dom/element'; import * as viewModule from './view'; import * as eiModule from './element_injector'; -import {ShadowDomStrategy} from './shadow_dom_strategy'; -import {EventManager} from 'angular2/src/render/dom/events/event_manager'; import {ListWrapper} from 'angular2/src/facade/collection'; import {Type} from 'angular2/src/facade/lang'; - +import * as vfModule from './view_factory'; export class PrivateComponentLocation { _elementInjector:eiModule.ElementInjector; - _elt:NgElement; _view:viewModule.View; - constructor(elementInjector:eiModule.ElementInjector, elt:NgElement, view:viewModule.View){ + constructor(elementInjector:eiModule.ElementInjector, view:viewModule.View){ this._elementInjector = elementInjector; - this._elt = elt; this._view = view; } - createComponent(type:Type, annotation:Directive, componentProtoView:viewModule.ProtoView, - eventManager:EventManager, shadowDomStrategy:ShadowDomStrategy) { + createComponent(viewFactory: vfModule.ViewFactory, type:Type, annotation:Directive, componentProtoView:viewModule.ProtoView) { var context = this._elementInjector.createPrivateComponent(type, annotation); - var view = componentProtoView.instantiate(this._elementInjector, eventManager); - view.hydrate(this._elementInjector.getShadowDomAppInjector(), this._elementInjector, null, context, null); - - shadowDomStrategy.attachTemplate(this._elt.domElement, view); + var view = viewFactory.getView(componentProtoView); + view.hydrate(this._elementInjector.getShadowDomAppInjector(), this._elementInjector, context, null); + this._view.proto.renderer.setDynamicComponentView( + this._view.render, this._elementInjector.getBoundElementIndex(), view.render + ); ListWrapper.push(this._view.componentChildViews, view); this._view.changeDetector.addChild(view.changeDetector); } diff --git a/modules/angular2/src/core/compiler/proto_view_factory.js b/modules/angular2/src/core/compiler/proto_view_factory.js index 9a843ff7d0..a7a4a3ecdc 100644 --- a/modules/angular2/src/core/compiler/proto_view_factory.js +++ b/modules/angular2/src/core/compiler/proto_view_factory.js @@ -1,23 +1,23 @@ +import {Injectable} from 'angular2/di'; import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {reflector} from 'angular2/src/reflection/reflection'; import {ChangeDetection} from 'angular2/change_detection'; -import {ShadowDomStrategy} from './shadow_dom_strategy'; import {Component, Viewport, DynamicComponent} from '../annotations/annotations'; import * as renderApi from 'angular2/src/render/api'; -import {DirectDomProtoViewRef} from 'angular2/src/render/dom/direct_dom_renderer'; import {ProtoView} from './view'; import {ProtoElementInjector, DirectiveBinding} from './element_injector'; +@Injectable() export class ProtoViewFactory { _changeDetection:ChangeDetection; - _shadowDomStrategy:ShadowDomStrategy; + _renderer:renderApi.Renderer; - constructor(changeDetection, shadowDomStrategy) { + constructor(changeDetection:ChangeDetection, renderer:renderApi.Renderer) { this._changeDetection = changeDetection; - this._shadowDomStrategy = shadowDomStrategy; + this._renderer = renderer; } createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoView, directives:List):ProtoView { @@ -30,13 +30,10 @@ export class ProtoViewFactory { 'dummy', componentAnnotation.changeDetection ); } - var domProtoView = this._getDomProtoView(renderProtoView.render); - var protoView = new ProtoView(renderProtoView.render, domProtoView.element, protoChangeDetector, - this._shadowDomStrategy, null); + var protoView = new ProtoView(this._renderer, renderProtoView.render, protoChangeDetector); for (var i=0; i { - protoView.bindElementProperty(astWithSource.ast, propertyName, MapWrapper.get(domElementBinder.propertySetters, propertyName)); + protoView.bindElementProperty(astWithSource.ast, propertyName); }); // events MapWrapper.forEach(renderElementBinder.eventBindings, (astWithSource, eventName) => { diff --git a/modules/angular2/src/core/compiler/shadow_dom_emulation/content_tag.js b/modules/angular2/src/core/compiler/shadow_dom_emulation/content_tag.js deleted file mode 100644 index 0cb1210176..0000000000 --- a/modules/angular2/src/core/compiler/shadow_dom_emulation/content_tag.js +++ /dev/null @@ -1,88 +0,0 @@ -import * as ldModule from './light_dom'; -import {Inject, Injectable} from 'angular2/di'; -import {DOM} from 'angular2/src/dom/dom_adapter'; -import {isPresent} from 'angular2/src/facade/lang'; -import {List, ListWrapper} from 'angular2/src/facade/collection'; - -class ContentStrategy { - nodes:List; - insert(nodes:List){} -} - -/** - * An implementation of the content tag that is used by transcluding components. - * It is used when the content tag is not a direct child of another component, - * and thus does not affect redistribution. - */ -@Injectable() -class RenderedContent extends ContentStrategy { - beginScript; - endScript; - - constructor(contentEl) { - super(); - this.beginScript = contentEl; - this.endScript = DOM.nextSibling(this.beginScript); - this.nodes = []; - } - - // Inserts the nodes in between the start and end scripts. - // Previous content is removed. - insert(nodes:List) { - this.nodes = nodes; - DOM.insertAllBefore(this.endScript, nodes); - this._removeNodesUntil(ListWrapper.isEmpty(nodes) ? this.endScript : nodes[0]); - } - - _removeNodesUntil(node) { - var p = DOM.parentElement(this.beginScript); - for (var next = DOM.nextSibling(this.beginScript); - next !== node; - next = DOM.nextSibling(this.beginScript)) { - DOM.removeChild(p, next); - } - } -} - -/** - * An implementation of the content tag that is used by transcluding components. - * It is used when the content tag is a direct child of another component, - * and thus does not get rendered but only affect the distribution of its parent component. - */ -class IntermediateContent extends ContentStrategy { - destinationLightDom:ldModule.LightDom; - - constructor(destinationLightDom:ldModule.LightDom) { - super(); - this.destinationLightDom = destinationLightDom; - this.nodes = []; - } - - insert(nodes:List) { - this.nodes = nodes; - this.destinationLightDom.redistribute(); - } -} - - -export class Content { - select:string; - _strategy:ContentStrategy; - contentStartElement; - - constructor(destinationLightDom:ldModule.LightDom, contentStartEl, selector:string) { - this.select = selector; - this.contentStartElement = contentStartEl; - this._strategy = isPresent(destinationLightDom) ? - new IntermediateContent(destinationLightDom) : - new RenderedContent(contentStartEl); - } - - nodes():List { - return this._strategy.nodes; - } - - insert(nodes:List) { - this._strategy.insert(nodes); - } -} diff --git a/modules/angular2/src/core/compiler/shadow_dom_emulation/light_dom.js b/modules/angular2/src/core/compiler/shadow_dom_emulation/light_dom.js deleted file mode 100644 index 82a915ae4e..0000000000 --- a/modules/angular2/src/core/compiler/shadow_dom_emulation/light_dom.js +++ /dev/null @@ -1,140 +0,0 @@ -import {DOM} from 'angular2/src/dom/dom_adapter'; -import {List, ListWrapper} from 'angular2/src/facade/collection'; -import {isBlank, isPresent} from 'angular2/src/facade/lang'; - -import * as viewModule from '../view'; -import {Content} from './content_tag'; - -export class DestinationLightDom {} - - -class _Root { - node; - viewContainer; - content; - - constructor(node, viewContainer, content) { - this.node = node; - this.viewContainer = viewContainer; - this.content = content; - } -} - -// TODO: LightDom should implement DestinationLightDom -// once interfaces are supported -export class LightDom { - // The light DOM of the element is enclosed inside the lightDomView - lightDomView:viewModule.View; - // The shadow DOM - shadowDomView:viewModule.View; - // The nodes of the light DOM - nodes:List; - roots:List<_Root>; - - constructor(lightDomView:viewModule.View, shadowDomView:viewModule.View, element) { - this.lightDomView = lightDomView; - this.shadowDomView = shadowDomView; - this.nodes = DOM.childNodesAsList(element); - - this.roots = null; - } - - redistribute() { - var tags = this.contentTags(); - if (tags.length > 0) { - redistributeNodes(tags, this.expandedDomNodes()); - } - } - - contentTags(): List { - return this._collectAllContentTags(this.shadowDomView, []); - } - - // Collects the Content directives from the view and all its child views - _collectAllContentTags(view: viewModule.View, acc:List):List { - var contentTags = view.contentTags; - var vcs = view.viewContainers; - for (var i=0; i { - this._collectAllContentTags(view, acc); - }); - } - } - return acc; - } - - // Collects the nodes of the light DOM by merging: - // - nodes from enclosed ViewContainers, - // - nodes from enclosed content tags, - // - plain DOM nodes - expandedDomNodes():List { - var res = []; - - var roots = this._roots(); - for (var i = 0; i < roots.length; ++i) { - - var root = roots[i]; - - if (isPresent(root.viewContainer)) { - res = ListWrapper.concat(res, root.viewContainer.nodes()); - } else if (isPresent(root.content)) { - res = ListWrapper.concat(res, root.content.nodes()); - } else { - ListWrapper.push(res, root.node); - } - } - return res; - } - - // Returns a list of Roots for all the nodes of the light DOM. - // The Root object contains the DOM node and its corresponding injector (could be null). - _roots() { - if (isPresent(this.roots)) return this.roots; - - var viewContainers = this.lightDomView.viewContainers; - var contentTags = this.lightDomView.contentTags; - - this.roots = ListWrapper.map(this.nodes, (n) => { - var foundVc = null; - var foundContentTag = null; - for (var i=0; i, nodes:List) { - for (var i = 0; i < contents.length; ++i) { - var content = contents[i]; - var select = content.select; - var matchSelector = (n) => DOM.elementMatches(n, select); - - // Empty selector is identical to - if (select.length === 0) { - content.insert(nodes); - ListWrapper.clear(nodes); - } else { - var matchingNodes = ListWrapper.filter(nodes, matchSelector); - content.insert(matchingNodes); - ListWrapper.removeAll(nodes, matchingNodes); - } - } -} diff --git a/modules/angular2/src/core/compiler/shadow_dom_strategy.js b/modules/angular2/src/core/compiler/shadow_dom_strategy.js deleted file mode 100644 index 72cca0a9a6..0000000000 --- a/modules/angular2/src/core/compiler/shadow_dom_strategy.js +++ /dev/null @@ -1,114 +0,0 @@ -import {Injectable} from 'angular2/di'; - -import {stringify} from 'angular2/src/facade/lang'; -import {DOM} from 'angular2/src/dom/dom_adapter'; -import * as viewModule from './view'; -import {LightDom} from './shadow_dom_emulation/light_dom'; -import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner'; -import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; - -// temporal import while we migrated the views over -import * as sds from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy'; -import * as nsds from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy'; -import * as eusds from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy'; -import * as essds from 'angular2/src/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy'; - -/** - * @publicModule angular2/template - */ -export class ShadowDomStrategy { - render: sds.ShadowDomStrategy; - - attachTemplate(el, view:viewModule.View) {} - constructLightDom(lightDomView:viewModule.View, shadowDomView:viewModule.View, el): LightDom { return null; } - - shimAppElement(componentType, insertionElement) { - this.render.processElement(null, stringify(componentType), insertionElement); - } -} - -/** - * This strategy emulates the Shadow DOM for the templates, styles **excluded**: - * - components templates are added as children of their component element, - * - styles are moved from the templates to the styleHost (i.e. the document head). - * - * Notes: - * - styles are **not** scoped to their component and will apply to the whole document, - * - you can **not** use shadow DOM specific selectors in the styles - * - * @publicModule angular2/template - */ -@Injectable() -export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy { - - constructor(styleUrlResolver: StyleUrlResolver, styleHost) { - super(); - this.render = new eusds.EmulatedUnscopedShadowDomStrategy(styleUrlResolver, styleHost); - } - - attachTemplate(el, view:viewModule.View) { - DOM.clearNodes(el); - _moveViewNodesIntoParent(el, view); - } - - constructLightDom(lightDomView:viewModule.View, shadowDomView:viewModule.View, el): LightDom { - return new LightDom(lightDomView, shadowDomView, el); - } -} - -/** - * This strategy emulates the Shadow DOM for the templates, styles **included**: - * - components templates are added as children of their component element, - * - both the template and the styles are modified so that styles are scoped to the component - * they belong to, - * - styles are moved from the templates to the styleHost (i.e. the document head). - * - * Notes: - * - styles are scoped to their component and will apply only to it, - * - a common subset of shadow DOM selectors are supported, - * - see `ShadowCss` for more information and limitations. - * - * @publicModule angular2/template - */ -@Injectable() -export class EmulatedScopedShadowDomStrategy extends ShadowDomStrategy { - - constructor(styleInliner: StyleInliner, styleUrlResolver: StyleUrlResolver, styleHost) { - super(); - this.render = new essds.EmulatedScopedShadowDomStrategy(styleInliner, styleUrlResolver, styleHost); - } - - attachTemplate(el, view:viewModule.View) { - DOM.clearNodes(el); - _moveViewNodesIntoParent(el, view); - } - - constructLightDom(lightDomView:viewModule.View, shadowDomView:viewModule.View, el): LightDom { - return new LightDom(lightDomView, shadowDomView, el); - } -} - -/** - * This strategies uses the native Shadow DOM support. - * - * The templates for the component are inserted in a Shadow Root created on the component element. - * Hence they are strictly isolated. - */ -@Injectable() -export class NativeShadowDomStrategy extends ShadowDomStrategy { - - constructor(styleUrlResolver: StyleUrlResolver) { - super(); - this.render = new nsds.NativeShadowDomStrategy(styleUrlResolver); - } - - attachTemplate(el, view:viewModule.View){ - _moveViewNodesIntoParent(DOM.createShadowRoot(el), view); - } -} - -function _moveViewNodesIntoParent(parent, view) { - for (var i = 0; i < view.nodes.length; ++i) { - DOM.appendChild(parent, view.nodes[i]); - } -} diff --git a/modules/angular2/src/core/compiler/view.js b/modules/angular2/src/core/compiler/view.js index 436d0e1941..daefe68cee 100644 --- a/modules/angular2/src/core/compiler/view.js +++ b/modules/angular2/src/core/compiler/view.js @@ -1,5 +1,3 @@ -import {DOM} from 'angular2/src/dom/dom_adapter'; -import {Promise} from 'angular2/src/facade/async'; import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; import {AST, Locals, ChangeDispatcher, ProtoChangeDetector, ChangeDetector, ChangeRecord, BindingRecord, BindingPropagationConfig, uninitialized} from 'angular2/change_detection'; @@ -9,76 +7,51 @@ import {ElementBinder} from './element_binder'; import {SetterFn} from 'angular2/src/reflection/types'; import {IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; import {Injector} from 'angular2/di'; -import {NgElement} from 'angular2/src/core/dom/element'; import {ViewContainer} from './view_container'; -import {LightDom} from './shadow_dom_emulation/light_dom'; -import {Content} from './shadow_dom_emulation/content_tag'; -import {ShadowDomStrategy} from './shadow_dom_strategy'; -import {ViewPool} from './view_pool'; -import {EventManager} from 'angular2/src/render/dom/events/event_manager'; import * as renderApi from 'angular2/src/render/api'; -const NG_BINDING_CLASS = 'ng-binding'; -const NG_BINDING_CLASS_SELECTOR = '.ng-binding'; - -// TODO(rado): make this configurable/smarter. -var VIEW_POOL_CAPACITY = 10000; -var VIEW_POOL_PREFILL = 0; - /** * Const of making objects: http://jsperf.com/instantiate-size-of-object * * @publicModule angular2/template */ @IMPLEMENTS(ChangeDispatcher) +// TODO(tbosch): this is not supported in dart2js (no '.' is allowed) +// @IMPLEMENTS(renderApi.EventDispatcher) export class View { + render:renderApi.ViewRef; /// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector rootElementInjectors:List; elementInjectors:List; - bindElements:List; - textNodes:List; changeDetector:ChangeDetector; - /// When the view is part of render tree, the DocumentFragment is empty, which is why we need - /// to keep track of the nodes. - nodes:List; componentChildViews: List; viewContainers: List; - contentTags: List; preBuiltObjects: List; - lightDoms: List; proto: ProtoView; context: any; locals:Locals; - constructor(proto:ProtoView, nodes:List, protoLocals:Map) { + constructor(proto:ProtoView, protoLocals:Map) { + this.render = null; this.proto = proto; - this.nodes = nodes; this.changeDetector = null; this.elementInjectors = null; this.rootElementInjectors = null; - this.textNodes = null; - this.bindElements = null; this.componentChildViews = null; this.viewContainers = null; - this.contentTags = null; this.preBuiltObjects = null; - this.lightDoms = null; this.context = null; this.locals = new Locals(null, MapWrapper.clone(protoLocals)); //TODO optimize this } - init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List, textNodes: List, bindElements:List, - viewContainers:List, contentTags:List, preBuiltObjects:List, componentChildViews:List, lightDoms:List) { + init(changeDetector:ChangeDetector, elementInjectors:List, rootElementInjectors:List, + viewContainers:List, preBuiltObjects:List, componentChildViews:List) { this.changeDetector = changeDetector; this.elementInjectors = elementInjectors; this.rootElementInjectors = rootElementInjectors; - this.textNodes = textNodes; - this.bindElements = bindElements; this.viewContainers = viewContainers; - this.contentTags = contentTags; this.preBuiltObjects = preBuiltObjects; this.componentChildViews = componentChildViews; - this.lightDoms = lightDoms; } setLocal(contextName: string, value) { @@ -124,16 +97,33 @@ export class View { * A call to hydrate/dehydrate does not attach/detach the view from the view * tree. */ - hydrate(appInjector: Injector, hostElementInjector: ElementInjector, hostLightDom: LightDom, + hydrate(appInjector: Injector, hostElementInjector: ElementInjector, context: Object, locals:Locals) { + var renderComponentViewRefs = this.proto.renderer.createView(this.proto.render); + this.internalHydrateRecurse(renderComponentViewRefs, 0, appInjector, hostElementInjector, context, locals); + } + + dehydrate() { + var render = this.render; + this.internalDehydrateRecurse(); + this.proto.renderer.destroyView(render); + } + + internalHydrateRecurse( + renderComponentViewRefs:List, + renderComponentIndex:number, + appInjector: Injector, hostElementInjector: ElementInjector, + context: Object, locals:Locals):number { if (this.hydrated()) throw new BaseException('The view is already hydrated.'); + + this.render = renderComponentViewRefs[renderComponentIndex++]; this._hydrateContext(context, locals); // viewContainers for (var i = 0; i < this.viewContainers.length; i++) { var vc = this.viewContainers[i]; if (isPresent(vc)) { - vc.hydrate(appInjector, hostElementInjector, hostLightDom); + vc.internalHydrateRecurse(new renderApi.ViewContainerRef(this.render, i), appInjector, hostElementInjector); } } @@ -158,7 +148,7 @@ export class View { // elementInjectors var elementInjector = this.elementInjectors[i]; if (isPresent(elementInjector)) { - elementInjector.instantiateDirectives(appInjector, shadowDomAppInjector, this.preBuiltObjects[i]); + elementInjector.instantiateDirectives(appInjector, hostElementInjector, shadowDomAppInjector, this.preBuiltObjects[i]); // The exporting of $implicit is a special case. Since multiple elements will all export // the different values as $implicit, directly assign $implicit bindings to the variable @@ -167,30 +157,32 @@ export class View { if (elementInjector.isExportingComponent()) { this.locals.set(exportImplicitName, elementInjector.getComponent()); } else if (elementInjector.isExportingElement()) { - this.locals.set(exportImplicitName, elementInjector.getNgElement().domElement); + this.locals.set(exportImplicitName, elementInjector.getNgElement()); } } if (isPresent(binders[i].nestedProtoView) && isPresent(componentDirective)) { - this.componentChildViews[componentChildViewIndex++].hydrate(shadowDomAppInjector, - elementInjector, this.lightDoms[i], elementInjector.getComponent(), null); - } - } - - for (var i = 0; i < this.lightDoms.length; ++i) { - var lightDom = this.lightDoms[i]; - if (isPresent(lightDom)) { - lightDom.redistribute(); + renderComponentIndex = this.componentChildViews[componentChildViewIndex].internalHydrateRecurse( + renderComponentViewRefs, + renderComponentIndex, + shadowDomAppInjector, + elementInjector, + elementInjector.getComponent(), + null + ); + componentChildViewIndex++; } } + this.proto.renderer.setEventDispatcher(this.render, this); + return renderComponentIndex; } - dehydrate() { + internalDehydrateRecurse() { // Note: preserve the opposite order of the hydration process. // componentChildViews for (var i = 0; i < this.componentChildViews.length; i++) { - this.componentChildViews[i].dehydrate(); + this.componentChildViews[i].internalDehydrateRecurse(); } // elementInjectors @@ -205,11 +197,13 @@ export class View { for (var i = 0; i < this.viewContainers.length; i++) { var vc = this.viewContainers[i]; if (isPresent(vc)) { - vc.dehydrate(); + vc.internalDehydrateRecurse(); } } } + this.render = null; + this._dehydrateContext(); } @@ -223,11 +217,9 @@ export class View { * @param {int} binderIndex */ triggerEventHandlers(eventName: string, eventObj, binderIndex: int) { - var handlers = this.proto.eventHandlers[binderIndex]; - if (isBlank(handlers)) return; - var handler = StringMapWrapper.get(handlers, eventName); - if (isBlank(handler)) return; - handler(eventObj, this); + var locals = MapWrapper.create(); + MapWrapper.set(locals, '$event', eventObj); + this.dispatchEvent(binderIndex, eventName, locals); } onAllChangesDone(directiveMemento:DirectiveMemento) { @@ -245,15 +237,39 @@ export class View { if (memento instanceof DirectiveBindingMemento) { var directiveMemento:DirectiveBindingMemento = memento; directiveMemento.invoke(currentValue, this.elementInjectors); - } else if (memento instanceof ElementBindingMemento) { var elementMemento:ElementBindingMemento = memento; - elementMemento.invoke(currentValue, this.bindElements); - + this.proto.renderer.setElementProperty( + this.render, elementMemento.elementIndex, elementMemento.propertyName, currentValue + ); } else { // we know it refers to _textNodes. var textNodeIndex:number = memento; - DOM.setText(this.textNodes[textNodeIndex], currentValue); + this.proto.renderer.setText(this.render, textNodeIndex, currentValue); + } + } + + // implementation of EventDispatcher#dispatchEvent + dispatchEvent( + elementIndex:number, eventName:string, locals:Map + ):void { + // Most of the time the event will be fired only when the view is in the live document. + // However, in a rare circumstance the view might get dehydrated, in between the event + // queuing up and firing. + if (this.hydrated()) { + var elBinder = this.proto.elementBinders[elementIndex]; + if (isBlank(elBinder.events)) return; + var eventMap = elBinder.events[eventName]; + if (isBlank(eventName)) return; + MapWrapper.forEach(eventMap, (expr, directiveIndex) => { + var context; + if (directiveIndex === -1) { + context = this.context; + } else { + context = this.elementInjectors[elementIndex].getDirectiveAtIndex(directiveIndex); + } + expr.eval(context, new Locals(this.locals, locals)); + }); } } } @@ -263,21 +279,11 @@ export class View { * @publicModule angular2/template */ export class ProtoView { - element; elementBinders:List; protoChangeDetector:ProtoChangeDetector; variableBindings: Map; protoLocals:Map; textNodesWithBindingCount:int; - elementsWithBindingCount:int; - instantiateInPlace:boolean; - rootBindingOffset:int; - isTemplateElement:boolean; - shadowDomStrategy: ShadowDomStrategy; - _viewPool: ViewPool; - stylePromises: List; - // List>, indexed by binder index - eventHandlers:List; bindingRecords:List; parentProtoView:ProtoView; _variableBindings:List; @@ -285,58 +291,36 @@ export class ProtoView { _directiveMementosMap:Map; _directiveMementos:List; render:renderApi.ProtoViewRef; + renderer:renderApi.Renderer; constructor( + renderer:renderApi.Renderer, render:renderApi.ProtoViewRef, - template, - protoChangeDetector:ProtoChangeDetector, - shadowDomStrategy:ShadowDomStrategy, parentProtoView:ProtoView = null) { + protoChangeDetector:ProtoChangeDetector) { + this.renderer = renderer; this.render = render; - this.element = template; this.elementBinders = []; this.variableBindings = MapWrapper.create(); this.protoLocals = MapWrapper.create(); this.protoChangeDetector = protoChangeDetector; - this.parentProtoView = parentProtoView; + this.parentProtoView = null; this.textNodesWithBindingCount = 0; - this.elementsWithBindingCount = 0; - this.instantiateInPlace = false; - this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) - ? 1 : 0; - this.isTemplateElement = DOM.isTemplateElement(this.element); - this.shadowDomStrategy = shadowDomStrategy; - this._viewPool = new ViewPool(VIEW_POOL_CAPACITY); - this.stylePromises = []; - this.eventHandlers = []; this.bindingRecords = []; this._directiveMementosMap = MapWrapper.create(); this._variableBindings = null; this._directiveMementos = null; } - // TODO(rado): hostElementInjector should be moved to hydrate phase. - instantiate(hostElementInjector: ElementInjector, eventManager: EventManager):View { - if (this._viewPool.length() == 0) this._preFillPool(hostElementInjector, eventManager); - var view = this._viewPool.pop(); - return isPresent(view) ? view : this._instantiate(hostElementInjector, eventManager); - } - - _preFillPool(hostElementInjector: ElementInjector, eventManager: EventManager) { - for (var i = 0; i < VIEW_POOL_PREFILL; i++) { - this._viewPool.push(this._instantiate(hostElementInjector, eventManager)); - } - } - //TODO: Tobias or Victor. Moving it into the constructor. // this work should be done the constructor of ProtoView once we separate // ProtoView and ProtoViewBuilder - _getVariableBindings() { + getVariableBindings() { if (isPresent(this._variableBindings)) { return this._variableBindings; } this._variableBindings = isPresent(this.parentProtoView) ? - ListWrapper.clone(this.parentProtoView._getVariableBindings()) : []; + ListWrapper.clone(this.parentProtoView.getVariableBindings()) : []; MapWrapper.forEach(this.protoLocals, (v, local) => { ListWrapper.push(this._variableBindings, local); @@ -348,7 +332,7 @@ export class ProtoView { //TODO: Tobias or Victor. Moving it into the constructor. // this work should be done the constructor of ProtoView once we separate // ProtoView and ProtoViewBuilder - _getDirectiveMementos() { + getDirectiveMementos() { if (isPresent(this._directiveMementos)) { return this._directiveMementos; } @@ -367,185 +351,6 @@ export class ProtoView { return this._directiveMementos; } - _instantiate(hostElementInjector: ElementInjector, eventManager: EventManager): View { - var rootElementClone = this.instantiateInPlace ? this.element : DOM.importIntoDoc(this.element); - var elementsWithBindingsDynamic; - if (this.isTemplateElement) { - elementsWithBindingsDynamic = DOM.querySelectorAll(DOM.content(rootElementClone), NG_BINDING_CLASS_SELECTOR); - } else { - elementsWithBindingsDynamic= DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS); - } - - var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length); - for (var binderIdx = 0; binderIdx < elementsWithBindingsDynamic.length; ++binderIdx) { - elementsWithBindings[binderIdx] = elementsWithBindingsDynamic[binderIdx]; - } - - var viewNodes; - if (this.isTemplateElement) { - var childNode = DOM.firstChild(DOM.content(rootElementClone)); - viewNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in ProtoView - // Note: An explicit loop is the fastest way to convert a DOM array into a JS array! - while(childNode != null) { - ListWrapper.push(viewNodes, childNode); - childNode = DOM.nextSibling(childNode); - } - } else { - viewNodes = [rootElementClone]; - } - - var view = new View(this, viewNodes, this.protoLocals); - var changeDetector = this.protoChangeDetector.instantiate(view, this.bindingRecords, - this._getVariableBindings(), this._getDirectiveMementos()); - - var binders = this.elementBinders; - var elementInjectors = ListWrapper.createFixedSize(binders.length); - var eventHandlers = ListWrapper.createFixedSize(binders.length); - var rootElementInjectors = []; - var textNodes = []; - var elementsWithPropertyBindings = []; - var preBuiltObjects = ListWrapper.createFixedSize(binders.length); - var viewContainers = ListWrapper.createFixedSize(binders.length); - var contentTags = ListWrapper.createFixedSize(binders.length); - var componentChildViews = []; - var lightDoms = ListWrapper.createFixedSize(binders.length); - - for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) { - var binder = binders[binderIdx]; - var element; - if (binderIdx === 0 && this.rootBindingOffset === 1) { - element = rootElementClone; - } else { - element = elementsWithBindings[binderIdx - this.rootBindingOffset]; - } - var elementInjector = null; - - // elementInjectors and rootElementInjectors - var protoElementInjector = binder.protoElementInjector; - if (isPresent(protoElementInjector)) { - if (isPresent(protoElementInjector.parent)) { - var parentElementInjector = elementInjectors[protoElementInjector.parent.index]; - elementInjector = protoElementInjector.instantiate(parentElementInjector, null); - } else { - elementInjector = protoElementInjector.instantiate(null, hostElementInjector); - ListWrapper.push(rootElementInjectors, elementInjector); - } - } - elementInjectors[binderIdx] = elementInjector; - - if (binder.hasElementPropertyBindings) { - ListWrapper.push(elementsWithPropertyBindings, element); - } - - // textNodes - var textNodeIndices = binder.textNodeIndices; - if (isPresent(textNodeIndices)) { - var childNode = DOM.firstChild(DOM.templateAwareRoot(element)); - for (var j = 0, k = 0; j < textNodeIndices.length; j++) { - for(var index = textNodeIndices[j]; k < index; k++) { - childNode = DOM.nextSibling(childNode); - } - ListWrapper.push(textNodes, childNode); - } - } - - // componentChildViews - var lightDom = null; - var bindingPropagationConfig = null; - if (isPresent(binder.nestedProtoView) && isPresent(binder.componentDirective)) { - var strategy = this.shadowDomStrategy; - var childView = binder.nestedProtoView.instantiate(elementInjector, eventManager); - changeDetector.addChild(childView.changeDetector); - - lightDom = strategy.constructLightDom(view, childView, element); - strategy.attachTemplate(element, childView); - - bindingPropagationConfig = new BindingPropagationConfig(childView.changeDetector); - - ListWrapper.push(componentChildViews, childView); - } - lightDoms[binderIdx] = lightDom; - - var destLightDom = null; - if (isPresent(binder.parent) && binder.distanceToParent === 1) { - destLightDom = lightDoms[binder.parent.index]; - } - - // viewContainers - var viewContainer = null; - if (isPresent(binder.viewportDirective)) { - viewContainer = new ViewContainer(view, element, binder.nestedProtoView, elementInjector, - eventManager, destLightDom); - } - viewContainers[binderIdx] = viewContainer; - - // contentTags - var contentTag = null; - if (isPresent(binder.contentTagSelector)) { - contentTag = new Content(destLightDom, element, binder.contentTagSelector); - } - contentTags[binderIdx] = contentTag; - - // preBuiltObjects - if (isPresent(elementInjector)) { - preBuiltObjects[binderIdx] = new PreBuiltObjects(view, new NgElement(element), viewContainer, - bindingPropagationConfig); - } - - // events - if (isPresent(binder.events)) { - eventHandlers[binderIdx] = StringMapWrapper.create(); - StringMapWrapper.forEach(binder.events, (eventMap, eventName) => { - var handler = ProtoView.buildEventHandler(eventMap, binderIdx); - StringMapWrapper.set(eventHandlers[binderIdx], eventName, handler); - if (isBlank(elementInjector) || !elementInjector.hasEventEmitter(eventName)) { - eventManager.addEventListener(element, eventName, - (event) => { handler(event, view); }); - } - }); - } - } - - this.eventHandlers = eventHandlers; - - view.init(changeDetector, elementInjectors, rootElementInjectors, textNodes, elementsWithPropertyBindings, - viewContainers, contentTags, preBuiltObjects, componentChildViews, lightDoms); - - return view; - } - - returnToPool(view: View) { - this._viewPool.push(view); - } - - /** - * Creates an event handler. - * - * @param {Map} eventMap Map directiveIndexes to expressions - * @param {int} injectorIdx - * @returns {Function} - */ - static buildEventHandler(eventMap: Map, injectorIdx: int) { - var locals = MapWrapper.create(); - return (event, view) => { - // Most of the time the event will be fired only when the view is in the live document. - // However, in a rare circumstance the view might get dehydrated, in between the event - // queuing up and firing. - if (view.hydrated()) { - MapWrapper.set(locals, '$event', event); - MapWrapper.forEach(eventMap, (expr, directiveIndex) => { - var context; - if (directiveIndex === -1) { - context = view.context; - } else { - context = view.elementInjectors[injectorIdx].getDirectiveAtIndex(directiveIndex); - } - expr.eval(context, new Locals(view.locals, locals)); - }); - } - } - } - bindVariable(contextName:string, templateName:string) { MapWrapper.set(this.variableBindings, contextName, templateName); MapWrapper.set(this.protoLocals, templateName, null); @@ -562,12 +367,7 @@ export class ProtoView { /** * Adds a text node binding for the last created ElementBinder via bindElement */ - bindTextNode(indexInParent:int, expression:AST) { - var elBinder = this.elementBinders[this.elementBinders.length-1]; - if (isBlank(elBinder.textNodeIndices)) { - elBinder.textNodeIndices = ListWrapper.create(); - } - ListWrapper.push(elBinder.textNodeIndices, indexInParent); + bindTextNode(expression:AST) { var memento = this.textNodesWithBindingCount++; ListWrapper.push(this.bindingRecords, new BindingRecord(expression, memento, null)); } @@ -575,13 +375,8 @@ export class ProtoView { /** * Adds an element property binding for the last created ElementBinder via bindElement */ - bindElementProperty(expression:AST, setterName:string, setter:SetterFn) { - var elBinder = this.elementBinders[this.elementBinders.length-1]; - if (!elBinder.hasElementPropertyBindings) { - elBinder.hasElementPropertyBindings = true; - this.elementsWithBindingCount++; - } - var memento = new ElementBindingMemento(this.elementsWithBindingCount-1, setter); + bindElementProperty(expression:AST, setterName:string) { + var memento = new ElementBindingMemento(this.elementBinders.length-1, setterName); ListWrapper.push(this.bindingRecords, new BindingRecord(expression, memento, null)); } @@ -651,16 +446,12 @@ export class ProtoView { /** */ export class ElementBindingMemento { - _elementIndex:int; - _setter:SetterFn; - constructor(elementIndex:int, setter:SetterFn) { - this._elementIndex = elementIndex; - this._setter = setter; - } + elementIndex:int; + propertyName:string; - invoke(currentValue:any, bindElements:List) { - var element = bindElements[this._elementIndex]; - this._setter(element, currentValue); + constructor(elementIndex:int, propertyName:string) { + this.elementIndex = elementIndex; + this.propertyName = propertyName; } } diff --git a/modules/angular2/src/core/compiler/view_container.js b/modules/angular2/src/core/compiler/view_container.js index 3d49624973..3d11a85c7f 100644 --- a/modules/angular2/src/core/compiler/view_container.js +++ b/modules/angular2/src/core/compiler/view_container.js @@ -1,59 +1,64 @@ -import * as viewModule from './view'; -import {DOM} from 'angular2/src/dom/dom_adapter'; import {ListWrapper, MapWrapper, List} from 'angular2/src/facade/collection'; import {BaseException} from 'angular2/src/facade/lang'; import {Injector} from 'angular2/di'; import * as eiModule from 'angular2/src/core/compiler/element_injector'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; -import {EventManager} from 'angular2/src/render/dom/events/event_manager'; -import {LightDom} from './shadow_dom_emulation/light_dom'; + +import * as renderApi from 'angular2/src/render/api'; +import * as viewModule from './view'; +import * as vfModule from './view_factory'; /** * @publicModule angular2/template */ export class ViewContainer { + render:renderApi.ViewContainerRef; + viewFactory: vfModule.ViewFactory; parentView: viewModule.View; - templateElement; defaultProtoView: viewModule.ProtoView; _views: List; - _lightDom: LightDom; - _eventManager: EventManager; elementInjector: eiModule.ElementInjector; appInjector: Injector; hostElementInjector: eiModule.ElementInjector; - hostLightDom: LightDom; - constructor(parentView: viewModule.View, - templateElement, + constructor(viewFactory:vfModule.ViewFactory, + parentView: viewModule.View, defaultProtoView: viewModule.ProtoView, - elementInjector: eiModule.ElementInjector, - eventManager: EventManager, - lightDom = null) { + elementInjector: eiModule.ElementInjector) { + this.viewFactory = viewFactory; + this.render = null; this.parentView = parentView; - this.templateElement = templateElement; this.defaultProtoView = defaultProtoView; this.elementInjector = elementInjector; - this._lightDom = lightDom; // The order in this list matches the DOM order. this._views = []; this.appInjector = null; this.hostElementInjector = null; - this.hostLightDom = null; - this._eventManager = eventManager; } - hydrate(appInjector: Injector, hostElementInjector: eiModule.ElementInjector, hostLightDom: LightDom) { + internalHydrateRecurse(render:renderApi.ViewContainerRef, appInjector: Injector, hostElementInjector: eiModule.ElementInjector) { + this.render = render; this.appInjector = appInjector; this.hostElementInjector = hostElementInjector; - this.hostLightDom = hostLightDom; } - dehydrate() { + internalDehydrateRecurse() { this.appInjector = null; this.hostElementInjector = null; - this.hostLightDom = null; - this.clear(); + this.render = null; + // Note: We don't call clear here, + // as we don't want to change the render side + // (i.e. don't deattach views on the render side), + // as the render side does its own recursion. + for (var i = this._views.length - 1; i >= 0; i--) { + var view = this._views[i]; + view.changeDetector.remove(); + this._unlinkElementInjectors(view); + view.internalDehydrateRecurse(); + this.viewFactory.returnView(view); + } + this._views = []; } clear() { @@ -70,11 +75,6 @@ export class ViewContainer { return this._views.length; } - _siblingToInsertAfter(index: number) { - if (index == 0) return this.templateElement; - return ListWrapper.last(this._views[index - 1].nodes); - } - hydrated() { return isPresent(this.appInjector); } @@ -84,28 +84,27 @@ export class ViewContainer { create(atIndex=-1): viewModule.View { if (!this.hydrated()) throw new BaseException( 'Cannot create views on a dehydrated ViewContainer'); - // TODO(rado): replace with viewFactory. - var newView = this.defaultProtoView.instantiate(this.hostElementInjector, this._eventManager); + var newView = this.viewFactory.getView(this.defaultProtoView); // insertion must come before hydration so that element injector trees are attached. - this.insert(newView, atIndex); - newView.hydrate(this.appInjector, this.hostElementInjector, this.hostLightDom, + this._insertWithoutRender(newView, atIndex); + // hydration must come before changing the render side, + // as it acquires the render views. + newView.hydrate(this.appInjector, this.hostElementInjector, this.parentView.context, this.parentView.locals); + this.defaultProtoView.renderer.insertViewIntoContainer(this.render, newView.render, atIndex); - // new content tags might have appeared, we need to redistrubute. - if (isPresent(this.hostLightDom)) { - this.hostLightDom.redistribute(); - } return newView; } insert(view, atIndex=-1): viewModule.View { + this._insertWithoutRender(view, atIndex); + this.defaultProtoView.renderer.insertViewIntoContainer(this.render, view.render, atIndex); + return view; + } + + _insertWithoutRender(view, atIndex=-1): viewModule.View { if (atIndex == -1) atIndex = this._views.length; ListWrapper.insert(this._views, atIndex, view); - if (isBlank(this._lightDom)) { - ViewContainer.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view); - } else { - this._lightDom.redistribute(); - } this.parentView.changeDetector.addChild(view.changeDetector); this._linkElementInjectors(view); @@ -116,8 +115,7 @@ export class ViewContainer { if (atIndex == -1) atIndex = this._views.length - 1; var view = this.detach(atIndex); view.dehydrate(); - // TODO(rado): this needs to be delayed until after any pending animations. - this.defaultProtoView.returnToPool(view); + this.viewFactory.returnView(view); // view is intentionally not returned to the client. } @@ -129,32 +127,12 @@ export class ViewContainer { if (atIndex == -1) atIndex = this._views.length - 1; var detachedView = this.get(atIndex); ListWrapper.removeAt(this._views, atIndex); - if (isBlank(this._lightDom)) { - ViewContainer.removeViewNodes(detachedView); - } else { - this._lightDom.redistribute(); - } - // content tags might have disappeared we need to do redistribution. - if (isPresent(this.hostLightDom)) { - this.hostLightDom.redistribute(); - } + this.defaultProtoView.renderer.detachViewFromContainer(this.render, atIndex); detachedView.changeDetector.remove(); this._unlinkElementInjectors(detachedView); return detachedView; } - contentTagContainers() { - return this._views; - } - - nodes():List { - var r = []; - for (var i = 0; i < this._views.length; ++i) { - r = ListWrapper.concat(r, this._views[i].nodes); - } - return r; - } - _linkElementInjectors(view) { for (var i = 0; i < view.rootElementInjectors.length; ++i) { view.rootElementInjectors[i].parent = this.elementInjector; @@ -166,19 +144,4 @@ export class ViewContainer { view.rootElementInjectors[i].parent = null; } } - - static moveViewNodesAfterSibling(sibling, view) { - for (var i = view.nodes.length - 1; i >= 0; --i) { - DOM.insertAfter(sibling, view.nodes[i]); - } - } - - static removeViewNodes(view) { - var len = view.nodes.length; - if (len == 0) return; - var parent = view.nodes[0].parentNode; - for (var i = len - 1; i >= 0; --i) { - DOM.removeChild(parent, view.nodes[i]); - } - } } diff --git a/modules/angular2/src/core/compiler/view_factory.js b/modules/angular2/src/core/compiler/view_factory.js new file mode 100644 index 0000000000..083d7b02b8 --- /dev/null +++ b/modules/angular2/src/core/compiler/view_factory.js @@ -0,0 +1,109 @@ +import {Injectable, Inject, OpaqueToken} from 'angular2/di'; +import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; +import * as eli from './element_injector'; +import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; +import {NgElement} from 'angular2/src/core/compiler/ng_element'; +import * as vcModule from './view_container'; +import * as viewModule from './view'; +import {BindingPropagationConfig} from 'angular2/change_detection'; + +// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this! +export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity'; + +@Injectable() +export class ViewFactory { + _poolCapacity:number; + _pooledViews:List; + + constructor(@Inject(VIEW_POOL_CAPACITY) capacity) { + this._poolCapacity = capacity; + this._pooledViews = ListWrapper.create(); + } + + getView(protoView:viewModule.ProtoView):viewModule.View { + // TODO(tbosch): benchmark this scanning of views and maybe + // replace it with a fancy LRU Map/List combination... + var view; + for (var i=this._pooledViews.length-1; i>=0; i--) { + var pooledView = this._pooledViews[i]; + if (pooledView.proto === protoView) { + view = ListWrapper.removeAt(this._pooledViews, i); + } + } + if (isBlank(view)) { + view = this._createView(protoView); + } + return view; + } + + returnView(view:viewModule.View) { + if (view.hydrated()) { + throw new BaseException('Only dehydrated Views can be put back into the pool!'); + } + ListWrapper.push(this._pooledViews, view); + while (this._pooledViews.length > this._poolCapacity) { + ListWrapper.removeAt(this._pooledViews, 0); + } + } + + _createView(protoView:viewModule.ProtoView): viewModule.View { + var view = new viewModule.View(protoView, protoView.protoLocals); + var changeDetector = protoView.protoChangeDetector.instantiate(view, protoView.bindingRecords, + protoView.getVariableBindings(), protoView.getDirectiveMementos()); + + var binders = protoView.elementBinders; + var elementInjectors = ListWrapper.createFixedSize(binders.length); + var rootElementInjectors = []; + var preBuiltObjects = ListWrapper.createFixedSize(binders.length); + var viewContainers = ListWrapper.createFixedSize(binders.length); + var componentChildViews = []; + + for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) { + var binder = binders[binderIdx]; + var elementInjector = null; + + // elementInjectors and rootElementInjectors + var protoElementInjector = binder.protoElementInjector; + if (isPresent(protoElementInjector)) { + if (isPresent(protoElementInjector.parent)) { + var parentElementInjector = elementInjectors[protoElementInjector.parent.index]; + elementInjector = protoElementInjector.instantiate(parentElementInjector); + } else { + elementInjector = protoElementInjector.instantiate(null); + ListWrapper.push(rootElementInjectors, elementInjector); + } + } + elementInjectors[binderIdx] = elementInjector; + + // componentChildViews + var bindingPropagationConfig = null; + if (isPresent(binder.nestedProtoView) && isPresent(binder.componentDirective)) { + var childView = this._createView(binder.nestedProtoView); + changeDetector.addChild(childView.changeDetector); + + bindingPropagationConfig = new BindingPropagationConfig(childView.changeDetector); + + ListWrapper.push(componentChildViews, childView); + } + + // viewContainers + var viewContainer = null; + if (isPresent(binder.viewportDirective)) { + viewContainer = new vcModule.ViewContainer(this, view, binder.nestedProtoView, elementInjector); + } + viewContainers[binderIdx] = viewContainer; + + // preBuiltObjects + if (isPresent(elementInjector)) { + preBuiltObjects[binderIdx] = new eli.PreBuiltObjects(view, new NgElement(view, binderIdx), viewContainer, + bindingPropagationConfig); + } + } + + view.init(changeDetector, elementInjectors, rootElementInjectors, + viewContainers, preBuiltObjects, componentChildViews); + + return view; + } + +} \ No newline at end of file diff --git a/modules/angular2/src/core/compiler/view_pool.js b/modules/angular2/src/core/compiler/view_pool.js deleted file mode 100644 index ef6d42d67e..0000000000 --- a/modules/angular2/src/core/compiler/view_pool.js +++ /dev/null @@ -1,26 +0,0 @@ -import {ListWrapper, MapWrapper, StringMapWrapper, List} from 'angular2/src/facade/collection'; -import * as viewModule from './view'; - -export class ViewPool { - _views: List; - _capacity: number; - constructor(capacity: number) { - this._views = []; - this._capacity = capacity; - } - - pop(): viewModule.View { - return ListWrapper.isEmpty(this._views) ? null : ListWrapper.removeLast(this._views); - } - - push(view: viewModule.View) { - if (this._views.length < this._capacity) { - ListWrapper.push(this._views, view); - } - } - - length() { - return this._views.length; - } -} - diff --git a/modules/angular2/src/core/dom/element.js b/modules/angular2/src/core/dom/element.js deleted file mode 100644 index bb9c98fb6b..0000000000 --- a/modules/angular2/src/core/dom/element.js +++ /dev/null @@ -1,16 +0,0 @@ -import {DOM} from 'angular2/src/dom/dom_adapter'; -import {normalizeBlank} from 'angular2/src/facade/lang'; - -/** - * @publicModule angular2/angular2 - */ -export class NgElement { - domElement; - constructor(domElement) { - this.domElement = domElement; - } - - getAttribute(name:string) { - return normalizeBlank(DOM.getAttribute(this.domElement, name)); - } -} \ No newline at end of file diff --git a/modules/angular2/src/directives/class.js b/modules/angular2/src/directives/class.js index 69d8375cac..9a986ae904 100644 --- a/modules/angular2/src/directives/class.js +++ b/modules/angular2/src/directives/class.js @@ -1,7 +1,7 @@ import {Decorator} from 'angular2/src/core/annotations/annotations'; import {isPresent} from 'angular2/src/facade/lang'; import {DOM} from 'angular2/src/dom/dom_adapter'; -import {NgElement} from 'angular2/src/core/dom/element'; +import {NgElement} from 'angular2/src/core/compiler/ng_element'; @Decorator({ selector: '[class]', diff --git a/modules/angular2/src/directives/switch.js b/modules/angular2/src/directives/switch.js index b71d163c1e..56fdca4850 100644 --- a/modules/angular2/src/directives/switch.js +++ b/modules/angular2/src/directives/switch.js @@ -1,6 +1,5 @@ import {Decorator, Viewport} from 'angular2/src/core/annotations/annotations'; import {ViewContainer} from 'angular2/src/core/compiler/view_container'; -import {NgElement} from 'angular2/src/core/dom/element'; import {isPresent, isBlank, normalizeBlank} from 'angular2/src/facade/lang'; import {ListWrapper, List, MapWrapper, Map} from 'angular2/src/facade/collection'; import {Parent} from 'angular2/src/core/annotations/visibility'; @@ -156,7 +155,7 @@ export class SwitchWhen { _switch: Switch; _viewContainer: ViewContainer; - constructor(el: NgElement, viewContainer: ViewContainer, @Parent() sswitch: Switch) { + constructor(viewContainer: ViewContainer, @Parent() sswitch: Switch) { // `_whenDefault` is used as a marker for a not yet initialized value this._value = _whenDefault; this._switch = sswitch; diff --git a/modules/angular2/src/render/api.js b/modules/angular2/src/render/api.js index 2315bb5dcb..03e556d98c 100644 --- a/modules/angular2/src/render/api.js +++ b/modules/angular2/src/render/api.js @@ -203,7 +203,7 @@ export class Renderer { * Sets the dispatcher for all events that have been defined in the template or in directives * in the given view. */ - setEventDispatcher(viewRef:ViewRef, dispatcher:EventDispatcher):void {} + setEventDispatcher(viewRef:ViewRef, dispatcher:any/*EventDispatcher*/):void {} /** * To be called at the end of the VmTurn so the API can buffer calls @@ -218,10 +218,10 @@ export class Renderer { export class EventDispatcher { /** * Called when an event was triggered for a on-* attribute on an element. - * @param {List} locals Locals to be used to evaluate the + * @param {Map} locals Locals to be used to evaluate the * event expressions */ dispatchEvent( - elementIndex:number, eventName:string, locals:List + elementIndex:number, eventName:string, locals:Map ):void {} } diff --git a/modules/angular2/src/render/dom/compiler/compiler.js b/modules/angular2/src/render/dom/compiler/compiler.js index 628c7a30ce..5d33843351 100644 --- a/modules/angular2/src/render/dom/compiler/compiler.js +++ b/modules/angular2/src/render/dom/compiler/compiler.js @@ -1,10 +1,14 @@ +import {Injectable} from 'angular2/di'; + import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import {BaseException} from 'angular2/src/facade/lang'; import {Template, ProtoView} from '../../api'; import {CompilePipeline} from './compile_pipeline'; import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; -import {CompileStepFactory} from './compile_step_factory'; +import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory'; +import {Parser} from 'angular2/change_detection'; +import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy'; /** * The compiler loads and translates the html templates of components into @@ -43,4 +47,11 @@ export class Compiler { return PromiseWrapper.resolve(protoView); } } +} + +@Injectable() +export class DefaultCompiler extends Compiler { + constructor(parser:Parser, shadowDomStrategy:ShadowDomStrategy, templateLoader: TemplateLoader) { + super(new DefaultStepFactory(parser, shadowDomStrategy), templateLoader); + } } \ No newline at end of file diff --git a/modules/angular2/src/render/dom/direct_dom_renderer.js b/modules/angular2/src/render/dom/direct_dom_renderer.js index fe2dbe8f84..4f0c963626 100644 --- a/modules/angular2/src/render/dom/direct_dom_renderer.js +++ b/modules/angular2/src/render/dom/direct_dom_renderer.js @@ -1,3 +1,4 @@ +import {Injectable} from 'angular2/di'; import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; import {List, ListWrapper} from 'angular2/src/facade/collection'; import {isBlank, isPresent} from 'angular2/src/facade/lang'; @@ -14,7 +15,7 @@ function _resolveViewContainer(vc:api.ViewContainerRef) { return _resolveView(vc.view).viewContainers[vc.elementIndex]; } -function _resolveView(viewRef:_DirectDomViewRef) { +function _resolveView(viewRef:DirectDomViewRef) { return isPresent(viewRef) ? viewRef.delegate : null; } @@ -23,7 +24,7 @@ function _resolveProtoView(protoViewRef:DirectDomProtoViewRef) { } function _wrapView(view:View) { - return new _DirectDomViewRef(view); + return new DirectDomViewRef(view); } function _collectComponentChildViewRefs(view, target = null) { @@ -51,7 +52,7 @@ export class DirectDomProtoViewRef extends api.ProtoViewRef { } } -class _DirectDomViewRef extends api.ViewRef { +export class DirectDomViewRef extends api.ViewRef { delegate:View; constructor(delegate:View) { @@ -60,6 +61,7 @@ class _DirectDomViewRef extends api.ViewRef { } } +@Injectable() export class DirectDomRenderer extends api.Renderer { _compiler: Compiler; _viewFactory: ViewFactory; @@ -131,7 +133,7 @@ export class DirectDomRenderer extends api.Renderer { _resolveView(viewRef).setText(textNodeIndex, text); } - setEventDispatcher(viewRef:api.ViewRef, dispatcher:api.EventDispatcher) { + setEventDispatcher(viewRef:api.ViewRef, dispatcher:any/*api.EventDispatcher*/):void { _resolveView(viewRef).setEventDispatcher(dispatcher); } } diff --git a/modules/angular2/src/render/dom/view/proto_view_builder.js b/modules/angular2/src/render/dom/view/proto_view_builder.js index 37fa03ddd0..7e3e20810a 100644 --- a/modules/angular2/src/render/dom/view/proto_view_builder.js +++ b/modules/angular2/src/render/dom/view/proto_view_builder.js @@ -242,19 +242,19 @@ export class EventLocalsAstSplitter extends AstTransformer { } splitEventAstIntoLocals(eventBindings:Map):Map { - // TODO(tbosch): reenable this when we are using - // the render views - return eventBindings; - // if (isPresent(eventBindings)) { - // var result = MapWrapper.create(); - // MapWrapper.forEach(eventBindings, (astWithSource, eventName) => { - // var adjustedAst = astWithSource.ast.visit(this); - // MapWrapper.set(result, eventName, new ASTWithSource(adjustedAst, astWithSource.source, '')); - // ListWrapper.push(this.eventNames, eventName); - // }); - // return result; - // } - // return null; + if (isPresent(eventBindings)) { + var result = MapWrapper.create(); + MapWrapper.forEach(eventBindings, (astWithSource, eventName) => { + // TODO(tbosch): reenable this when we are parsing element properties + // out of action expressions + // var adjustedAst = astWithSource.ast.visit(this); + var adjustedAst = astWithSource.ast; + MapWrapper.set(result, eventName, new ASTWithSource(adjustedAst, astWithSource.source, '')); + ListWrapper.push(this.eventNames, eventName); + }); + return result; + } + return null; } visitAccessMember(ast:AccessMember) { diff --git a/modules/angular2/src/render/dom/view/view.js b/modules/angular2/src/render/dom/view/view.js index b486332840..4e8a2a435e 100644 --- a/modules/angular2/src/render/dom/view/view.js +++ b/modules/angular2/src/render/dom/view/view.js @@ -1,7 +1,6 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; -import {Locals} from 'angular2/change_detection'; import {ViewContainer} from './view_container'; import {ProtoView} from './proto_view'; @@ -10,7 +9,7 @@ import {Content} from '../shadow_dom/content_tag'; import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy'; -import {EventDispatcher} from '../../api'; +// import {EventDispatcher} from '../../api'; const NG_BINDING_CLASS = 'ng-binding'; @@ -31,7 +30,7 @@ export class View { lightDoms: List; proto: ProtoView; _hydrated: boolean; - _eventDispatcher: EventDispatcher; + _eventDispatcher: any/*EventDispatcher*/; constructor( proto:ProtoView, rootNodes:List, @@ -43,6 +42,7 @@ export class View { this.viewContainers = viewContainers; this.contentTags = contentTags; this.lightDoms = ListWrapper.createFixedSize(boundElements.length); + ListWrapper.fill(this.lightDoms, null); this.componentChildViews = ListWrapper.createFixedSize(boundElements.length); this._hydrated = false; } @@ -134,7 +134,10 @@ export class View { // componentChildViews for (var i = 0; i < this.componentChildViews.length; i++) { - this.componentChildViews[i].dehydrate(); + var cv = this.componentChildViews[i]; + if (isPresent(cv)) { + cv.dehydrate(); + } } // viewContainers and content tags @@ -150,10 +153,11 @@ export class View { } } } + this._eventDispatcher = null; this._hydrated = false; } - setEventDispatcher(dispatcher:EventDispatcher) { + setEventDispatcher(dispatcher:any/*EventDispatcher*/) { this._eventDispatcher = dispatcher; } @@ -161,8 +165,11 @@ export class View { if (isPresent(this._eventDispatcher)) { var evalLocals = MapWrapper.create(); MapWrapper.set(evalLocals, '$event', event); - var localValues = this.proto.elementBinders[elementIndex].eventLocals.eval(null, new Locals(null, evalLocals)); - this._eventDispatcher.dispatchEvent(elementIndex, eventName, localValues); + // TODO(tbosch): reenable this when we are parsing element properties + // out of action expressions + // var localValues = this.proto.elementBinders[elementIndex].eventLocals.eval(null, new Locals(null, evalLocals)); + // this._eventDispatcher.dispatchEvent(elementIndex, eventName, localValues); + this._eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals); } } } diff --git a/modules/angular2/src/render/dom/view/view_container.js b/modules/angular2/src/render/dom/view/view_container.js index 4df3216085..570fceb1c2 100644 --- a/modules/angular2/src/render/dom/view/view_container.js +++ b/modules/angular2/src/render/dom/view/view_container.js @@ -35,7 +35,7 @@ export class ViewContainer { if (isBlank(this._lightDom)) { for (var i = this._views.length - 1; i >= 0; i--) { var view = this._views[i]; - ViewContainer.removeViewNodesFromParent(this.templateElement.parentNode, view); + ViewContainer.removeViewNodes(view); this._viewFactory.returnView(view); } this._views = []; @@ -72,12 +72,11 @@ export class ViewContainer { } insert(view, atIndex=-1): viewModule.View { - this._checkHydrated(); - if (atIndex == -1) atIndex = this._views.length; - ListWrapper.insert(this._views, atIndex, view); if (!view.hydrated()) { view.hydrate(this._hostLightDom); } + if (atIndex == -1) atIndex = this._views.length; + ListWrapper.insert(this._views, atIndex, view); if (isBlank(this._lightDom)) { ViewContainer.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view); @@ -100,7 +99,7 @@ export class ViewContainer { var detachedView = this.get(atIndex); ListWrapper.removeAt(this._views, atIndex); if (isBlank(this._lightDom)) { - ViewContainer.removeViewNodesFromParent(this.templateElement.parentNode, detachedView); + ViewContainer.removeViewNodes(detachedView); } else { this._lightDom.redistribute(); } @@ -129,8 +128,11 @@ export class ViewContainer { } } - static removeViewNodesFromParent(parent, view) { - for (var i = view.rootNodes.length - 1; i >= 0; --i) { + static removeViewNodes(view) { + var len = view.rootNodes.length; + if (len == 0) return; + var parent = view.rootNodes[0].parentNode; + for (var i = len - 1; i >= 0; --i) { DOM.removeChild(parent, view.rootNodes[i]); } } diff --git a/modules/angular2/src/render/dom/view/view_factory.js b/modules/angular2/src/render/dom/view/view_factory.js index 077bd7e624..2883dedd0a 100644 --- a/modules/angular2/src/render/dom/view/view_factory.js +++ b/modules/angular2/src/render/dom/view/view_factory.js @@ -1,4 +1,4 @@ -import {OpaqueToken} from 'angular2/di'; +import {OpaqueToken, Inject, Injectable} from 'angular2/di'; import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection'; @@ -8,32 +8,33 @@ import {Content} from '../shadow_dom/content_tag'; import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy'; import {EventManager} from 'angular2/src/render/dom/events/event_manager'; -import {ViewContainer} from './view_container'; -import {ProtoView} from './proto_view'; -import {View} from './view'; +import * as vcModule from './view_container'; +import * as pvModule from './proto_view'; +import * as viewModule from './view'; import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from '../util'; -export var VIEW_POOL_CAPACITY = new OpaqueToken('ViewFactory.viewPoolCapacity'); - +// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this! +export const VIEW_POOL_CAPACITY = 'render.ViewFactory.viewPoolCapacity'; +@Injectable() export class ViewFactory { _poolCapacity:number; - _pooledViews:List; + _pooledViews:List; _eventManager:EventManager; _shadowDomStrategy:ShadowDomStrategy; - constructor(capacity, eventManager, shadowDomStrategy) { + constructor(@Inject(VIEW_POOL_CAPACITY) capacity, eventManager:EventManager, shadowDomStrategy:ShadowDomStrategy) { this._poolCapacity = capacity; this._pooledViews = ListWrapper.create(); this._eventManager = eventManager; this._shadowDomStrategy = shadowDomStrategy; } - getView(protoView:ProtoView):View { + getView(protoView:pvModule.ProtoView):viewModule.View { // TODO(tbosch): benchmark this scanning of views and maybe // replace it with a fancy LRU Map/List combination... var view; - for (var i=0; i=0; i--) { var pooledView = this._pooledViews[i]; if (pooledView.proto === protoView) { view = ListWrapper.removeAt(this._pooledViews, i); @@ -45,7 +46,7 @@ export class ViewFactory { return view; } - returnView(view:View) { + returnView(view:viewModule.View) { if (view.hydrated()) { view.dehydrate(); } @@ -55,7 +56,7 @@ export class ViewFactory { } } - _createView(protoView:ProtoView): View { + _createView(protoView:pvModule.ProtoView): viewModule.View { var rootElementClone = protoView.isRootView ? protoView.element : DOM.importIntoDoc(protoView.element); var elementsWithBindingsDynamic; if (protoView.isTemplateElement) { @@ -72,7 +73,7 @@ export class ViewFactory { var viewRootNodes; if (protoView.isTemplateElement) { var childNode = DOM.firstChild(DOM.content(rootElementClone)); - viewRootNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in ProtoView + viewRootNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in pvModule.ProtoView // Note: An explicit loop is the fastest way to convert a DOM array into a JS array! while(childNode != null) { ListWrapper.push(viewRootNodes, childNode); @@ -108,7 +109,7 @@ export class ViewFactory { // viewContainers var viewContainer = null; if (isBlank(binder.componentId) && isPresent(binder.nestedProtoView)) { - viewContainer = new ViewContainer(this, element); + viewContainer = new vcModule.ViewContainer(this, element); } viewContainers[binderIdx] = viewContainer; @@ -120,7 +121,7 @@ export class ViewFactory { contentTags[binderIdx] = contentTag; } - var view = new View( + var view = new viewModule.View( protoView, viewRootNodes, boundTextNodes, boundElements, viewContainers, contentTags ); diff --git a/modules/angular2/src/services/ruler.js b/modules/angular2/src/services/ruler.js index 8d14b4f175..40136dee25 100644 --- a/modules/angular2/src/services/ruler.js +++ b/modules/angular2/src/services/ruler.js @@ -1,6 +1,6 @@ import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; import {DomAdapter} from 'angular2/src/dom/dom_adapter'; -import {NgElement} from 'angular2/src/core/dom/element'; +import {NgElement} from 'angular2/src/core/compiler/ng_element'; export class Rectangle { left; diff --git a/modules/angular2/src/test_lib/test_bed.js b/modules/angular2/src/test_lib/test_bed.js index 7de912ebcc..f8024a1b73 100644 --- a/modules/angular2/src/test_lib/test_bed.js +++ b/modules/angular2/src/test_lib/test_bed.js @@ -1,4 +1,4 @@ -import {Injector} from 'angular2/di'; +import {Injector, bind} from 'angular2/di'; import {Type, isPresent, BaseException} from 'angular2/src/facade/lang'; import {Promise} from 'angular2/src/facade/async'; @@ -10,12 +10,15 @@ import {Template} from 'angular2/src/core/annotations/template'; import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; import {Compiler} from 'angular2/src/core/compiler/compiler'; import {View} from 'angular2/src/core/compiler/view'; +import {ViewFactory} from 'angular2/src/core/compiler/view_factory'; -import {EventManager} from 'angular2/src/render/dom/events/event_manager'; +import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector'; +import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; -import {queryView} from './utils'; +import {queryView, viewRootNodes, el} from './utils'; import {instantiateType, getTypeOf} from './lang_utils'; + export class TestBed { _injector: Injector; @@ -85,11 +88,17 @@ export class TestBed { this.setInlineTemplate(component, html); } - return this._injector.get(Compiler).compile(component).then((pv) => { - var eventManager = this._injector.get(EventManager); - var view = pv.instantiate(null, eventManager); - view.hydrate(this._injector, null, null, context, null); - return new ViewProxy(view); + var rootEl = el('
'); + var metadataReader = this._injector.get(DirectiveMetadataReader); + var componentBinding = DirectiveBinding.createFromBinding( + bind(component).toValue(context), + metadataReader.read(component).annotation + ); + return this._injector.get(Compiler).compileRoot(rootEl, componentBinding).then((pv) => { + var viewFactory = this._injector.get(ViewFactory); + var view = viewFactory.getView(pv); + view.hydrate(this._injector, null, context, null); + return new ViewProxy(view.componentChildViews[0]); }); } } @@ -108,8 +117,8 @@ export class ViewProxy { return this._view.context; } - get nodes(): List { - return this._view.nodes; + get rootNodes(): List { + return viewRootNodes(this._view); } detectChanges(): void { diff --git a/modules/angular2/src/test_lib/test_injector.js b/modules/angular2/src/test_lib/test_injector.js index 92236aae1d..feef7d75e2 100644 --- a/modules/angular2/src/test_lib/test_injector.js +++ b/modules/angular2/src/test_lib/test_injector.js @@ -7,13 +7,15 @@ import {ExceptionHandler} from 'angular2/src/core/exception_handler'; import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; -import {ShadowDomStrategy, EmulatedUnscopedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; +import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy'; +import {EmulatedUnscopedShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy'; import {XHR} from 'angular2/src/services/xhr'; import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; import {UrlResolver} from 'angular2/src/services/url_resolver'; import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner'; import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone'; +import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_component_loader'; import {DOM} from 'angular2/src/dom/dom_adapter'; @@ -32,6 +34,13 @@ import {Injector} from 'angular2/di'; import {List, ListWrapper} from 'angular2/src/facade/collection'; import {FunctionWrapper} from 'angular2/src/facade/lang'; +import {ViewFactory, VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_factory'; +import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; +import {Renderer} from 'angular2/src/render/api'; +import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer'; +import * as rc from 'angular2/src/render/dom/compiler/compiler'; +import * as rvf from 'angular2/src/render/dom/view/view_factory'; + /** * Returns the root injector bindings. * @@ -67,11 +76,19 @@ function _getAppBindings() { bind(ShadowDomStrategy).toFactory( (styleUrlResolver, doc) => new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, doc.head), [StyleUrlResolver, appDocumentToken]), + bind(Renderer).toClass(DirectDomRenderer), + bind(rc.Compiler).toClass(rc.DefaultCompiler), + rvf.ViewFactory, + bind(rvf.VIEW_POOL_CAPACITY).toValue(500), + ProtoViewFactory, + ViewFactory, + bind(VIEW_POOL_CAPACITY).toValue(500), Compiler, CompilerCache, bind(TemplateResolver).toClass(MockTemplateResolver), bind(ChangeDetection).toValue(dynamicChangeDetection), TemplateLoader, + PrivateComponentLoader, DirectiveMetadataReader, Parser, Lexer, diff --git a/modules/angular2/src/test_lib/utils.js b/modules/angular2/src/test_lib/utils.js index a436d03998..89e968d0e0 100644 --- a/modules/angular2/src/test_lib/utils.js +++ b/modules/angular2/src/test_lib/utils.js @@ -24,9 +24,14 @@ export class Log { } } +export function viewRootNodes(view) { + return view.render.delegate.rootNodes; +} + export function queryView(view, selector) { - for (var i = 0; i < view.nodes.length; ++i) { - var res = DOM.querySelector(view.nodes[i], selector); + var rootNodes = viewRootNodes(view); + for (var i = 0; i < rootNodes.length; ++i) { + var res = DOM.querySelector(rootNodes[i], selector); if (isPresent(res)) { return res; } diff --git a/modules/angular2/test/core/compiler/compiler_spec.js b/modules/angular2/test/core/compiler/compiler_spec.js index 94cd8e7994..e1640ee0b7 100644 --- a/modules/angular2/test/core/compiler/compiler_spec.js +++ b/modules/angular2/test/core/compiler/compiler_spec.js @@ -16,7 +16,7 @@ import {List, ListWrapper, Map, MapWrapper, StringMapWrapper} from 'angular2/src import {Type, isBlank, stringify, isPresent} from 'angular2/src/facade/lang'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; -import {NewCompiler, CompilerCache} from 'angular2/src/core/compiler/compiler'; +import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler'; import {ProtoView} from 'angular2/src/core/compiler/view'; import {ElementBinder} from 'angular2/src/core/compiler/element_binder'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; @@ -45,7 +45,7 @@ export function main() { var urlResolver = new FakeUrlResolver(); renderer = new FakeRenderer(renderCompileResults); protoViewFactory = new FakeProtoViewFactory(protoViewFactoryResults) - return new NewCompiler( + return new Compiler( reader, new CompilerCache(), tplResolver, @@ -373,7 +373,7 @@ export function main() { ], [rootProtoView, mainProtoView] ); - compiler.compileRoot(null, createDirectiveBinding(reader, MainComponent)).then( (protoView) => { + compiler.compileRoot(null, MainComponent).then( (protoView) => { expect(protoView).toBe(rootProtoView); expect(rootProtoView.elementBinders[0].nestedProtoView).toBe(mainProtoView); async.done(); @@ -388,7 +388,7 @@ function createDirectiveBinding(reader, type) { } function createProtoView(elementBinders = null) { - var pv = new ProtoView(null, null, null, null, null); + var pv = new ProtoView(null, null, null); if (isBlank(elementBinders)) { elementBinders = []; } diff --git a/modules/angular2/test/core/compiler/element_injector_spec.js b/modules/angular2/test/core/compiler/element_injector_spec.js index 30378450e6..8c9f918733 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.js +++ b/modules/angular2/test/core/compiler/element_injector_spec.js @@ -1,7 +1,6 @@ import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach, SpyObject, proxy, el} from 'angular2/test_lib'; import {isBlank, isPresent, IMPLEMENTS} from 'angular2/src/facade/lang'; import {ListWrapper, MapWrapper, List, StringMapWrapper} from 'angular2/src/facade/collection'; -import {DOM} from 'angular2/src/dom/dom_adapter'; import {ProtoElementInjector, PreBuiltObjects, DirectiveBinding} from 'angular2/src/core/compiler/element_injector'; import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; import {EventEmitter, PropertySetter, Attribute} from 'angular2/src/core/annotations/di'; @@ -9,10 +8,11 @@ import {onDestroy} from 'angular2/src/core/annotations/annotations'; import {Optional, Injector, Inject, bind} from 'angular2/di'; import {ProtoView, View} from 'angular2/src/core/compiler/view'; import {ViewContainer} from 'angular2/src/core/compiler/view_container'; -import {NgElement} from 'angular2/src/core/dom/element'; -import {LightDom, DestinationLightDom} from 'angular2/src/core/compiler/shadow_dom_emulation/light_dom'; +import {NgElement} from 'angular2/src/core/compiler/ng_element'; import {Directive} from 'angular2/src/core/annotations/annotations'; -import {BindingPropagationConfig} from 'angular2/change_detection'; +import {BindingPropagationConfig, Parser, Lexer} from 'angular2/change_detection'; + +import {ViewRef, Renderer} from 'angular2/src/render/api'; class DummyDirective extends Directive { constructor({lifecycle} = {}) { super({lifecycle: lifecycle}); } @@ -22,10 +22,6 @@ class DummyDirective extends Directive { @IMPLEMENTS(View) class DummyView extends SpyObject {noSuchMethod(m){super.noSuchMethod(m)}} -@proxy -@IMPLEMENTS(LightDom) -class DummyLightDom extends SpyObject {noSuchMethod(m){super.noSuchMethod(m)}} - class SimpleDirective { } @@ -198,10 +194,10 @@ export function main() { var proto = new ProtoElementInjector(null, 0, bindings, isPresent(shadowDomAppInjector)); proto.attributes = attributes; - var inj = proto.instantiate(null, null); + var inj = proto.instantiate(null); var preBuilt = isPresent(preBuiltObjects) ? preBuiltObjects : defaultPreBuiltObjects; - inj.instantiateDirectives(lightDomAppInjector, shadowDomAppInjector, preBuilt); + inj.instantiateDirectives(lightDomAppInjector, null, shadowDomAppInjector, preBuilt); return inj; } @@ -211,13 +207,13 @@ export function main() { var inj = new Injector([]); var protoParent = new ProtoElementInjector(null, 0, parentBindings); - var parent = protoParent.instantiate(null, null); + var parent = protoParent.instantiate(null); - parent.instantiateDirectives(inj, null, parentPreBuildObjects); + parent.instantiateDirectives(inj, null, null, parentPreBuildObjects); var protoChild = new ProtoElementInjector(protoParent, 1, childBindings, false, 1); - var child = protoChild.instantiate(parent, null); - child.instantiateDirectives(inj, null, defaultPreBuiltObjects); + var child = protoChild.instantiate(parent); + child.instantiateDirectives(inj, null, null, defaultPreBuiltObjects); return child; } @@ -229,12 +225,12 @@ export function main() { var shadowInj = inj.createChild([]); var protoParent = new ProtoElementInjector(null, 0, hostBindings, true); - var host = protoParent.instantiate(null, null); - host.instantiateDirectives(inj, shadowInj, hostPreBuildObjects); + var host = protoParent.instantiate(null); + host.instantiateDirectives(inj, null, shadowInj, hostPreBuildObjects); var protoChild = new ProtoElementInjector(protoParent, 0, shadowBindings, false, 1); - var shadow = protoChild.instantiate(null, host); - shadow.instantiateDirectives(shadowInj, null, null); + var shadow = protoChild.instantiate(null); + shadow.instantiateDirectives(shadowInj, host, null, null); return shadow; } @@ -278,9 +274,9 @@ export function main() { var protoChild1 = new ProtoElementInjector(protoParent, 1, []); var protoChild2 = new ProtoElementInjector(protoParent, 2, []); - var p = protoParent.instantiate(null, null); - var c1 = protoChild1.instantiate(p, null); - var c2 = protoChild2.instantiate(p, null); + var p = protoParent.instantiate(null); + var c1 = protoChild1.instantiate(p); + var c2 = protoChild2.instantiate(p); expect(humanize(p, [ [p, 'parent'], @@ -295,8 +291,8 @@ export function main() { var protoParent = new ProtoElementInjector(null, 0, []); var protoChild = new ProtoElementInjector(protoParent, 1, [], false, distance); - var p = protoParent.instantiate(null, null); - var c = protoChild.instantiate(p, null); + var p = protoParent.instantiate(null); + var c = protoChild.instantiate(p); expect(c.directParent()).toEqual(p); }); @@ -306,8 +302,8 @@ export function main() { var protoParent = new ProtoElementInjector(null, 0, []); var protoChild = new ProtoElementInjector(protoParent, 1, [], false, distance); - var p = protoParent.instantiate(null, null); - var c = protoChild.instantiate(p, null); + var p = protoParent.instantiate(null); + var c = protoChild.instantiate(p); expect(c.directParent()).toEqual(null); }); @@ -496,14 +492,14 @@ export function main() { }); it("should return element", function () { - var element = new NgElement(null); + var element = new NgElement(null, null); var inj = injector([], null, null, new PreBuiltObjects(null, element, null, null)); expect(inj.get(NgElement)).toEqual(element); }); it('should return viewContainer', function () { - var viewContainer = new ViewContainer(null, null, null, null, null); + var viewContainer = new ViewContainer(null, null, null, null); var inj = injector([], null, null, new PreBuiltObjects(null, null, viewContainer, null)); expect(inj.get(ViewContainer)).toEqual(viewContainer); @@ -536,9 +532,9 @@ export function main() { injWithPrivateComponent.createPrivateComponent(SomeOtherDirective, null); var shadowDomProtoInjector = new ProtoElementInjector(null, 0, [NeedDirectiveFromAncestor], false); - var shadowDomInj = shadowDomProtoInjector.instantiate(null, injWithPrivateComponent); + var shadowDomInj = shadowDomProtoInjector.instantiate(null); - expect(() => shadowDomInj.instantiateDirectives(appInjector, null, defaultPreBuiltObjects)). + expect(() => shadowDomInj.instantiateDirectives(appInjector, injWithPrivateComponent, null, defaultPreBuiltObjects)). toThrowError(new RegExp("No provider for SimpleDirective")); }); @@ -547,8 +543,8 @@ export function main() { injWithPrivateComponent.createPrivateComponent(SimpleDirective, null); var shadowDomProtoInjector = new ProtoElementInjector(null, 0, [NeedDirectiveFromAncestor], false); - var shadowDomInjector = shadowDomProtoInjector.instantiate(null, injWithPrivateComponent); - shadowDomInjector.instantiateDirectives(appInjector, null, defaultPreBuiltObjects); + var shadowDomInjector = shadowDomProtoInjector.instantiate(null); + shadowDomInjector.instantiateDirectives(appInjector, injWithPrivateComponent, null, defaultPreBuiltObjects); expect(shadowDomInjector.get(NeedDirectiveFromAncestor)).toBeAnInstanceOf(NeedDirectiveFromAncestor); expect(shadowDomInjector.get(NeedDirectiveFromAncestor).dependency).toBeAnInstanceOf(SimpleDirective); @@ -566,7 +562,7 @@ export function main() { expect(inj.getPrivateComponent()).toBe(null); expect(dir.onDestroyCounter).toBe(1); - inj.instantiateDirectives(null, null, null); + inj.instantiateDirectives(null, null, null, null); expect(inj.getPrivateComponent()).not.toBe(null); }); @@ -577,15 +573,18 @@ export function main() { function createpreBuildObject(eventName, eventHandler) { var handlers = StringMapWrapper.create(); StringMapWrapper.set(handlers, eventName, eventHandler); - var pv = new ProtoView(null, null, null, null); - pv.eventHandlers = [handlers]; - var view = new View(pv, null, MapWrapper.create()); + var pv = new ProtoView(null, null, null); + pv.bindElement(null, 0, null, null, null); + pv.bindEvent(eventName, new Parser(new Lexer()).parseAction('handler()', '')); + + var view = new View(pv, MapWrapper.create()); + view.context = new ContextWithHandler(eventHandler); return new PreBuiltObjects(view, null, null, null); } it('should be injectable and callable', () => { var called = false; - var preBuildObject = createpreBuildObject('click', (e, view) => { called = true;}); + var preBuildObject = createpreBuildObject('click', () => { called = true;}); var inj = injector([NeedsEventEmitter], null, null, preBuildObject); inj.get(NeedsEventEmitter).click(); expect(called).toEqual(true); @@ -593,7 +592,7 @@ export function main() { it('should be injectable and callable without specifying param type annotation', () => { var called = false; - var preBuildObject = createpreBuildObject('click', (e, view) => { called = true;}); + var preBuildObject = createpreBuildObject('click', () => { called = true;}); var inj = injector([NeedsEventEmitterNoType], null, null, preBuildObject); inj.get(NeedsEventEmitterNoType).click(); expect(called).toEqual(true); @@ -613,11 +612,17 @@ export function main() { }); describe('property setter', () => { - it('should be injectable and callable', () => { - var div = el('
'); - var ngElement = new NgElement(div); + var renderer, view; - var preBuildObject = new PreBuiltObjects(null, ngElement, null, null); + beforeEach( () => { + renderer = new FakeRenderer(); + var protoView = new ProtoView(renderer, null, null); + view = new View(protoView, MapWrapper.create()); + view.render = new ViewRef(); + }); + + it('should be injectable and callable', () => { + var preBuildObject = new PreBuiltObjects(view, null, null, null); var inj = injector([NeedsPropertySetter], null, null, preBuildObject); var component = inj.get(NeedsPropertySetter); component.setProp('foobar'); @@ -627,22 +632,21 @@ export function main() { component.setStyle('40px'); component.setStyleWithUnit(50); - expect(div.title).toEqual('foobar'); - expect(DOM.getAttribute(div, 'role')).toEqual('button'); - expect(DOM.hasClass(div, 'active')).toEqual(true); - expect(DOM.hasClass(div, 'foo-bar')).toEqual(true); - expect(DOM.getStyle(div, 'width')).toEqual('40px'); - expect(DOM.getStyle(div, 'height')).toEqual('50px'); + expect(renderer.log[0]).toEqual([view.render, 0, 'title', 'foobar']); + expect(renderer.log[1]).toEqual([view.render, 0, 'attr.role', 'button']); + expect(renderer.log[2]).toEqual([view.render, 0, 'class.active', true]); + expect(renderer.log[3]).toEqual([view.render, 0, 'class.foo-bar', true]); + expect(renderer.log[4]).toEqual([view.render, 0, 'style.width', '40px']); + expect(renderer.log[5]).toEqual([view.render, 0, 'style.height.px', 50]); }); it('should be injectable and callable without specifying param type annotation', () => { - var div = el('
'); - var preBuildObject = new PreBuiltObjects(null, new NgElement(div), null, null); + var preBuildObject = new PreBuiltObjects(view, null, null, null); var inj = injector([NeedsPropertySetterNoType], null, null, preBuildObject); var component = inj.get(NeedsPropertySetterNoType); component.setProp('foobar'); - expect(div.title).toEqual('foobar'); + expect(renderer.log[0]).toEqual([view.render, 0, 'title', 'foobar']); }); }); @@ -673,3 +677,22 @@ export function main() { }); } + +class ContextWithHandler { + handler; + constructor(handler) { + this.handler = handler; + } +} + +class FakeRenderer extends Renderer { + log:List; + constructor() { + super(); + this.log = []; + } + setElementProperty(viewRef, elementIndex, propertyName, value) { + ListWrapper.push(this.log, [viewRef, elementIndex, propertyName, value]); + } + +} \ No newline at end of file diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js index 2d8a265369..917d78aaa1 100644 --- a/modules/angular2/test/core/compiler/integration_spec.js +++ b/modules/angular2/test/core/compiler/integration_spec.js @@ -5,32 +5,27 @@ import { xdescribe, describe, el, + dispatchEvent, expect, iit, inject, + beforeEachBindings, it, - xit, + xit } from 'angular2/test_lib'; +import {TestBed} from 'angular2/src/test_lib/test_bed'; + import {DOM} from 'angular2/src/dom/dom_adapter'; import {Type, isPresent, BaseException, assertionsEnabled, isJsObject} from 'angular2/src/facade/lang'; import {PromiseWrapper} from 'angular2/src/facade/async'; import {Injector, bind} from 'angular2/di'; -import {Lexer, Parser, dynamicChangeDetection, - DynamicChangeDetection, Pipe, PipeRegistry, BindingPropagationConfig, ON_PUSH} from 'angular2/change_detection'; +import {dynamicChangeDetection, + ChangeDetection, DynamicChangeDetection, Pipe, PipeRegistry, BindingPropagationConfig, ON_PUSH} from 'angular2/change_detection'; -import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler'; -import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; -import {ShadowDomStrategy, EmulatedUnscopedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy'; import {PrivateComponentLocation} from 'angular2/src/core/compiler/private_component_location'; import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_component_loader'; -import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader'; -import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock'; -import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; -import {UrlResolver} from 'angular2/src/services/url_resolver'; -import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver'; -import {EventManager} from 'angular2/src/render/dom/events/event_manager'; import {Decorator, Component, Viewport, DynamicComponent} from 'angular2/src/core/annotations/annotations'; import {Template} from 'angular2/src/core/annotations/template'; @@ -43,161 +38,117 @@ import {ViewContainer} from 'angular2/src/core/compiler/view_container'; export function main() { describe('integration tests', function() { - var directiveMetadataReader, shadowDomStrategy, compiler, tplResolver; - - function createCompiler(tplResolver, changedDetection) { - var urlResolver = new UrlResolver(); - return new Compiler(changedDetection, - new TemplateLoader(null, null), - directiveMetadataReader, - new Parser(new Lexer()), - new CompilerCache(), - shadowDomStrategy, - tplResolver, - new ComponentUrlMapper(), - urlResolver - ); - } + var ctx; beforeEach( () => { - tplResolver = new MockTemplateResolver(); - - directiveMetadataReader = new DirectiveMetadataReader(); - - var urlResolver = new UrlResolver(); - shadowDomStrategy = new EmulatedUnscopedShadowDomStrategy(new StyleUrlResolver(urlResolver), null); - - compiler = createCompiler(tplResolver, dynamicChangeDetection); + ctx = new MyComp(); }); describe('react to record changes', function() { - var view, ctx, cd; - function createView(pv) { - ctx = new MyComp(); - view = pv.instantiate(null, null); - - view.hydrate(new Injector([ - bind(Compiler).toValue(compiler), - bind(DirectiveMetadataReader).toValue(directiveMetadataReader), - bind(ShadowDomStrategy).toValue(shadowDomStrategy), - bind(EventManager).toValue(null), - PrivateComponentLoader - ]), null, null, ctx, null); - - cd = view.changeDetector; - } - - it('should consume text node changes', inject([AsyncTestCompleter], (async) => { - tplResolver.setTemplate(MyComp, new Template({inline: '
{{ctxProp}}
'})); - compiler.compile(MyComp).then((pv) => { - createView(pv); + it('should consume text node changes', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideTemplate(MyComp, new Template({inline: '
{{ctxProp}}
'})); + tb.createView(MyComp, {context: ctx}).then((view) => { ctx.ctxProp = 'Hello World!'; - cd.detectChanges(); - expect(DOM.getInnerHTML(view.nodes[0])).toEqual('Hello World!'); + view.detectChanges(); + expect(DOM.getInnerHTML(view.rootNodes[0])).toEqual('Hello World!'); async.done(); }); })); - it('should consume element binding changes', inject([AsyncTestCompleter], (async) => { - tplResolver.setTemplate(MyComp, new Template({inline: '
'})); + it('should consume element binding changes', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideTemplate(MyComp, new Template({inline: '
'})); - compiler.compile(MyComp).then((pv) => { - createView(pv); + tb.createView(MyComp, {context: ctx}).then((view) => { ctx.ctxProp = 'Hello World!'; - cd.detectChanges(); + view.detectChanges(); - expect(view.nodes[0].id).toEqual('Hello World!'); + expect(view.rootNodes[0].id).toEqual('Hello World!'); async.done(); }); })); - it('should consume binding to aria-* attributes', inject([AsyncTestCompleter], (async) => { - tplResolver.setTemplate(MyComp, new Template({inline: '
'})); + it('should consume binding to aria-* attributes', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideTemplate(MyComp, new Template({inline: '
'})); - compiler.compile(MyComp).then((pv) => { - createView(pv); + tb.createView(MyComp, {context: ctx}).then((view) => { ctx.ctxProp = 'Initial aria label'; - cd.detectChanges(); - expect(DOM.getAttribute(view.nodes[0], 'aria-label')).toEqual('Initial aria label'); + view.detectChanges(); + expect(DOM.getAttribute(view.rootNodes[0], 'aria-label')).toEqual('Initial aria label'); ctx.ctxProp = 'Changed aria label'; - cd.detectChanges(); - expect(DOM.getAttribute(view.nodes[0], 'aria-label')).toEqual('Changed aria label'); + view.detectChanges(); + expect(DOM.getAttribute(view.rootNodes[0], 'aria-label')).toEqual('Changed aria label'); async.done(); }); })); - it('should consume binding to property names where attr name and property name do not match', inject([AsyncTestCompleter], (async) => { - tplResolver.setTemplate(MyComp, new Template({inline: '
'})); + it('should consume binding to property names where attr name and property name do not match', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideTemplate(MyComp, new Template({inline: '
'})); - compiler.compile(MyComp).then((pv) => { - createView(pv); + tb.createView(MyComp, {context: ctx}).then((view) => { - cd.detectChanges(); - expect(view.nodes[0].tabIndex).toEqual(0); + view.detectChanges(); + expect(view.rootNodes[0].tabIndex).toEqual(0); ctx.ctxNumProp = 5; - cd.detectChanges(); - expect(view.nodes[0].tabIndex).toEqual(5); + view.detectChanges(); + expect(view.rootNodes[0].tabIndex).toEqual(5); async.done(); }); })); - it('should consume binding to camel-cased properties using dash-cased syntax in templates', inject([AsyncTestCompleter], (async) => { - tplResolver.setTemplate(MyComp, new Template({inline: ''})); + it('should consume binding to camel-cased properties using dash-cased syntax in templates', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideTemplate(MyComp, new Template({inline: ''})); - compiler.compile(MyComp).then((pv) => { - createView(pv); + tb.createView(MyComp, {context: ctx}).then((view) => { - cd.detectChanges(); - expect(view.nodes[0].readOnly).toBeFalsy(); + view.detectChanges(); + expect(view.rootNodes[0].readOnly).toBeFalsy(); ctx.ctxBoolProp = true; - cd.detectChanges(); - expect(view.nodes[0].readOnly).toBeTruthy(); + view.detectChanges(); + expect(view.rootNodes[0].readOnly).toBeTruthy(); async.done(); }); })); - it('should consume binding to inner-html', inject([AsyncTestCompleter], (async) => { - tplResolver.setTemplate(MyComp, new Template({inline: '
'})); + it('should consume binding to inner-html', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideTemplate(MyComp, new Template({inline: '
'})); - compiler.compile(MyComp).then((pv) => { - createView(pv); + tb.createView(MyComp, {context: ctx}).then((view) => { ctx.ctxProp = 'Some HTML'; - cd.detectChanges(); - expect(DOM.getInnerHTML(view.nodes[0])).toEqual('Some HTML'); + view.detectChanges(); + expect(DOM.getInnerHTML(view.rootNodes[0])).toEqual('Some HTML'); ctx.ctxProp = 'Some other
HTML
'; - cd.detectChanges(); - expect(DOM.getInnerHTML(view.nodes[0])).toEqual('Some other
HTML
'); + view.detectChanges(); + expect(DOM.getInnerHTML(view.rootNodes[0])).toEqual('Some other
HTML
'); async.done(); }); })); - it('should ignore bindings to unknown properties', inject([AsyncTestCompleter], (async) => { - tplResolver.setTemplate(MyComp, new Template({inline: '
'})); + it('should ignore bindings to unknown properties', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideTemplate(MyComp, new Template({inline: '
'})); - compiler.compile(MyComp).then((pv) => { - createView(pv); + tb.createView(MyComp, {context: ctx}).then((view) => { ctx.ctxProp = 'Some value'; - cd.detectChanges(); - expect(DOM.hasProperty(view.nodes[0], 'unknown')).toBeFalsy(); + view.detectChanges(); + expect(DOM.hasProperty(view.rootNodes[0], 'unknown')).toBeFalsy(); async.done(); }); })); - it('should consume directive watch expression change.', inject([AsyncTestCompleter], (async) => { + it('should consume directive watch expression change.', inject([TestBed, AsyncTestCompleter], (tb, async) => { var tpl = '
' + '
' + @@ -205,80 +156,81 @@ export function main() { '
' + '
' + '
' - tplResolver.setTemplate(MyComp, new Template({inline: tpl, directives: [MyDir]})); + tb.overrideTemplate(MyComp, new Template({inline: tpl, directives: [MyDir]})); - compiler.compile(MyComp).then((pv) => { - createView(pv); + tb.createView(MyComp, {context: ctx}).then((view) => { ctx.ctxProp = 'Hello World!'; - cd.detectChanges(); + view.detectChanges(); - expect(view.elementInjectors[0].get(MyDir).dirProp).toEqual('Hello World!'); - expect(view.elementInjectors[1].get(MyDir).dirProp).toEqual('Hi there!'); - expect(view.elementInjectors[2].get(MyDir).dirProp).toEqual('Hi there!'); - expect(view.elementInjectors[3].get(MyDir).dirProp).toEqual('One more Hello World!'); + expect(view.rawView.elementInjectors[0].get(MyDir).dirProp).toEqual('Hello World!'); + expect(view.rawView.elementInjectors[1].get(MyDir).dirProp).toEqual('Hi there!'); + expect(view.rawView.elementInjectors[2].get(MyDir).dirProp).toEqual('Hi there!'); + expect(view.rawView.elementInjectors[3].get(MyDir).dirProp).toEqual('One more Hello World!'); async.done(); }); })); - it("should support pipes in bindings and bind config", inject([AsyncTestCompleter], (async) => { - tplResolver.setTemplate(MyComp, - new Template({ - inline: '', - directives: [ComponentWithPipes] - })); - - - var registry = new PipeRegistry({ - "double" : [new DoublePipeFactory()] + describe('pipes', () => { + beforeEachBindings(() => { + return [bind(ChangeDetection).toFactory( + () => new DynamicChangeDetection(new PipeRegistry({ + "double" : [new DoublePipeFactory()] + })), + [] + )]; }); - var changeDetection = new DynamicChangeDetection(registry); - var compiler = createCompiler(tplResolver, changeDetection); - compiler.compile(MyComp).then((pv) => { - createView(pv); - ctx.ctxProp = 'a'; - cd.detectChanges(); + it("should support pipes in bindings and bind config", inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideTemplate(MyComp, + new Template({ + inline: '', + directives: [ComponentWithPipes] + })); - var comp = view.locals.get("comp"); + tb.createView(MyComp, {context: ctx}).then((view) => { - // it is doubled twice: once in the binding, second time in the bind config - expect(comp.prop).toEqual('aaaa'); - async.done(); - }); - })); + ctx.ctxProp = 'a'; + view.detectChanges(); - it('should support nested components.', inject([AsyncTestCompleter], (async) => { - tplResolver.setTemplate(MyComp, new Template({ + var comp = view.rawView.locals.get("comp"); + + // it is doubled twice: once in the binding, second time in the bind config + expect(comp.prop).toEqual('aaaa'); + async.done(); + }); + })); + }); + + it('should support nested components.', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideTemplate(MyComp, new Template({ inline: '', directives: [ChildComp] })); - compiler.compile(MyComp).then((pv) => { - createView(pv); + tb.createView(MyComp, {context: ctx}).then((view) => { - cd.detectChanges(); + view.detectChanges(); - expect(view.nodes).toHaveText('hello'); + expect(view.rootNodes).toHaveText('hello'); async.done(); }); })); // GH issue 328 - https://github.com/angular/angular/issues/328 - it('should support different directive types on a single node', inject([AsyncTestCompleter], (async) => { - tplResolver.setTemplate(MyComp, + it('should support different directive types on a single node', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideTemplate(MyComp, new Template({ inline: '', directives: [MyDir, ChildComp] })); - compiler.compile(MyComp).then((pv) => { - createView(pv); + tb.createView(MyComp, {context: ctx}).then((view) => { ctx.ctxProp = 'Hello World!'; - cd.detectChanges(); + view.detectChanges(); - var elInj = view.elementInjectors[0]; + var elInj = view.rawView.elementInjectors[0]; expect(elInj.get(MyDir).dirProp).toEqual('Hello World!'); expect(elInj.get(ChildComp).dirProp).toEqual(null); @@ -286,57 +238,54 @@ export function main() { }); })); - it('should support directives where a binding attribute is not given', inject([AsyncTestCompleter], (async) => { - tplResolver.setTemplate(MyComp, + it('should support directives where a binding attribute is not given', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideTemplate(MyComp, new Template({ // No attribute "el-prop" specified. inline: '

', directives: [MyDir] })); - compiler.compile(MyComp).then((pv) => { - createView(pv); + tb.createView(MyComp, {context: ctx}).then((view) => { async.done(); }); })); - it('should support directives where a selector matches property binding', inject([AsyncTestCompleter], (async) => { - tplResolver.setTemplate(MyComp, + it('should support directives where a selector matches property binding', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideTemplate(MyComp, new Template({ inline: '

', directives: [IdComponent] })); - compiler.compile(MyComp).then((pv) => { - createView(pv); + tb.createView(MyComp, {context: ctx}).then((view) => { ctx.ctxProp = 'some_id'; - cd.detectChanges(); - expect(view.nodes[0].id).toEqual('some_id'); - expect(view.nodes).toHaveText('Matched on id with some_id'); + view.detectChanges(); + expect(view.rootNodes[0].id).toEqual('some_id'); + expect(view.rootNodes).toHaveText('Matched on id with some_id'); ctx.ctxProp = 'other_id'; - cd.detectChanges(); - expect(view.nodes[0].id).toEqual('other_id'); - expect(view.nodes).toHaveText('Matched on id with other_id'); + view.detectChanges(); + expect(view.rootNodes[0].id).toEqual('other_id'); + expect(view.rootNodes).toHaveText('Matched on id with other_id'); async.done(); }); })); - it('should support template directives via `