diff --git a/modules/angular2/router.js b/modules/angular2/router.js index 48b92a2271..a5118abcec 100644 --- a/modules/angular2/router.js +++ b/modules/angular2/router.js @@ -18,7 +18,7 @@ import {Router, RootRouter} from './src/router/router'; import {RouteRegistry} from './src/router/route_registry'; import {Pipeline} from './src/router/pipeline'; import {Location} from './src/router/location'; -import {appComponentAnnotatedTypeToken} from './src/core/application_tokens'; +import {appComponentRefToken} from './src/core/application_tokens'; import {bind} from './di'; export var routerInjectables:List = [ @@ -26,7 +26,7 @@ export var routerInjectables:List = [ Pipeline, BrowserLocation, Location, - bind(Router).toFactory((registry, pipeline, location, meta) => { - return new RootRouter(registry, pipeline, location, meta.type); - }, [RouteRegistry, Pipeline, Location, appComponentAnnotatedTypeToken]) + bind(Router).toFactory((registry, pipeline, location, app) => { + return new RootRouter(registry, pipeline, location, app.hostComponentType); + }, [RouteRegistry, Pipeline, Location, appComponentRefToken]) ]; diff --git a/modules/angular2/src/change_detection/interfaces.ts b/modules/angular2/src/change_detection/interfaces.ts index dd2ebf07ef..4e0ed1a082 100644 --- a/modules/angular2/src/change_detection/interfaces.ts +++ b/modules/angular2/src/change_detection/interfaces.ts @@ -68,4 +68,4 @@ export class ChangeDetectorDefinition { constructor(public id: string, public strategy: string, public variableNames: List, public bindingRecords: List, public directiveRecords: List) {} -} \ No newline at end of file +} diff --git a/modules/angular2/src/core/application.js b/modules/angular2/src/core/application.js index 7afcb0169b..40e1bb7a95 100644 --- a/modules/angular2/src/core/application.js +++ b/modules/angular2/src/core/application.js @@ -9,7 +9,7 @@ import {Parser, Lexer, ChangeDetection, DynamicChangeDetection, PipeRegistry, de 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 {DirectiveResolver} from './compiler/directive_resolver'; import {List, ListWrapper} from 'angular2/src/facade/collection'; import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; import {NgZone} from 'angular2/src/core/zone/ng_zone'; @@ -39,8 +39,7 @@ import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; import {internalView} from 'angular2/src/core/compiler/view_ref'; import { - appComponentRefToken, - appComponentAnnotatedTypeToken + appComponentRefToken } from './application_tokens'; var _rootInjector: Injector; @@ -54,16 +53,14 @@ var _rootBindings = [ function _injectorBindings(appComponentType): List { return [ bind(DOCUMENT_TOKEN).toValue(DOM.defaultDoc()), - bind(appComponentAnnotatedTypeToken).toFactory((reader) => { - // TODO(rado): investigate whether to support bindings on root component. - return reader.read(appComponentType); - }, [DirectiveMetadataReader]), - bind(appComponentRefToken).toAsyncFactory((dynamicComponentLoader, injector, - appComponentAnnotatedType, testability, registry) => { + metadataReader, testability, registry) => { - var selector = appComponentAnnotatedType.annotation.selector; - return dynamicComponentLoader.loadIntoNewLocation(appComponentAnnotatedType.type, null, selector, injector).then( (componentRef) => { + var annotation = metadataReader.resolve(appComponentType); + + var selector = annotation.selector; + // TODO(rado): investigate whether to support bindings on root component. + return dynamicComponentLoader.loadIntoNewLocation(appComponentType, null, selector, injector).then( (componentRef) => { var domView = resolveInternalDomView(componentRef.hostView.render); // We need to do this here to ensure that we create Testability and // it's ready on the window for users. @@ -71,7 +68,7 @@ function _injectorBindings(appComponentType): List { return componentRef; }); - }, [DynamicComponentLoader, Injector, appComponentAnnotatedTypeToken, + }, [DynamicComponentLoader, Injector, DirectiveResolver, Testability, TestabilityRegistry]), bind(appComponentType).toFactory((ref) => ref.instance, @@ -109,7 +106,7 @@ function _injectorBindings(appComponentType): List { bind(PipeRegistry).toValue(defaultPipeRegistry), bind(ChangeDetection).toClass(DynamicChangeDetection), TemplateLoader, - DirectiveMetadataReader, + DirectiveResolver, Parser, Lexer, ExceptionHandler, @@ -262,7 +259,7 @@ export function bootstrap(appComponentType: Type, lc.registerWith(zone, appChangeDetector); lc.tick(); //the first tick that will bootstrap the app - bootstrapProcess.resolve(new ApplicationRef(componentRef, appInjector)); + bootstrapProcess.resolve(new ApplicationRef(componentRef, appComponentType, appInjector)); }, (err) => { @@ -276,9 +273,15 @@ export function bootstrap(appComponentType: Type, export class ApplicationRef { _hostComponent:ComponentRef; _injector:Injector; - constructor(hostComponent:ComponentRef, injector:Injector) { + _hostComponentType:Type; + constructor(hostComponent:ComponentRef, hostComponentType:Type, injector:Injector) { this._hostComponent = hostComponent; this._injector = injector; + this._hostComponentType = hostComponentType; + } + + get hostComponentType() { + return this._hostComponentType; } get hostComponent() { diff --git a/modules/angular2/src/core/application_tokens.js b/modules/angular2/src/core/application_tokens.js index 8921b5509b..417927506b 100644 --- a/modules/angular2/src/core/application_tokens.js +++ b/modules/angular2/src/core/application_tokens.js @@ -1,4 +1,3 @@ import {OpaqueToken} from 'angular2/di'; export var appComponentRefToken:OpaqueToken = new OpaqueToken('ComponentRef'); -export var appComponentAnnotatedTypeToken:OpaqueToken = new OpaqueToken('AppComponentAnnotatedType'); diff --git a/modules/angular2/src/core/compiler/compiler.js b/modules/angular2/src/core/compiler/compiler.js index 790fe2b36b..1ed78f0654 100644 --- a/modules/angular2/src/core/compiler/compiler.js +++ b/modules/angular2/src/core/compiler/compiler.js @@ -4,9 +4,10 @@ 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 {DirectiveMetadataReader} from './directive_metadata_reader'; -import {Component, Directive} from '../annotations_impl/annotations'; +import {DirectiveResolver} from './directive_resolver'; + import {AppProtoView} from './view'; +import {ElementBinder} from './element_binder'; import {ProtoViewRef} from './view_ref'; import {DirectiveBinding} from './element_injector'; import {TemplateResolver} from './template_resolver'; @@ -47,7 +48,7 @@ export class CompilerCache { */ @Injectable() export class Compiler { - _reader: DirectiveMetadataReader; + _reader: DirectiveResolver; _compilerCache:CompilerCache; _compiling:Map; _templateResolver: TemplateResolver; @@ -57,7 +58,7 @@ export class Compiler { _render: renderApi.RenderCompiler; _protoViewFactory:ProtoViewFactory; - constructor(reader: DirectiveMetadataReader, + constructor(reader: DirectiveResolver, cache:CompilerCache, templateResolver: TemplateResolver, componentUrlMapper: ComponentUrlMapper, @@ -79,11 +80,11 @@ export class Compiler { if (directiveTypeOrBinding instanceof DirectiveBinding) { return directiveTypeOrBinding; } else if (directiveTypeOrBinding instanceof Binding) { - let meta = this._reader.read(directiveTypeOrBinding.token); - return DirectiveBinding.createFromBinding(directiveTypeOrBinding, meta.annotation); + let annotation = this._reader.resolve(directiveTypeOrBinding.token); + return DirectiveBinding.createFromBinding(directiveTypeOrBinding, annotation); } else { - let meta = this._reader.read(directiveTypeOrBinding); - return DirectiveBinding.createFromType(meta.type, meta.annotation); + let annotation = this._reader.resolve(directiveTypeOrBinding); + return DirectiveBinding.createFromType(directiveTypeOrBinding, annotation); } } @@ -93,9 +94,9 @@ export class Compiler { var componentBinding = this._bindDirective(componentTypeOrBinding); this._assertTypeIsComponent(componentBinding); - var directiveMetadata = Compiler.buildRenderDirective(componentBinding); + var directiveMetadata = componentBinding.metadata; return this._render.compileHost(directiveMetadata).then( (hostRenderPv) => { - return this._compileNestedProtoViews(null, null, hostRenderPv, [componentBinding], true); + return this._compileNestedProtoViews(componentBinding, hostRenderPv, [componentBinding]); }).then( (appProtoView) => { return new ProtoViewRef(appProtoView); }); @@ -139,7 +140,7 @@ export class Compiler { ); var renderTemplate = this._buildRenderTemplate(component, template, directives); pvPromise = this._render.compile(renderTemplate).then( (renderPv) => { - return this._compileNestedProtoViews(null, componentBinding, renderPv, directives, true); + return this._compileNestedProtoViews(componentBinding, renderPv, directives); }); MapWrapper.set(this._compiling, component, pvPromise); @@ -147,10 +148,12 @@ export class Compiler { } // TODO(tbosch): union type return AppProtoView or Promise - _compileNestedProtoViews(parentProtoView, componentBinding, renderPv, directives, isComponentRootView) { - var nestedPVPromises = []; - var protoView = this._protoViewFactory.createProtoView(parentProtoView, componentBinding, renderPv, directives); - if (isComponentRootView && isPresent(componentBinding)) { + _compileNestedProtoViews(componentBinding, renderPv, directives) { + var protoViews = this._protoViewFactory.createAppProtoViews(componentBinding, renderPv, directives); + var protoView = protoViews[0]; + // TODO(tbosch): we should be caching host protoViews as well! + // -> need a separate cache for this... + if (renderPv.type === renderApi.ProtoViewDto.COMPONENT_VIEW_TYPE && isPresent(componentBinding)) { // Populate the cache before compiling the nested components, // so that components can reference themselves in their template. var component = componentBinding.key.token; @@ -158,25 +161,18 @@ export class Compiler { MapWrapper.delete(this._compiling, component); } - var binderIndex = 0; - ListWrapper.forEach(protoView.elementBinders, (elementBinder) => { + var nestedPVPromises = []; + ListWrapper.forEach(this._collectComponentElementBinders(protoViews), (elementBinder) => { var nestedComponent = elementBinder.componentDirective; - var nestedRenderProtoView = renderPv.elementBinders[binderIndex].nestedProtoView; var elementBinderDone = (nestedPv) => { elementBinder.nestedProtoView = nestedPv; }; - var nestedCall = null; - if (isPresent(nestedComponent)) { - nestedCall = this._compile(nestedComponent); - } else if (isPresent(nestedRenderProtoView)) { - nestedCall = this._compileNestedProtoViews(protoView, componentBinding, nestedRenderProtoView, directives, false); - } + var nestedCall = this._compile(nestedComponent); if (PromiseWrapper.isPromise(nestedCall)) { ListWrapper.push(nestedPVPromises, nestedCall.then(elementBinderDone)); } else if (isPresent(nestedCall)) { elementBinderDone(nestedCall); } - binderIndex++; }); var protoViewDone = (_) => { @@ -189,6 +185,18 @@ export class Compiler { } } + _collectComponentElementBinders(protoViews:List):List { + var componentElementBinders = []; + ListWrapper.forEach(protoViews, (protoView) => { + ListWrapper.forEach(protoView.elementBinders, (elementBinder) => { + if (isPresent(elementBinder.componentDirective)) { + ListWrapper.push(componentElementBinders, elementBinder); + } + }); + }); + return componentElementBinders; + } + _buildRenderTemplate(component, view, directives): renderApi.ViewDefinition { var componentUrl = this._urlResolver.resolve( this._appUrl, this._componentUrlMapper.getUrl(component) @@ -206,36 +214,7 @@ export class Compiler { componentId: stringify(component), absUrl: templateAbsUrl, template: view.template, - directives: ListWrapper.map(directives, Compiler.buildRenderDirective) - }); - } - - static buildRenderDirective(directiveBinding):renderApi.DirectiveMetadata { - var ann = directiveBinding.annotation; - var renderType; - var compileChildren = ann.compileChildren; - if (ann instanceof Component) { - renderType = renderApi.DirectiveMetadata.COMPONENT_TYPE; - } else { - renderType = renderApi.DirectiveMetadata.DIRECTIVE_TYPE; - } - var readAttributes = []; - ListWrapper.forEach(directiveBinding.dependencies, (dep) => { - if (isPresent(dep.attributeName)) { - ListWrapper.push(readAttributes, dep.attributeName); - } - }); - return new renderApi.DirectiveMetadata({ - id: stringify(directiveBinding.key.token), - type: renderType, - selector: ann.selector, - compileChildren: compileChildren, - hostListeners: isPresent(ann.hostListeners) ? MapWrapper.createFromStringMap(ann.hostListeners) : null, - hostProperties: isPresent(ann.hostProperties) ? MapWrapper.createFromStringMap(ann.hostProperties) : null, - hostAttributes: isPresent(ann.hostAttributes) ? MapWrapper.createFromStringMap(ann.hostAttributes) : null, - hostActions: isPresent(ann.hostActions) ? MapWrapper.createFromStringMap(ann.hostActions) : null, - properties: isPresent(ann.properties) ? MapWrapper.createFromStringMap(ann.properties) : null, - readAttributes: readAttributes + directives: ListWrapper.map(directives, directiveBinding => directiveBinding.metadata ) }); } @@ -260,7 +239,7 @@ export class Compiler { } _assertTypeIsComponent(directiveBinding:DirectiveBinding):void { - if (!(directiveBinding.annotation instanceof Component)) { + if (directiveBinding.metadata.type !== renderApi.DirectiveMetadata.COMPONENT_TYPE) { throw new BaseException(`Could not load '${stringify(directiveBinding.key.token)}' because it is not a component.`); } } diff --git a/modules/angular2/src/core/compiler/directive_metadata.js b/modules/angular2/src/core/compiler/directive_metadata.js deleted file mode 100644 index 644beb2c07..0000000000 --- a/modules/angular2/src/core/compiler/directive_metadata.js +++ /dev/null @@ -1,19 +0,0 @@ -import {Type} from 'angular2/src/facade/lang'; -import {List} from 'angular2/src/facade/collection'; -import {Directive} from 'angular2/src/core/annotations_impl/annotations' -import {ResolvedBinding} from 'angular2/di'; - -/** - * Combination of a type with the Directive annotation - */ -export class DirectiveMetadata { - type:Type; - annotation:Directive; - resolvedInjectables:List; - - constructor(type:Type, annotation:Directive, resolvedInjectables:List) { - this.annotation = annotation; - this.type = type; - this.resolvedInjectables = resolvedInjectables; - } -} diff --git a/modules/angular2/src/core/compiler/directive_metadata_reader.js b/modules/angular2/src/core/compiler/directive_resolver.js similarity index 51% rename from modules/angular2/src/core/compiler/directive_metadata_reader.js rename to modules/angular2/src/core/compiler/directive_resolver.js index 807429d4e0..38d751a3e4 100644 --- a/modules/angular2/src/core/compiler/directive_metadata_reader.js +++ b/modules/angular2/src/core/compiler/directive_resolver.js @@ -1,24 +1,18 @@ -import {Injector} from 'angular2/di'; import {Injectable} from 'angular2/src/di/annotations_impl'; import {Type, isPresent, BaseException, stringify} from 'angular2/src/facade/lang'; -import {Directive, Component} from '../annotations_impl/annotations'; -import {DirectiveMetadata} from './directive_metadata'; +import {Directive} from '../annotations_impl/annotations'; import {reflector} from 'angular2/src/reflection/reflection'; @Injectable() -export class DirectiveMetadataReader { - read(type:Type):DirectiveMetadata { +export class DirectiveResolver { + resolve(type:Type):Directive { var annotations = reflector.annotations(type); if (isPresent(annotations)) { for (var i=0; i; + metadata: DirectiveMetadata; + publishAs: List; - constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, annotation:Directive) { + constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, + resolvedInjectables:List, metadata:DirectiveMetadata, annotation: Directive) { super(key, factory, dependencies, providedAsPromise); - this.callOnDestroy = isPresent(annotation) && annotation.hasLifecycleHook(onDestroy); - this.callOnChange = isPresent(annotation) && annotation.hasLifecycleHook(onChange); - this.callOnAllChangesDone = isPresent(annotation) && annotation.hasLifecycleHook(onAllChangesDone); - this.annotation = annotation; - if (annotation instanceof Component && isPresent(annotation.injectables)) { - this.resolvedInjectables = Injector.resolve(annotation.injectables); + this.resolvedInjectables = resolvedInjectables; + this.metadata = metadata; + if (annotation instanceof Component) { + this.publishAs = annotation.publishAs; + } else { + this.publishAs = null; } } + get callOnDestroy() { + return this.metadata.callOnDestroy; + } + + get callOnChange() { + return this.metadata.callOnChange; + } + + get callOnAllChangesDone() { + return this.metadata.callOnAllChangesDone; + } + get displayName() { return this.key.displayName; } get eventEmitters():List { - return isPresent(this.annotation) && isPresent(this.annotation.events) ? this.annotation.events : []; + return isPresent(this.metadata) && isPresent(this.metadata.events) ? this.metadata.events : []; } - get hostActions() { //StringMap - return isPresent(this.annotation) && isPresent(this.annotation.hostActions) ? this.annotation.hostActions : {}; + get hostActions():Map { + return isPresent(this.metadata) && isPresent(this.metadata.hostActions) ? this.metadata.hostActions : MapWrapper.create(); } get changeDetection() { - if (this.annotation instanceof Component) { - var c:Component = this.annotation; - return c.changeDetection; + if (isPresent(metadata)) { + return metadata.changeDetection; } else { return null; } } - static createFromBinding(b:Binding, annotation:Directive):DirectiveBinding { + static createFromBinding(b:Binding, ann:Directive):DirectiveBinding { + if (isBlank(ann)) { + ann = new Directive(); + } var rb = b.resolve(); var deps = ListWrapper.map(rb.dependencies, DirectiveDependency.createFrom); - return new DirectiveBinding(rb.key, rb.factory, deps, rb.providedAsPromise, annotation); + var renderType; + var compileChildren = ann.compileChildren; + var resolvedInjectables = null; + var changeDetection = null; + if (ann instanceof Component) { + renderType = DirectiveMetadata.COMPONENT_TYPE; + if (isPresent(ann.injectables)) { + resolvedInjectables = Injector.resolve(ann.injectables); + } + changeDetection = ann.changeDetection; + } else { + renderType = DirectiveMetadata.DIRECTIVE_TYPE; + } + var readAttributes = []; + ListWrapper.forEach(deps, (dep) => { + if (isPresent(dep.attributeName)) { + ListWrapper.push(readAttributes, dep.attributeName); + } + }); + var metadata = new DirectiveMetadata({ + id: stringify(rb.key.token), + type: renderType, + selector: ann.selector, + compileChildren: compileChildren, + events: ann.events, + hostListeners: isPresent(ann.hostListeners) ? MapWrapper.createFromStringMap(ann.hostListeners) : null, + hostProperties: isPresent(ann.hostProperties) ? MapWrapper.createFromStringMap(ann.hostProperties) : null, + hostAttributes: isPresent(ann.hostAttributes) ? MapWrapper.createFromStringMap(ann.hostAttributes) : null, + hostActions: isPresent(ann.hostActions) ? MapWrapper.createFromStringMap(ann.hostActions) : null, + properties: isPresent(ann.properties) ? MapWrapper.createFromStringMap(ann.properties) : null, + readAttributes: readAttributes, + callOnDestroy: ann.hasLifecycleHook(onDestroy), + callOnChange: ann.hasLifecycleHook(onChange), + callOnAllChangesDone: ann.hasLifecycleHook(onAllChangesDone), + changeDetection: changeDetection + }); + return new DirectiveBinding(rb.key, rb.factory, deps, rb.providedAsPromise, resolvedInjectables, metadata, ann); } static createFromType(type:Type, annotation:Directive):DirectiveBinding { var binding = new Binding(type, {toClass: type}); return DirectiveBinding.createFromBinding(binding, annotation); } + } // TODO(rado): benchmark and consider rolling in as ElementInjector fields. @@ -476,7 +528,7 @@ export class ProtoElementInjector { _createHostActionAccessors(b:DirectiveBinding) { var res = []; - StringMapWrapper.forEach(b.hostActions, (actionExpression, actionName) => { + MapWrapper.forEach(b.hostActions, (actionExpression, actionName) => { ListWrapper.push(res, new HostActionAccessor(actionExpression, reflector.getter(actionName))) }); return res; @@ -627,8 +679,7 @@ export class ElementInjector extends TreeNode { var p = this._proto; if (isPresent(p._keyId0)) this._getDirectiveByKeyId(p._keyId0); if (isPresent(shadowDomAppInjector)) { - var componentAnnotation:Component = this._proto._binding0.annotation; - var publishAs = componentAnnotation.publishAs; + var publishAs = this._proto._binding0.publishAs; if (isPresent(publishAs) && publishAs.length > 0) { // If there's a component directive on this element injector, then // 0-th key must contain the directive itself. diff --git a/modules/angular2/src/core/compiler/proto_view_factory.js b/modules/angular2/src/core/compiler/proto_view_factory.js index cdce000b29..3e2c1bd2b5 100644 --- a/modules/angular2/src/core/compiler/proto_view_factory.js +++ b/modules/angular2/src/core/compiler/proto_view_factory.js @@ -4,14 +4,15 @@ 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, DirectiveIndex, BindingRecord, DirectiveRecord, ProtoChangeDetector, ChangeDetectorDefinition} from 'angular2/change_detection'; -import {Component} from '../annotations_impl/annotations'; +import { + ChangeDetection, DirectiveIndex, BindingRecord, DirectiveRecord, + ProtoChangeDetector, DEFAULT, ChangeDetectorDefinition +} from 'angular2/change_detection'; import * as renderApi from 'angular2/src/render/api'; import {AppProtoView} from './view'; import {ProtoElementInjector, DirectiveBinding} from './element_injector'; - class BindingRecordsCreator { _directiveRecordsMap; _textNodeIndex:number; @@ -21,26 +22,31 @@ class BindingRecordsCreator { this._textNodeIndex = 0; } - getBindingRecords(elementBinders:List, sortedDirectives:List):List { + getBindingRecords(elementBinders:List, + allDirectiveMetadatas:List + ):List { var bindings = []; for (var boundElementIndex = 0; boundElementIndex < elementBinders.length; boundElementIndex++) { var renderElementBinder = elementBinders[boundElementIndex]; bindings = ListWrapper.concat(bindings, this._createTextNodeRecords(renderElementBinder)); bindings = ListWrapper.concat(bindings, this._createElementPropertyRecords(boundElementIndex, renderElementBinder)); - bindings = ListWrapper.concat(bindings, this._createDirectiveRecords(boundElementIndex, sortedDirectives[boundElementIndex])); + bindings = ListWrapper.concat(bindings, this._createDirectiveRecords(boundElementIndex, + renderElementBinder.directives, allDirectiveMetadatas)); } return bindings; } - getDirectiveRecords(sortedDirectives:List): List { + getDirectiveRecords( + elementBinders:List, + allDirectiveMetadatas:List): List { var directiveRecords = []; - for (var elementIndex = 0; elementIndex < sortedDirectives.length; ++elementIndex) { - var dirs = sortedDirectives[elementIndex].directives; + for (var elementIndex = 0; elementIndex < elementBinders.length; ++elementIndex) { + var dirs = elementBinders[elementIndex].directives; for (var dirIndex = 0; dirIndex < dirs.length; ++dirIndex) { - ListWrapper.push(directiveRecords, this._getDirectiveRecord(elementIndex, dirIndex, dirs[dirIndex])); + ListWrapper.push(directiveRecords, this._getDirectiveRecord(elementIndex, dirIndex, allDirectiveMetadatas[dirs[dirIndex].directiveIndex])); } } @@ -60,17 +66,19 @@ class BindingRecordsCreator { return res; } - _createDirectiveRecords(boundElementIndex:number, sortedDirectives:SortedDirectives) { + _createDirectiveRecords(boundElementIndex:number, directiveBinders:List, + allDirectiveMetadatas:List) { var res = []; - for (var i = 0; i < sortedDirectives.renderDirectives.length; i++) { - var directiveBinder = sortedDirectives.renderDirectives[i]; + for (var i = 0; i < directiveBinders.length; i++) { + var directiveBinder = directiveBinders[i]; + var directiveMetadata = allDirectiveMetadatas[directiveBinder.directiveIndex]; // directive properties MapWrapper.forEach(directiveBinder.propertyBindings, (astWithSource, propertyName) => { // TODO: these setters should eventually be created by change detection, to make // it monomorphic! var setter = reflector.setter(propertyName); - var directiveRecord = this._getDirectiveRecord(boundElementIndex, i, sortedDirectives.directives[i]); + var directiveRecord = this._getDirectiveRecord(boundElementIndex, i, directiveMetadata); var b = BindingRecord.createForDirective(astWithSource, propertyName, setter, directiveRecord); ListWrapper.push(res, b); }); @@ -85,22 +93,21 @@ class BindingRecordsCreator { return res; } - _getDirectiveRecord(boundElementIndex:number, directiveIndex:number, binding:DirectiveBinding): DirectiveRecord { + _getDirectiveRecord(boundElementIndex:number, directiveIndex:number, directiveMetadata:renderApi.DirectiveMetadata): DirectiveRecord { var id = boundElementIndex * 100 + directiveIndex; if (!MapWrapper.contains(this._directiveRecordsMap, id)) { - var changeDetection = binding.changeDetection; + var changeDetection = directiveMetadata.changeDetection; MapWrapper.set(this._directiveRecordsMap, id, new DirectiveRecord(new DirectiveIndex(boundElementIndex, directiveIndex), - binding.callOnAllChangesDone, binding.callOnChange, changeDetection)); + directiveMetadata.callOnAllChangesDone, directiveMetadata.callOnChange, changeDetection)); } return MapWrapper.get(this._directiveRecordsMap, id); } } - @Injectable() export class ProtoViewFactory { _changeDetection:ChangeDetection; @@ -109,32 +116,117 @@ export class ProtoViewFactory { this._changeDetection = changeDetection; } - createProtoView(parentProtoView:AppProtoView, componentBinding:DirectiveBinding, - renderProtoView: renderApi.ProtoViewDto, directives:List):AppProtoView { + /** + * Returns the data needed to create ChangeDetectors + * for the given ProtoView and all nested ProtoViews. + */ + getChangeDetectorDefinitions(hostComponentMetadata:renderApi.DirectiveMetadata, + rootRenderProtoView: renderApi.ProtoViewDto, allRenderDirectiveMetadata:List):List { + var nestedPvsWithIndex = this._collectNestedProtoViews(rootRenderProtoView); + var nestedPvVariableBindings = this._collectNestedProtoViewsVariableBindings(nestedPvsWithIndex); + var nestedPvVariableNames = this._collectNestedProtoViewsVariableNames(nestedPvsWithIndex, nestedPvVariableBindings); + return this._getChangeDetectorDefinitions( + hostComponentMetadata, + nestedPvsWithIndex, + nestedPvVariableNames, + allRenderDirectiveMetadata + ); + } + + createAppProtoViews(hostComponentBinding:DirectiveBinding, + rootRenderProtoView: renderApi.ProtoViewDto, allDirectives:List):List { + var allRenderDirectiveMetadata = ListWrapper.map(allDirectives, directiveBinding => directiveBinding.metadata ); + var nestedPvsWithIndex = this._collectNestedProtoViews(rootRenderProtoView); + var nestedPvVariableBindings = this._collectNestedProtoViewsVariableBindings(nestedPvsWithIndex); + var nestedPvVariableNames = this._collectNestedProtoViewsVariableNames(nestedPvsWithIndex, nestedPvVariableBindings); + var changeDetectorDefs = this._getChangeDetectorDefinitions( + hostComponentBinding.metadata, nestedPvsWithIndex, nestedPvVariableNames, allRenderDirectiveMetadata + ); + var protoChangeDetectors = ListWrapper.map( + changeDetectorDefs, changeDetectorDef => this._changeDetection.createProtoChangeDetector(changeDetectorDef) + ); + var appProtoViews = ListWrapper.createFixedSize(nestedPvsWithIndex.length); + ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex) => { + var appProtoView = this._createAppProtoView( + pvWithIndex.renderProtoView, + protoChangeDetectors[pvWithIndex.index], + nestedPvVariableBindings[pvWithIndex.index], + allDirectives + ); + if (isPresent(pvWithIndex.parentIndex)) { + var parentView = appProtoViews[pvWithIndex.parentIndex]; + parentView.elementBinders[pvWithIndex.boundElementIndex].nestedProtoView = appProtoView; + } + appProtoViews[pvWithIndex.index] = appProtoView; + }); + return appProtoViews; + } + + _collectNestedProtoViews(renderProtoView:renderApi.ProtoViewDto, parentIndex:number = null, boundElementIndex = null, result:List = null):List { + if (isBlank(result)) { + result = []; + } + ListWrapper.push(result, new RenderProtoViewWithIndex(renderProtoView, result.length, parentIndex, boundElementIndex)); + var currentIndex = result.length - 1; + var childBoundElementIndex = 0; + ListWrapper.forEach(renderProtoView.elementBinders, (elementBinder) => { + if (isPresent(elementBinder.nestedProtoView)) { + this._collectNestedProtoViews(elementBinder.nestedProtoView, currentIndex, childBoundElementIndex, result); + } + childBoundElementIndex++; + }); + return result; + } + + _getChangeDetectorDefinitions( + hostComponentMetadata:renderApi.DirectiveMetadata, + nestedPvsWithIndex: List, + nestedPvVariableNames: List>, + allRenderDirectiveMetadata:List):List { + return ListWrapper.map(nestedPvsWithIndex, (pvWithIndex) => { + var elementBinders = pvWithIndex.renderProtoView.elementBinders; + var bindingRecordsCreator = new BindingRecordsCreator(); + var bindingRecords = bindingRecordsCreator.getBindingRecords(elementBinders, allRenderDirectiveMetadata); + var directiveRecords = bindingRecordsCreator.getDirectiveRecords(elementBinders, allRenderDirectiveMetadata); + var strategyName = DEFAULT; + var typeString; + if (pvWithIndex.renderProtoView.type === renderApi.ProtoViewDto.COMPONENT_VIEW_TYPE) { + strategyName = hostComponentMetadata.changeDetection; + typeString = 'comp'; + } else if (pvWithIndex.renderProtoView.type === renderApi.ProtoViewDto.HOST_VIEW_TYPE) { + typeString = 'host'; + } else { + typeString = 'embedded'; + } + var id = `${hostComponentMetadata.id}_${typeString}_${pvWithIndex.index}`; + var variableNames = nestedPvVariableNames[pvWithIndex.index]; + return new ChangeDetectorDefinition(id, strategyName, variableNames, bindingRecords, directiveRecords); + }); + } + + _createAppProtoView( + renderProtoView: renderApi.ProtoViewDto, + protoChangeDetector: ProtoChangeDetector, + variableBindings: Map, + allDirectives:List + ):AppProtoView { var elementBinders = renderProtoView.elementBinders; - var sortedDirectives = ListWrapper.map(elementBinders, b => new SortedDirectives(b.directives, directives)); - - var variableBindings = this._createVariableBindings(renderProtoView); - var protoLocals = this._createProtoLocals(variableBindings); - var variableNames = this._createVariableNames(parentProtoView, protoLocals); - - var protoChangeDetector = this._createProtoChangeDetector(elementBinders, sortedDirectives, componentBinding, variableNames); - var protoView = new AppProtoView(renderProtoView.render, protoChangeDetector, variableBindings, protoLocals, variableNames); + var protoView = new AppProtoView(renderProtoView.render, protoChangeDetector, variableBindings); // TODO: vsavkin refactor to pass element binders into proto view - this._createElementBinders(protoView, elementBinders, sortedDirectives) - this._bindDirectiveEvents(protoView, sortedDirectives); + this._createElementBinders(protoView, elementBinders, allDirectives); + this._bindDirectiveEvents(protoView, elementBinders); return protoView; } - _createProtoLocals(varBindings:Map):Map { - var protoLocals = MapWrapper.create(); - MapWrapper.forEach(varBindings, (mappedName, varName) => { - MapWrapper.set(protoLocals, mappedName, null); + _collectNestedProtoViewsVariableBindings( + nestedPvsWithIndex: List + ):List> { + return ListWrapper.map(nestedPvsWithIndex, (pvWithIndex) => { + return this._createVariableBindings(pvWithIndex.renderProtoView); }); - return protoLocals; } _createVariableBindings(renderProtoView):Map { @@ -150,42 +242,46 @@ export class ProtoViewFactory { return variableBindings; } - _createVariableNames(parentProtoView, protoLocals):List { - var variableNames = isPresent(parentProtoView) ? ListWrapper.clone(parentProtoView.variableNames) : []; - MapWrapper.forEach(protoLocals, (v, local) => { + _collectNestedProtoViewsVariableNames( + nestedPvsWithIndex: List, + nestedPvVariableBindings:List> + ):List> { + var nestedPvVariableNames = ListWrapper.createFixedSize(nestedPvsWithIndex.length); + ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex) => { + var parentVariableNames = isPresent(pvWithIndex.parentIndex) ? nestedPvVariableNames[pvWithIndex.parentIndex] : null; + nestedPvVariableNames[pvWithIndex.index] = this._createVariableNames( + parentVariableNames, nestedPvVariableBindings[pvWithIndex.index] + ); + }); + return nestedPvVariableNames; + } + + _createVariableNames(parentVariableNames, variableBindings):List { + var variableNames = isPresent(parentVariableNames) ? ListWrapper.clone(parentVariableNames) : []; + MapWrapper.forEach(variableBindings, (local, v) => { ListWrapper.push(variableNames, local); }); return variableNames; } - _createProtoChangeDetector(elementBinders, sortedDirectives, componentBinding, variableNames):ProtoChangeDetector { - var bindingRecordsCreator = new BindingRecordsCreator(); - var bindingRecords = bindingRecordsCreator.getBindingRecords(elementBinders, sortedDirectives); - var directiveRecords = bindingRecordsCreator.getDirectiveRecords(sortedDirectives); - - var changeDetection = null; - var name = 'root'; - if (isPresent(componentBinding)) { - var componentAnnotation:Component = componentBinding.annotation; - changeDetection = componentAnnotation.changeDetection; - name = 'dummy'; - } - - var definition = new ChangeDetectorDefinition(name, changeDetection, variableNames, bindingRecords, directiveRecords); - return this._changeDetection.createProtoChangeDetector(definition); - } - - _createElementBinders(protoView, elementBinders, sortedDirectives) { + _createElementBinders(protoView, elementBinders, allDirectiveBindings) { for (var i=0; i allDirectiveBindings[dir.directiveIndex] ); + var componentDirectiveBinding = null; + if (directiveBindings.length > 0) { + if (directiveBindings[0].metadata.type === renderApi.DirectiveMetadata.COMPONENT_TYPE) { + componentDirectiveBinding = directiveBindings[0]; + } + } var protoElementInjector = this._createProtoElementInjector( - i, parentPeiWithDistance, dirs, renderElementBinder); + i, parentPeiWithDistance, renderElementBinder, componentDirectiveBinding, directiveBindings); - this._createElementBinder(protoView, i, renderElementBinder, protoElementInjector, dirs); + this._createElementBinder(protoView, i, renderElementBinder, protoElementInjector, componentDirectiveBinding); } } @@ -205,22 +301,22 @@ export class ProtoViewFactory { return new ParentProtoElementInjectorWithDistance(null, -1); } - _createProtoElementInjector(binderIndex, parentPeiWithDistance, sortedDirectives, renderElementBinder) { + _createProtoElementInjector(binderIndex, parentPeiWithDistance, renderElementBinder, componentDirectiveBinding, directiveBindings) { var protoElementInjector = null; // Create a protoElementInjector for any element that either has bindings *or* has one // or more var- defined. Elements with a var- defined need a their own element injector // so that, when hydrating, $implicit can be set to the element. var hasVariables = MapWrapper.size(renderElementBinder.variableBindings) > 0; - if (sortedDirectives.directives.length > 0 || hasVariables) { + if (directiveBindings.length > 0 || hasVariables) { protoElementInjector = new ProtoElementInjector( parentPeiWithDistance.protoElementInjector, binderIndex, - sortedDirectives.directives, - isPresent(sortedDirectives.componentDirective), parentPeiWithDistance.distance + directiveBindings, + isPresent(componentDirectiveBinding), parentPeiWithDistance.distance ); protoElementInjector.attributes = renderElementBinder.readAttributes; if (hasVariables) { - protoElementInjector.exportComponent = isPresent(sortedDirectives.componentDirective); - protoElementInjector.exportElement = isBlank(sortedDirectives.componentDirective); + protoElementInjector.exportComponent = isPresent(componentDirectiveBinding); + protoElementInjector.exportElement = isBlank(componentDirectiveBinding); // experiment var exportImplicitName = MapWrapper.get(renderElementBinder.variableBindings, '\$implicit'); @@ -232,7 +328,7 @@ export class ProtoViewFactory { return protoElementInjector; } - _createElementBinder(protoView, boundElementIndex, renderElementBinder, protoElementInjector, sortedDirectives) { + _createElementBinder(protoView, boundElementIndex, renderElementBinder, protoElementInjector, componentDirectiveBinding) { var parent = null; if (renderElementBinder.parentIndex !== -1) { parent = protoView.elementBinders[renderElementBinder.parentIndex]; @@ -241,7 +337,7 @@ export class ProtoViewFactory { parent, renderElementBinder.distanceToParent, protoElementInjector, - sortedDirectives.componentDirective + componentDirectiveBinding ); protoView.bindEvent(renderElementBinder.eventBindings, boundElementIndex, -1); // variables @@ -255,9 +351,9 @@ export class ProtoViewFactory { return elBinder; } - _bindDirectiveEvents(protoView, sortedDirectives:List) { - for (var boundElementIndex = 0; boundElementIndex < sortedDirectives.length; ++boundElementIndex) { - var dirs = sortedDirectives[boundElementIndex].renderDirectives; + _bindDirectiveEvents(protoView, elementBinders:List) { + for (var boundElementIndex = 0; boundElementIndex < elementBinders.length; ++boundElementIndex) { + var dirs = elementBinders[boundElementIndex].directives; for (var i = 0; i < dirs.length; i++) { var directiveBinder = dirs[i]; @@ -268,28 +364,16 @@ export class ProtoViewFactory { } } -class SortedDirectives { - componentDirective: DirectiveBinding; - renderDirectives: List; - directives: List; - - constructor(renderDirectives, allDirectives) { - this.renderDirectives = []; - this.directives = []; - this.componentDirective = null; - ListWrapper.forEach(renderDirectives, (renderDirectiveBinder) => { - var directiveBinding = allDirectives[renderDirectiveBinder.directiveIndex]; - if (directiveBinding.annotation instanceof Component) { - // component directives need to be the first binding in ElementInjectors! - this.componentDirective = directiveBinding; - ListWrapper.insert(this.renderDirectives, 0, renderDirectiveBinder); - ListWrapper.insert(this.directives, 0, directiveBinding); - } else { - ListWrapper.push(this.renderDirectives, renderDirectiveBinder); - ListWrapper.push(this.directives, directiveBinding); - } - }); - +class RenderProtoViewWithIndex { + renderProtoView:renderApi.ProtoViewDto; + index:number; + parentIndex:number; + boundElementIndex:number; + constructor(renderProtoView:renderApi.ProtoViewDto, index:number, parentIndex:number, boundElementIndex:number) { + this.renderProtoView = renderProtoView; + this.index = index; + this.parentIndex = parentIndex; + this.boundElementIndex = boundElementIndex; } } diff --git a/modules/angular2/src/core/compiler/view.js b/modules/angular2/src/core/compiler/view.js index b016f86037..d239368ce4 100644 --- a/modules/angular2/src/core/compiler/view.js +++ b/modules/angular2/src/core/compiler/view.js @@ -168,20 +168,21 @@ export class AppProtoView { variableBindings: Map; protoLocals:Map; bindings:List; - variableNames:List; render:renderApi.RenderProtoViewRef; constructor( render:renderApi.RenderProtoViewRef, protoChangeDetector:ProtoChangeDetector, - variableBindings:Map, - protoLocals:Map, - variableNames:List) { + variableBindings:Map) { this.render = render; this.elementBinders = []; this.variableBindings = variableBindings; - this.protoLocals = protoLocals; - this.variableNames = variableNames; + this.protoLocals = MapWrapper.create(); + if (isPresent(variableBindings)) { + MapWrapper.forEach(variableBindings, (templateName, _) => { + MapWrapper.set(this.protoLocals, templateName, null); + }); + } this.protoChangeDetector = protoChangeDetector; } diff --git a/modules/angular2/src/core/compiler/view_manager_utils.js b/modules/angular2/src/core/compiler/view_manager_utils.js index 2d7e175f0f..4c7da85af6 100644 --- a/modules/angular2/src/core/compiler/view_manager_utils.js +++ b/modules/angular2/src/core/compiler/view_manager_utils.js @@ -7,15 +7,15 @@ import * as viewModule from './view'; import * as avmModule from './view_manager'; import {Renderer} from 'angular2/src/render/api'; import {BindingPropagationConfig, Locals} from 'angular2/change_detection'; -import {DirectiveMetadataReader} from './directive_metadata_reader'; +import {DirectiveResolver} from './directive_resolver'; import {RenderViewRef} from 'angular2/src/render/api'; @Injectable() export class AppViewManagerUtils { - _metadataReader:DirectiveMetadataReader; + _directiveResolver:DirectiveResolver; - constructor(metadataReader:DirectiveMetadataReader) { - this._metadataReader = metadataReader; + constructor(metadataReader:DirectiveResolver) { + this._directiveResolver = metadataReader; } getComponentInstance(parentView:viewModule.AppView, boundElementIndex:number):any { @@ -171,7 +171,7 @@ export class AppViewManagerUtils { if (isBlank(injector)) { injector = elementInjector.getLightDomAppInjector(); } - var annotation = this._metadataReader.read(componentBinding.token).annotation; + var annotation = this._directiveResolver.resolve(componentBinding.token); var componentDirective = eli.DirectiveBinding.createFromBinding(componentBinding, annotation); elementInjector.dynamicallyCreateComponent(componentDirective, injector); } diff --git a/modules/angular2/src/render/api.js b/modules/angular2/src/render/api.js index 44570e573f..fab06ba55b 100644 --- a/modules/angular2/src/render/api.js +++ b/modules/angular2/src/render/api.js @@ -93,7 +93,7 @@ export class ProtoViewDto { static get COMPONENT_VIEW_TYPE() { return 1; } // A view that is embedded into another View via a