diff --git a/modules/angular2/src/common/directives/ng_class.ts b/modules/angular2/src/common/directives/ng_class.ts index 2f77b0767f..cae1ca01e6 100644 --- a/modules/angular2/src/common/directives/ng_class.ts +++ b/modules/angular2/src/common/directives/ng_class.ts @@ -169,10 +169,10 @@ export class NgClass implements DoCheck, OnDestroy { if (className.indexOf(' ') > -1) { var classes = className.split(/\s+/g); for (var i = 0, len = classes.length; i < len; i++) { - this._renderer.setElementClass(this._ngEl, classes[i], enabled); + this._renderer.setElementClass(this._ngEl.nativeElement, classes[i], enabled); } } else { - this._renderer.setElementClass(this._ngEl, className, enabled); + this._renderer.setElementClass(this._ngEl.nativeElement, className, enabled); } } } diff --git a/modules/angular2/src/common/directives/ng_for.ts b/modules/angular2/src/common/directives/ng_for.ts index 7529f14f08..ec768c4981 100644 --- a/modules/angular2/src/common/directives/ng_for.ts +++ b/modules/angular2/src/common/directives/ng_for.ts @@ -6,7 +6,7 @@ import { IterableDiffers, ViewContainerRef, TemplateRef, - ViewRef + EmbeddedViewRef } from 'angular2/core'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; @@ -110,7 +110,8 @@ export class NgFor implements DoCheck { } for (var i = 0, ilen = this._viewContainer.length; i < ilen; i++) { - this._viewContainer.get(i).setLocal('last', i === ilen - 1); + var viewRef = this._viewContainer.get(i); + viewRef.setLocal('last', i === ilen - 1); } } @@ -153,7 +154,7 @@ export class NgFor implements DoCheck { } class RecordViewTuple { - view: ViewRef; + view: EmbeddedViewRef; record: any; constructor(record, view) { this.record = record; diff --git a/modules/angular2/src/common/directives/ng_style.ts b/modules/angular2/src/common/directives/ng_style.ts index 90b5d74483..99d658a0f9 100644 --- a/modules/angular2/src/common/directives/ng_style.ts +++ b/modules/angular2/src/common/directives/ng_style.ts @@ -92,6 +92,6 @@ export class NgStyle implements DoCheck { } private _setStyle(name: string, val: string): void { - this._renderer.setElementStyle(this._ngEl, name, val); + this._renderer.setElementStyle(this._ngEl.nativeElement, name, val); } } diff --git a/modules/angular2/src/common/forms/directives/checkbox_value_accessor.ts b/modules/angular2/src/common/forms/directives/checkbox_value_accessor.ts index 3e52b078b3..0943268392 100644 --- a/modules/angular2/src/common/forms/directives/checkbox_value_accessor.ts +++ b/modules/angular2/src/common/forms/directives/checkbox_value_accessor.ts @@ -27,7 +27,7 @@ export class CheckboxControlValueAccessor implements ControlValueAccessor { constructor(private _renderer: Renderer, private _elementRef: ElementRef) {} writeValue(value: any): void { - this._renderer.setElementProperty(this._elementRef, 'checked', value); + this._renderer.setElementProperty(this._elementRef.nativeElement, 'checked', value); } registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; } registerOnTouched(fn: () => {}): void { this.onTouched = fn; } diff --git a/modules/angular2/src/common/forms/directives/default_value_accessor.ts b/modules/angular2/src/common/forms/directives/default_value_accessor.ts index 67c88b3fac..1cc88993a5 100644 --- a/modules/angular2/src/common/forms/directives/default_value_accessor.ts +++ b/modules/angular2/src/common/forms/directives/default_value_accessor.ts @@ -31,7 +31,7 @@ export class DefaultValueAccessor implements ControlValueAccessor { writeValue(value: any): void { var normalizedValue = isBlank(value) ? '' : value; - this._renderer.setElementProperty(this._elementRef, 'value', normalizedValue); + this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue); } registerOnChange(fn: (_: any) => void): void { this.onChange = fn; } diff --git a/modules/angular2/src/common/forms/directives/number_value_accessor.ts b/modules/angular2/src/common/forms/directives/number_value_accessor.ts index 41b04f0481..1122c60625 100644 --- a/modules/angular2/src/common/forms/directives/number_value_accessor.ts +++ b/modules/angular2/src/common/forms/directives/number_value_accessor.ts @@ -31,7 +31,7 @@ export class NumberValueAccessor implements ControlValueAccessor { constructor(private _renderer: Renderer, private _elementRef: ElementRef) {} writeValue(value: number): void { - this._renderer.setElementProperty(this._elementRef, 'value', value); + this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', value); } registerOnChange(fn: (_: number) => void): void { diff --git a/modules/angular2/src/common/forms/directives/select_control_value_accessor.ts b/modules/angular2/src/common/forms/directives/select_control_value_accessor.ts index 2fe527af13..1cb88f6db8 100644 --- a/modules/angular2/src/common/forms/directives/select_control_value_accessor.ts +++ b/modules/angular2/src/common/forms/directives/select_control_value_accessor.ts @@ -51,7 +51,7 @@ export class SelectControlValueAccessor implements ControlValueAccessor { writeValue(value: any): void { this.value = value; - this._renderer.setElementProperty(this._elementRef, 'value', value); + this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', value); } registerOnChange(fn: () => any): void { this.onChange = fn; } diff --git a/modules/angular2/src/common/forms/directives/shared.ts b/modules/angular2/src/common/forms/directives/shared.ts index a0a5bb53ec..e9a232b069 100644 --- a/modules/angular2/src/common/forms/directives/shared.ts +++ b/modules/angular2/src/common/forms/directives/shared.ts @@ -80,7 +80,6 @@ export function selectValueAccessor(dir: NgControl, var defaultAccessor; var builtinAccessor; var customAccessor; - valueAccessors.forEach(v => { if (v instanceof DefaultValueAccessor) { defaultAccessor = v; diff --git a/modules/angular2/src/compiler/change_definition_factory.ts b/modules/angular2/src/compiler/change_definition_factory.ts index ce7c940aa9..de12f8cbcd 100644 --- a/modules/angular2/src/compiler/change_definition_factory.ts +++ b/modules/angular2/src/compiler/change_definition_factory.ts @@ -1,4 +1,4 @@ -import {ListWrapper} from 'angular2/src/facade/collection'; +import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {reflector} from 'angular2/src/core/reflection/reflection'; @@ -43,7 +43,7 @@ export function createChangeDetectorDefinitions( class ProtoViewVisitor implements TemplateAstVisitor { viewIndex: number; - boundTextCount: number = 0; + nodeCount: number = 0; boundElementCount: number = 0; variableNames: string[] = []; bindingRecords: BindingRecord[] = []; @@ -57,6 +57,7 @@ class ProtoViewVisitor implements TemplateAstVisitor { } visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { + this.nodeCount++; this.boundElementCount++; templateVisitAll(this, ast.outputs); for (var i = 0; i < ast.directives.length; i++) { @@ -73,6 +74,7 @@ class ProtoViewVisitor implements TemplateAstVisitor { } visitElement(ast: ElementAst, context: any): any { + this.nodeCount++; if (ast.isBound()) { this.boundElementCount++; } @@ -132,14 +134,20 @@ class ProtoViewVisitor implements TemplateAstVisitor { } visitAttr(ast: AttrAst, context: any): any { return null; } visitBoundText(ast: BoundTextAst, context: any): any { - var boundTextIndex = this.boundTextCount++; - this.bindingRecords.push(BindingRecord.createForTextNode(ast.value, boundTextIndex)); + var nodeIndex = this.nodeCount++; + this.bindingRecords.push(BindingRecord.createForTextNode(ast.value, nodeIndex)); + return null; + } + visitText(ast: TextAst, context: any): any { + this.nodeCount++; return null; } - visitText(ast: TextAst, context: any): any { return null; } visitDirective(ast: DirectiveAst, directiveIndexAsNumber: number): any { var directiveIndex = new DirectiveIndex(this.boundElementCount - 1, directiveIndexAsNumber); var directiveMetadata = ast.directive; + var outputsArray = []; + StringMapWrapper.forEach(ast.directive.outputs, (eventName, dirProperty) => outputsArray.push( + [dirProperty, eventName])); var directiveRecord = new DirectiveRecord({ directiveIndex: directiveIndex, callAfterContentInit: @@ -153,7 +161,9 @@ class ProtoViewVisitor implements TemplateAstVisitor { callOnChanges: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1, callDoCheck: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1, callOnInit: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1, - changeDetection: directiveMetadata.changeDetection + callOnDestroy: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1, + changeDetection: directiveMetadata.changeDetection, + outputs: outputsArray }); this.directiveRecords.push(directiveRecord); diff --git a/modules/angular2/src/compiler/change_detector_compiler.ts b/modules/angular2/src/compiler/change_detector_compiler.ts index 1f42bd823a..db3b727b4a 100644 --- a/modules/angular2/src/compiler/change_detector_compiler.ts +++ b/modules/angular2/src/compiler/change_detector_compiler.ts @@ -3,6 +3,9 @@ import {SourceExpressions, moduleRef} from './source_module'; import { ChangeDetectorJITGenerator } from 'angular2/src/core/change_detection/change_detection_jit_generator'; +import {AbstractChangeDetector} from 'angular2/src/core/change_detection/abstract_change_detector'; +import {ChangeDetectionUtil} from 'angular2/src/core/change_detection/change_detection_util'; +import {ChangeDetectorState} from 'angular2/src/core/change_detection/constants'; import {createChangeDetectorDefinitions} from './change_definition_factory'; import {IS_DART, isJsObject, CONST_EXPR} from 'angular2/src/facade/lang'; @@ -23,6 +26,12 @@ const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector"; const UTIL = "ChangeDetectionUtil"; const CHANGE_DETECTOR_STATE = "ChangeDetectorState"; +export const CHANGE_DETECTION_JIT_IMPORTS = CONST_EXPR({ + 'AbstractChangeDetector': AbstractChangeDetector, + 'ChangeDetectionUtil': ChangeDetectionUtil, + 'ChangeDetectorState': ChangeDetectorState +}); + var ABSTRACT_CHANGE_DETECTOR_MODULE = moduleRef( `package:angular2/src/core/change_detection/abstract_change_detector${MODULE_SUFFIX}`); var UTIL_MODULE = @@ -45,14 +54,8 @@ export class ChangeDetectionCompiler { } private _createChangeDetectorFactory(definition: ChangeDetectorDefinition): Function { - if (IS_DART || !this._genConfig.useJit) { - var proto = new DynamicProtoChangeDetector(definition); - return (dispatcher) => proto.instantiate(dispatcher); - } else { - return new ChangeDetectorJITGenerator(definition, UTIL, ABSTRACT_CHANGE_DETECTOR, - CHANGE_DETECTOR_STATE) - .generate(); - } + var proto = new DynamicProtoChangeDetector(definition); + return () => proto.instantiate(); } compileComponentCodeGen(componentType: CompileTypeMetadata, strategy: ChangeDetectionStrategy, @@ -81,7 +84,7 @@ export class ChangeDetectionCompiler { definition, `${UTIL_MODULE}${UTIL}`, `${ABSTRACT_CHANGE_DETECTOR_MODULE}${ABSTRACT_CHANGE_DETECTOR}`, `${CONSTANTS_MODULE}${CHANGE_DETECTOR_STATE}`); - factories.push(`function(dispatcher) { return new ${codegen.typeName}(dispatcher); }`); + factories.push(`function() { return new ${codegen.typeName}(); }`); sourcePart = codegen.generateSource(); } index++; diff --git a/modules/angular2/src/compiler/command_compiler.ts b/modules/angular2/src/compiler/command_compiler.ts deleted file mode 100644 index 5819b4da1e..0000000000 --- a/modules/angular2/src/compiler/command_compiler.ts +++ /dev/null @@ -1,375 +0,0 @@ -import {isPresent, isBlank, Type, isString, StringWrapper, IS_DART} from 'angular2/src/facade/lang'; -import {SetWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection'; -import { - TemplateCmd, - TextCmd, - NgContentCmd, - BeginElementCmd, - EndElementCmd, - BeginComponentCmd, - EndComponentCmd, - EmbeddedTemplateCmd, - CompiledComponentTemplate -} from 'angular2/src/core/linker/template_commands'; -import { - TemplateAst, - TemplateAstVisitor, - NgContentAst, - EmbeddedTemplateAst, - ElementAst, - VariableAst, - BoundEventAst, - BoundElementPropertyAst, - AttrAst, - BoundTextAst, - TextAst, - DirectiveAst, - BoundDirectivePropertyAst, - templateVisitAll -} from './template_ast'; -import {CompileTypeMetadata, CompileDirectiveMetadata} from './directive_metadata'; -import {SourceExpressions, SourceExpression, moduleRef} from './source_module'; - -import {ViewEncapsulation} from 'angular2/src/core/metadata/view'; -import { - escapeSingleQuoteString, - codeGenConstConstructorCall, - codeGenValueFn, - MODULE_SUFFIX -} from './util'; -import {Injectable} from 'angular2/src/core/di'; - -export var TEMPLATE_COMMANDS_MODULE_REF = - moduleRef(`package:angular2/src/core/linker/template_commands${MODULE_SUFFIX}`); - -const IMPLICIT_TEMPLATE_VAR = '\$implicit'; -const CLASS_ATTR = 'class'; -const STYLE_ATTR = 'style'; - -@Injectable() -export class CommandCompiler { - compileComponentRuntime(component: CompileDirectiveMetadata, template: TemplateAst[], - changeDetectorFactories: Function[], - componentTemplateFactory: Function): TemplateCmd[] { - var visitor = new CommandBuilderVisitor( - new RuntimeCommandFactory(component, componentTemplateFactory, changeDetectorFactories), 0); - templateVisitAll(visitor, template); - return visitor.result; - } - - compileComponentCodeGen(component: CompileDirectiveMetadata, template: TemplateAst[], - changeDetectorFactoryExpressions: string[], - componentTemplateFactory: Function): SourceExpression { - var visitor = - new CommandBuilderVisitor(new CodegenCommandFactory(component, componentTemplateFactory, - changeDetectorFactoryExpressions), - 0); - templateVisitAll(visitor, template); - return new SourceExpression([], codeGenArray(visitor.result)); - } -} - -interface CommandFactory { - createText(value: string, isBound: boolean, ngContentIndex: number): R; - createNgContent(index: number, ngContentIndex: number): R; - createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[], - variableNameAndValues: string[], directives: CompileDirectiveMetadata[], - isBound: boolean, ngContentIndex: number): R; - createEndElement(): R; - createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[], - variableNameAndValues: string[], directives: CompileDirectiveMetadata[], - encapsulation: ViewEncapsulation, ngContentIndex: number): R; - createEndComponent(): R; - createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[], - variableNameAndValues: string[], directives: CompileDirectiveMetadata[], - isMerged: boolean, ngContentIndex: number, children: R[]): R; -} - -class RuntimeCommandFactory implements CommandFactory { - constructor(private component: CompileDirectiveMetadata, - private componentTemplateFactory: Function, - private changeDetectorFactories: Function[]) {} - private _mapDirectives(directives: CompileDirectiveMetadata[]): Type[] { - return directives.map(directive => directive.type.runtime); - } - - createText(value: string, isBound: boolean, ngContentIndex: number): TemplateCmd { - return new TextCmd(value, isBound, ngContentIndex); - } - createNgContent(index: number, ngContentIndex: number): TemplateCmd { - return new NgContentCmd(index, ngContentIndex); - } - createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[], - variableNameAndValues: string[], directives: CompileDirectiveMetadata[], - isBound: boolean, ngContentIndex: number): TemplateCmd { - return new BeginElementCmd(name, attrNameAndValues, eventTargetAndNames, variableNameAndValues, - this._mapDirectives(directives), isBound, ngContentIndex); - } - createEndElement(): TemplateCmd { return new EndElementCmd(); } - createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[], - variableNameAndValues: string[], directives: CompileDirectiveMetadata[], - encapsulation: ViewEncapsulation, ngContentIndex: number): TemplateCmd { - var nestedTemplateAccessor = this.componentTemplateFactory(directives[0]); - return new BeginComponentCmd(name, attrNameAndValues, eventTargetAndNames, - variableNameAndValues, this._mapDirectives(directives), - encapsulation, ngContentIndex, nestedTemplateAccessor); - } - createEndComponent(): TemplateCmd { return new EndComponentCmd(); } - createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[], - variableNameAndValues: string[], directives: CompileDirectiveMetadata[], - isMerged: boolean, ngContentIndex: number, - children: TemplateCmd[]): TemplateCmd { - return new EmbeddedTemplateCmd(attrNameAndValues, variableNameAndValues, - this._mapDirectives(directives), isMerged, ngContentIndex, - this.changeDetectorFactories[embeddedTemplateIndex], children); - } -} - -class CodegenCommandFactory implements CommandFactory { - constructor(private component: CompileDirectiveMetadata, - private componentTemplateFactory: Function, - private changeDetectorFactoryExpressions: string[]) {} - - createText(value: string, isBound: boolean, ngContentIndex: number): Expression { - return new Expression( - `${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'TextCmd')}(${escapeSingleQuoteString(value)}, ${isBound}, ${ngContentIndex})`); - } - createNgContent(index: number, ngContentIndex: number): Expression { - return new Expression( - `${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'NgContentCmd')}(${index}, ${ngContentIndex})`); - } - createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[], - variableNameAndValues: string[], directives: CompileDirectiveMetadata[], - isBound: boolean, ngContentIndex: number): Expression { - var attrsExpression = codeGenArray(attrNameAndValues); - return new Expression( - `${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'BeginElementCmd')}(${escapeSingleQuoteString(name)}, ${attrsExpression}, ` + - `${codeGenArray(eventTargetAndNames)}, ${codeGenArray(variableNameAndValues)}, ${codeGenDirectivesArray(directives)}, ${isBound}, ${ngContentIndex})`); - } - createEndElement(): Expression { - return new Expression( - `${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'EndElementCmd')}()`); - } - createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[], - variableNameAndValues: string[], directives: CompileDirectiveMetadata[], - encapsulation: ViewEncapsulation, ngContentIndex: number): Expression { - var attrsExpression = codeGenArray(attrNameAndValues); - return new Expression( - `${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'BeginComponentCmd')}(${escapeSingleQuoteString(name)}, ${attrsExpression}, ` + - `${codeGenArray(eventTargetAndNames)}, ${codeGenArray(variableNameAndValues)}, ${codeGenDirectivesArray(directives)}, ${codeGenViewEncapsulation(encapsulation)}, ${ngContentIndex}, ${this.componentTemplateFactory(directives[0])})`); - } - createEndComponent(): Expression { - return new Expression( - `${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'EndComponentCmd')}()`); - } - createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[], - variableNameAndValues: string[], directives: CompileDirectiveMetadata[], - isMerged: boolean, ngContentIndex: number, - children: Expression[]): Expression { - return new Expression( - `${codeGenConstConstructorCall(TEMPLATE_COMMANDS_MODULE_REF+'EmbeddedTemplateCmd')}(${codeGenArray(attrNameAndValues)}, ${codeGenArray(variableNameAndValues)}, ` + - `${codeGenDirectivesArray(directives)}, ${isMerged}, ${ngContentIndex}, ${this.changeDetectorFactoryExpressions[embeddedTemplateIndex]}, ${codeGenArray(children)})`); - } -} - -function visitAndReturnContext(visitor: TemplateAstVisitor, asts: TemplateAst[], - context: any): any { - templateVisitAll(visitor, asts, context); - return context; -} - -class CommandBuilderVisitor implements TemplateAstVisitor { - result: R[] = []; - transitiveNgContentCount: number = 0; - constructor(public commandFactory: CommandFactory, public embeddedTemplateIndex: number) {} - - private _readAttrNameAndValues(directives: CompileDirectiveMetadata[], - attrAsts: TemplateAst[]): string[] { - var attrs = keyValueArrayToMap(visitAndReturnContext(this, attrAsts, [])); - directives.forEach(directiveMeta => { - StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => { - var prevValue = attrs[name]; - attrs[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value; - }); - }); - return mapToKeyValueArray(attrs); - } - - visitNgContent(ast: NgContentAst, context: any): any { - this.transitiveNgContentCount++; - this.result.push(this.commandFactory.createNgContent(ast.index, ast.ngContentIndex)); - return null; - } - visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { - this.embeddedTemplateIndex++; - var childVisitor = new CommandBuilderVisitor(this.commandFactory, this.embeddedTemplateIndex); - templateVisitAll(childVisitor, ast.children); - var isMerged = childVisitor.transitiveNgContentCount > 0; - var variableNameAndValues = []; - ast.vars.forEach((varAst) => { - variableNameAndValues.push(varAst.name); - variableNameAndValues.push(varAst.value.length > 0 ? varAst.value : IMPLICIT_TEMPLATE_VAR); - }); - var directives = []; - ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => { - directiveAst.visit(this, new DirectiveContext(index, [], [], directives)); - }); - this.result.push(this.commandFactory.createEmbeddedTemplate( - this.embeddedTemplateIndex, this._readAttrNameAndValues(directives, ast.attrs), - variableNameAndValues, directives, isMerged, ast.ngContentIndex, childVisitor.result)); - this.transitiveNgContentCount += childVisitor.transitiveNgContentCount; - this.embeddedTemplateIndex = childVisitor.embeddedTemplateIndex; - return null; - } - visitElement(ast: ElementAst, context: any): any { - var component = ast.getComponent(); - var eventTargetAndNames = visitAndReturnContext(this, ast.outputs, []); - var variableNameAndValues = []; - if (isBlank(component)) { - ast.exportAsVars.forEach((varAst) => { - variableNameAndValues.push(varAst.name); - variableNameAndValues.push(null); - }); - } - var directives = []; - ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => { - directiveAst.visit(this, new DirectiveContext(index, eventTargetAndNames, - variableNameAndValues, directives)); - }); - eventTargetAndNames = removeKeyValueArrayDuplicates(eventTargetAndNames); - - var attrNameAndValues = this._readAttrNameAndValues(directives, ast.attrs); - if (isPresent(component)) { - this.result.push(this.commandFactory.createBeginComponent( - ast.name, attrNameAndValues, eventTargetAndNames, variableNameAndValues, directives, - component.template.encapsulation, ast.ngContentIndex)); - templateVisitAll(this, ast.children); - this.result.push(this.commandFactory.createEndComponent()); - } else { - this.result.push(this.commandFactory.createBeginElement( - ast.name, attrNameAndValues, eventTargetAndNames, variableNameAndValues, directives, - ast.isBound(), ast.ngContentIndex)); - templateVisitAll(this, ast.children); - this.result.push(this.commandFactory.createEndElement()); - } - return null; - } - visitVariable(ast: VariableAst, ctx: any): any { return null; } - visitAttr(ast: AttrAst, attrNameAndValues: string[]): any { - attrNameAndValues.push(ast.name); - attrNameAndValues.push(ast.value); - return null; - } - visitBoundText(ast: BoundTextAst, context: any): any { - this.result.push(this.commandFactory.createText(null, true, ast.ngContentIndex)); - return null; - } - visitText(ast: TextAst, context: any): any { - this.result.push(this.commandFactory.createText(ast.value, false, ast.ngContentIndex)); - return null; - } - visitDirective(ast: DirectiveAst, ctx: DirectiveContext): any { - ctx.targetDirectives.push(ast.directive); - templateVisitAll(this, ast.hostEvents, ctx.eventTargetAndNames); - ast.exportAsVars.forEach(varAst => { - ctx.targetVariableNameAndValues.push(varAst.name); - ctx.targetVariableNameAndValues.push(ctx.index); - }); - return null; - } - visitEvent(ast: BoundEventAst, eventTargetAndNames: string[]): any { - eventTargetAndNames.push(ast.target); - eventTargetAndNames.push(ast.name); - return null; - } - visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; } - visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; } -} - -function removeKeyValueArrayDuplicates(keyValueArray: string[]): string[] { - var knownPairs = new Set(); - var resultKeyValueArray = []; - for (var i = 0; i < keyValueArray.length; i += 2) { - var key = keyValueArray[i]; - var value = keyValueArray[i + 1]; - var pairId = `${key}:${value}`; - if (!SetWrapper.has(knownPairs, pairId)) { - resultKeyValueArray.push(key); - resultKeyValueArray.push(value); - knownPairs.add(pairId); - } - } - return resultKeyValueArray; -} - -function keyValueArrayToMap(keyValueArr: string[]): {[key: string]: string} { - var data: {[key: string]: string} = {}; - for (var i = 0; i < keyValueArr.length; i += 2) { - data[keyValueArr[i]] = keyValueArr[i + 1]; - } - return data; -} - -function mapToKeyValueArray(data: {[key: string]: string}): string[] { - var entryArray = []; - StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); }); - // We need to sort to get a defined output order - // for tests and for caching generated artifacts... - ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0])); - var keyValueArray = []; - entryArray.forEach((entry) => { - keyValueArray.push(entry[0]); - keyValueArray.push(entry[1]); - }); - return keyValueArray; -} - -function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: string): string { - if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) { - return `${attrValue1} ${attrValue2}`; - } else { - return attrValue2; - } -} - -class DirectiveContext { - constructor(public index: number, public eventTargetAndNames: string[], - public targetVariableNameAndValues: any[], - public targetDirectives: CompileDirectiveMetadata[]) {} -} - -class Expression { - constructor(public value: string) {} -} - -function escapeValue(value: any): string { - if (value instanceof Expression) { - return value.value; - } else if (isString(value)) { - return escapeSingleQuoteString(value); - } else if (isBlank(value)) { - return 'null'; - } else { - return `${value}`; - } -} - -function codeGenArray(data: any[]): string { - var base = `[${data.map(escapeValue).join(',')}]`; - return IS_DART ? `const ${base}` : base; -} - -function codeGenDirectivesArray(directives: CompileDirectiveMetadata[]): string { - var expressions = directives.map( - directiveType => `${moduleRef(directiveType.type.moduleUrl)}${directiveType.type.name}`); - var base = `[${expressions.join(',')}]`; - return IS_DART ? `const ${base}` : base; -} - -function codeGenViewEncapsulation(value: ViewEncapsulation): string { - if (IS_DART) { - return `${TEMPLATE_COMMANDS_MODULE_REF}${value}`; - } else { - return `${value}`; - } -} diff --git a/modules/angular2/src/compiler/compiler.ts b/modules/angular2/src/compiler/compiler.ts index c35bc9104c..23426ce6c1 100644 --- a/modules/angular2/src/compiler/compiler.ts +++ b/modules/angular2/src/compiler/compiler.ts @@ -17,7 +17,8 @@ import {TemplateNormalizer} from 'angular2/src/compiler/template_normalizer'; import {RuntimeMetadataResolver} from 'angular2/src/compiler/runtime_metadata'; import {ChangeDetectionCompiler} from 'angular2/src/compiler/change_detector_compiler'; import {StyleCompiler} from 'angular2/src/compiler/style_compiler'; -import {CommandCompiler} from 'angular2/src/compiler/command_compiler'; +import {ViewCompiler} from 'angular2/src/compiler/view_compiler'; +import {ProtoViewCompiler} from 'angular2/src/compiler/proto_view_compiler'; import {TemplateCompiler} from 'angular2/src/compiler/template_compiler'; import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection'; import {Compiler} from 'angular2/src/core/linker/compiler'; @@ -44,7 +45,8 @@ export const COMPILER_PROVIDERS: Array = CONST_EXPR([ RuntimeMetadataResolver, DEFAULT_PACKAGE_URL_PROVIDER, StyleCompiler, - CommandCompiler, + ProtoViewCompiler, + ViewCompiler, ChangeDetectionCompiler, new Provider(ChangeDetectorGenConfig, {useFactory: _createChangeDetectorGenConfig, deps: []}), TemplateCompiler, diff --git a/modules/angular2/src/compiler/directive_metadata.ts b/modules/angular2/src/compiler/directive_metadata.ts index f82f6b031a..2fe7ef0d38 100644 --- a/modules/angular2/src/compiler/directive_metadata.ts +++ b/modules/angular2/src/compiler/directive_metadata.ts @@ -7,6 +7,7 @@ import { RegExpWrapper, StringWrapper } from 'angular2/src/facade/lang'; +import {unimplemented} from 'angular2/src/facade/exceptions'; import {StringMapWrapper} from 'angular2/src/facade/collection'; import { ChangeDetectionStrategy, @@ -21,6 +22,16 @@ import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/linker/i // group 2: "event" from "(event)" var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g; +export abstract class CompileMetadataWithType { + static fromJson(data: {[key: string]: any}): CompileMetadataWithType { + return _COMPILE_METADATA_FROM_JSON[data['class']](data); + } + + abstract toJson(): {[key: string]: any}; + + get type(): CompileTypeMetadata { return unimplemented(); } +} + /** * Metadata regarding compilation of a type. */ @@ -107,7 +118,7 @@ export class CompileTemplateMetadata { /** * Metadata regarding compilation of a directive. */ -export class CompileDirectiveMetadata { +export class CompileDirectiveMetadata implements CompileMetadataWithType { static create({type, isComponent, dynamicLoadable, selector, exportAs, changeDetection, inputs, outputs, host, lifecycleHooks, template}: { type?: CompileTypeMetadata, @@ -241,6 +252,7 @@ export class CompileDirectiveMetadata { toJson(): {[key: string]: any} { return { + 'class': 'Directive', 'isComponent': this.isComponent, 'dynamicLoadable': this.dynamicLoadable, 'selector': this.selector, @@ -284,3 +296,38 @@ export function createHostComponentMeta(componentType: CompileTypeMetadata, selector: '*' }); } + + +export class CompilePipeMetadata implements CompileMetadataWithType { + type: CompileTypeMetadata; + name: string; + pure: boolean; + constructor({type, name, + pure}: {type?: CompileTypeMetadata, name?: string, pure?: boolean} = {}) { + this.type = type; + this.name = name; + this.pure = normalizeBool(pure); + } + + static fromJson(data: {[key: string]: any}): CompilePipeMetadata { + return new CompilePipeMetadata({ + type: isPresent(data['type']) ? CompileTypeMetadata.fromJson(data['type']) : data['type'], + name: data['name'], + pure: data['pure'] + }); + } + + toJson(): {[key: string]: any} { + return { + 'class': 'Pipe', + 'type': isPresent(this.type) ? this.type.toJson() : null, + 'name': this.name, + 'pure': this.pure + }; + } +} + +var _COMPILE_METADATA_FROM_JSON = { + 'Directive': CompileDirectiveMetadata.fromJson, + 'Pipe': CompilePipeMetadata.fromJson +}; diff --git a/modules/angular2/src/compiler/proto_view_compiler.ts b/modules/angular2/src/compiler/proto_view_compiler.ts new file mode 100644 index 0000000000..9b211391bc --- /dev/null +++ b/modules/angular2/src/compiler/proto_view_compiler.ts @@ -0,0 +1,397 @@ +import { + isPresent, + isBlank, + Type, + isString, + StringWrapper, + IS_DART, + CONST_EXPR +} from 'angular2/src/facade/lang'; +import { + SetWrapper, + StringMapWrapper, + ListWrapper, + MapWrapper +} from 'angular2/src/facade/collection'; +import { + TemplateAst, + TemplateAstVisitor, + NgContentAst, + EmbeddedTemplateAst, + ElementAst, + VariableAst, + BoundEventAst, + BoundElementPropertyAst, + AttrAst, + BoundTextAst, + TextAst, + DirectiveAst, + BoundDirectivePropertyAst, + templateVisitAll +} from './template_ast'; +import { + CompileTypeMetadata, + CompileDirectiveMetadata, + CompilePipeMetadata +} from './directive_metadata'; +import {SourceExpressions, SourceExpression, moduleRef} from './source_module'; +import {AppProtoView, AppView} from 'angular2/src/core/linker/view'; +import {ViewType} from 'angular2/src/core/linker/view_type'; +import {AppProtoElement, AppElement} from 'angular2/src/core/linker/element'; +import {ResolvedMetadataCache} from 'angular2/src/core/linker/resolved_metadata_cache'; +import { + escapeSingleQuoteString, + codeGenConstConstructorCall, + codeGenValueFn, + codeGenFnHeader, + MODULE_SUFFIX, + codeGenStringMap, + Expression, + Statement +} from './util'; +import {Injectable} from 'angular2/src/core/di'; + +export const PROTO_VIEW_JIT_IMPORTS = CONST_EXPR( + {'AppProtoView': AppProtoView, 'AppProtoElement': AppProtoElement, 'ViewType': ViewType}); + +// TODO: have a single file that reexports everything needed for +// codegen explicitly +// - helps understanding what codegen works against +// - less imports in codegen code +export var APP_VIEW_MODULE_REF = moduleRef('package:angular2/src/core/linker/view' + MODULE_SUFFIX); +export var VIEW_TYPE_MODULE_REF = + moduleRef('package:angular2/src/core/linker/view_type' + MODULE_SUFFIX); +export var APP_EL_MODULE_REF = + moduleRef('package:angular2/src/core/linker/element' + MODULE_SUFFIX); +export var METADATA_MODULE_REF = + moduleRef('package:angular2/src/core/metadata/view' + MODULE_SUFFIX); + +const IMPLICIT_TEMPLATE_VAR = '\$implicit'; +const CLASS_ATTR = 'class'; +const STYLE_ATTR = 'style'; + +@Injectable() +export class ProtoViewCompiler { + constructor() {} + + compileProtoViewRuntime(metadataCache: ResolvedMetadataCache, component: CompileDirectiveMetadata, + template: TemplateAst[], pipes: CompilePipeMetadata[]): + CompileProtoViews { + var protoViewFactory = new RuntimeProtoViewFactory(metadataCache, component, pipes); + var allProtoViews = []; + protoViewFactory.createCompileProtoView(template, [], [], allProtoViews); + return new CompileProtoViews([], allProtoViews); + } + + compileProtoViewCodeGen(resolvedMetadataCacheExpr: Expression, + component: CompileDirectiveMetadata, template: TemplateAst[], + pipes: CompilePipeMetadata[]): + CompileProtoViews { + var protoViewFactory = new CodeGenProtoViewFactory(resolvedMetadataCacheExpr, component, pipes); + var allProtoViews = []; + var allStatements = []; + protoViewFactory.createCompileProtoView(template, [], allStatements, allProtoViews); + return new CompileProtoViews( + allStatements.map(stmt => stmt.statement), allProtoViews); + } +} + +export class CompileProtoViews { + constructor(public declarations: STATEMENT[], + public protoViews: CompileProtoView[]) {} +} + + +export class CompileProtoView { + constructor(public embeddedTemplateIndex: number, + public protoElements: CompileProtoElement[], + public protoView: APP_PROTO_VIEW) {} +} + +export class CompileProtoElement { + constructor(public boundElementIndex, public attrNameAndValues: string[][], + public variableNameAndValues: string[][], public renderEvents: BoundEventAst[], + public directives: CompileDirectiveMetadata[], public embeddedTemplateIndex: number, + public appProtoEl: APP_PROTO_EL) {} +} + +function visitAndReturnContext(visitor: TemplateAstVisitor, asts: TemplateAst[], + context: any): any { + templateVisitAll(visitor, asts, context); + return context; +} + +abstract class ProtoViewFactory { + constructor(public component: CompileDirectiveMetadata) {} + + abstract createAppProtoView(embeddedTemplateIndex: number, viewType: ViewType, + templateVariableBindings: string[][], + targetStatements: STATEMENT[]): APP_PROTO_VIEW; + + abstract createAppProtoElement(boundElementIndex: number, attrNameAndValues: string[][], + variableNameAndValues: string[][], + directives: CompileDirectiveMetadata[], + targetStatements: STATEMENT[]): APP_PROTO_EL; + + createCompileProtoView(template: TemplateAst[], templateVariableBindings: string[][], + targetStatements: STATEMENT[], + targetProtoViews: CompileProtoView[]): + CompileProtoView { + var embeddedTemplateIndex = targetProtoViews.length; + // Note: targetProtoViews needs to be in depth first order. + // So we "reserve" a space here that we fill after the recursion is done + targetProtoViews.push(null); + var builder = new ProtoViewBuilderVisitor( + this, targetStatements, targetProtoViews); + templateVisitAll(builder, template); + var viewType = getViewType(this.component, embeddedTemplateIndex); + var appProtoView = this.createAppProtoView(embeddedTemplateIndex, viewType, + templateVariableBindings, targetStatements); + var cpv = new CompileProtoView( + embeddedTemplateIndex, builder.protoElements, appProtoView); + targetProtoViews[embeddedTemplateIndex] = cpv; + return cpv; + } +} + +class CodeGenProtoViewFactory extends ProtoViewFactory { + private _nextVarId: number = 0; + + constructor(public resolvedMetadataCacheExpr: Expression, component: CompileDirectiveMetadata, + public pipes: CompilePipeMetadata[]) { + super(component); + } + + private _nextProtoViewVar(embeddedTemplateIndex: number): string { + return `appProtoView${this._nextVarId++}_${this.component.type.name}${embeddedTemplateIndex}`; + } + + createAppProtoView(embeddedTemplateIndex: number, viewType: ViewType, + templateVariableBindings: string[][], + targetStatements: Statement[]): Expression { + var protoViewVarName = this._nextProtoViewVar(embeddedTemplateIndex); + var viewTypeExpr = codeGenViewType(viewType); + var pipesExpr = embeddedTemplateIndex === 0 ? + codeGenTypesArray(this.pipes.map(pipeMeta => pipeMeta.type)) : + null; + var statement = + `var ${protoViewVarName} = ${APP_VIEW_MODULE_REF}AppProtoView.create(${this.resolvedMetadataCacheExpr.expression}, ${viewTypeExpr}, ${pipesExpr}, ${codeGenStringMap(templateVariableBindings)});`; + targetStatements.push(new Statement(statement)); + return new Expression(protoViewVarName); + } + + createAppProtoElement(boundElementIndex: number, attrNameAndValues: string[][], + variableNameAndValues: string[][], directives: CompileDirectiveMetadata[], + targetStatements: Statement[]): Expression { + var varName = `appProtoEl${this._nextVarId++}_${this.component.type.name}`; + var value = `${APP_EL_MODULE_REF}AppProtoElement.create( + ${this.resolvedMetadataCacheExpr.expression}, + ${boundElementIndex}, + ${codeGenStringMap(attrNameAndValues)}, + ${codeGenDirectivesArray(directives)}, + ${codeGenStringMap(variableNameAndValues)} + )`; + var statement = `var ${varName} = ${value};`; + targetStatements.push(new Statement(statement)); + return new Expression(varName); + } +} + +class RuntimeProtoViewFactory extends ProtoViewFactory { + constructor(public metadataCache: ResolvedMetadataCache, component: CompileDirectiveMetadata, + public pipes: CompilePipeMetadata[]) { + super(component); + } + + createAppProtoView(embeddedTemplateIndex: number, viewType: ViewType, + templateVariableBindings: string[][], targetStatements: any[]): AppProtoView { + var pipes = + embeddedTemplateIndex === 0 ? this.pipes.map(pipeMeta => pipeMeta.type.runtime) : []; + var templateVars = keyValueArrayToStringMap(templateVariableBindings); + return AppProtoView.create(this.metadataCache, viewType, pipes, templateVars); + } + + createAppProtoElement(boundElementIndex: number, attrNameAndValues: string[][], + variableNameAndValues: string[][], directives: CompileDirectiveMetadata[], + targetStatements: any[]): AppProtoElement { + var attrs = keyValueArrayToStringMap(attrNameAndValues); + return AppProtoElement.create(this.metadataCache, boundElementIndex, attrs, + directives.map(dirMeta => dirMeta.type.runtime), + keyValueArrayToStringMap(variableNameAndValues)); + } +} + +class ProtoViewBuilderVisitor implements + TemplateAstVisitor { + protoElements: CompileProtoElement[] = []; + boundElementCount: number = 0; + + constructor(public factory: ProtoViewFactory, + public allStatements: STATEMENT[], + public allProtoViews: CompileProtoView[]) {} + + private _readAttrNameAndValues(directives: CompileDirectiveMetadata[], + attrAsts: TemplateAst[]): string[][] { + var attrs = visitAndReturnContext(this, attrAsts, {}); + directives.forEach(directiveMeta => { + StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => { + var prevValue = attrs[name]; + attrs[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value; + }); + }); + return mapToKeyValueArray(attrs); + } + + visitBoundText(ast: BoundTextAst, context: any): any { return null; } + visitText(ast: TextAst, context: any): any { return null; } + + visitNgContent(ast: NgContentAst, context: any): any { return null; } + + visitElement(ast: ElementAst, context: any): any { + var boundElementIndex = null; + if (ast.isBound()) { + boundElementIndex = this.boundElementCount++; + } + var component = ast.getComponent(); + + var variableNameAndValues: string[][] = []; + if (isBlank(component)) { + ast.exportAsVars.forEach((varAst) => { variableNameAndValues.push([varAst.name, null]); }); + } + var directives = []; + var renderEvents: Map = + visitAndReturnContext(this, ast.outputs, new Map()); + ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => { + directiveAst.visit(this, new DirectiveContext(index, boundElementIndex, renderEvents, + variableNameAndValues, directives)); + }); + var renderEventArray = []; + renderEvents.forEach((eventAst, _) => renderEventArray.push(eventAst)); + + var attrNameAndValues = this._readAttrNameAndValues(directives, ast.attrs); + this._addProtoElement(ast.isBound(), boundElementIndex, attrNameAndValues, + variableNameAndValues, renderEventArray, directives, null); + templateVisitAll(this, ast.children); + return null; + } + + visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { + var boundElementIndex = this.boundElementCount++; + var directives: CompileDirectiveMetadata[] = []; + ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => { + directiveAst.visit( + this, new DirectiveContext(index, boundElementIndex, new Map(), [], + directives)); + }); + + var attrNameAndValues = this._readAttrNameAndValues(directives, ast.attrs); + var templateVariableBindings = ast.vars.map( + varAst => [varAst.value.length > 0 ? varAst.value : IMPLICIT_TEMPLATE_VAR, varAst.name]); + var nestedProtoView = this.factory.createCompileProtoView( + ast.children, templateVariableBindings, this.allStatements, this.allProtoViews); + this._addProtoElement(true, boundElementIndex, attrNameAndValues, [], [], directives, + nestedProtoView.embeddedTemplateIndex); + return null; + } + + private _addProtoElement(isBound: boolean, boundElementIndex, attrNameAndValues: string[][], + variableNameAndValues: string[][], renderEvents: BoundEventAst[], + directives: CompileDirectiveMetadata[], embeddedTemplateIndex: number) { + var appProtoEl = null; + if (isBound) { + appProtoEl = + this.factory.createAppProtoElement(boundElementIndex, attrNameAndValues, + variableNameAndValues, directives, this.allStatements); + } + var compileProtoEl = new CompileProtoElement( + boundElementIndex, attrNameAndValues, variableNameAndValues, renderEvents, directives, + embeddedTemplateIndex, appProtoEl); + this.protoElements.push(compileProtoEl); + } + + visitVariable(ast: VariableAst, ctx: any): any { return null; } + visitAttr(ast: AttrAst, attrNameAndValues: {[key: string]: string}): any { + attrNameAndValues[ast.name] = ast.value; + return null; + } + visitDirective(ast: DirectiveAst, ctx: DirectiveContext): any { + ctx.targetDirectives.push(ast.directive); + templateVisitAll(this, ast.hostEvents, ctx.hostEventTargetAndNames); + ast.exportAsVars.forEach( + varAst => { ctx.targetVariableNameAndValues.push([varAst.name, ctx.index]); }); + return null; + } + visitEvent(ast: BoundEventAst, eventTargetAndNames: Map): any { + eventTargetAndNames.set(ast.fullName, ast); + return null; + } + visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; } + visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; } +} + +function mapToKeyValueArray(data: {[key: string]: string}): string[][] { + var entryArray = []; + StringMapWrapper.forEach(data, (value, name) => { entryArray.push([name, value]); }); + // We need to sort to get a defined output order + // for tests and for caching generated artifacts... + ListWrapper.sort(entryArray, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0])); + var keyValueArray = []; + entryArray.forEach((entry) => { keyValueArray.push([entry[0], entry[1]]); }); + return keyValueArray; +} + +function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: string): string { + if (attrName == CLASS_ATTR || attrName == STYLE_ATTR) { + return `${attrValue1} ${attrValue2}`; + } else { + return attrValue2; + } +} + +class DirectiveContext { + constructor(public index: number, public boundElementIndex: number, + public hostEventTargetAndNames: Map, + public targetVariableNameAndValues: any[][], + public targetDirectives: CompileDirectiveMetadata[]) {} +} + +function keyValueArrayToStringMap(keyValueArray: any[][]): {[key: string]: any} { + var stringMap: {[key: string]: string} = {}; + for (var i = 0; i < keyValueArray.length; i++) { + var entry = keyValueArray[i]; + stringMap[entry[0]] = entry[1]; + } + return stringMap; +} + +function codeGenDirectivesArray(directives: CompileDirectiveMetadata[]): string { + var expressions = directives.map(directiveType => typeRef(directiveType.type)); + return `[${expressions.join(',')}]`; +} + +function codeGenTypesArray(types: CompileTypeMetadata[]): string { + var expressions = types.map(typeRef); + return `[${expressions.join(',')}]`; +} + +function codeGenViewType(value: ViewType): string { + if (IS_DART) { + return `${VIEW_TYPE_MODULE_REF}${value}`; + } else { + return `${value}`; + } +} + +function typeRef(type: CompileTypeMetadata): string { + return `${moduleRef(type.moduleUrl)}${type.name}`; +} + +function getViewType(component: CompileDirectiveMetadata, embeddedTemplateIndex: number): ViewType { + if (embeddedTemplateIndex > 0) { + return ViewType.EMBEDDED; + } else if (component.type.isHost) { + return ViewType.HOST; + } else { + return ViewType.COMPONENT; + } +} diff --git a/modules/angular2/src/compiler/runtime_compiler.ts b/modules/angular2/src/compiler/runtime_compiler.ts index 54ae61b31d..07ac2990a3 100644 --- a/modules/angular2/src/compiler/runtime_compiler.ts +++ b/modules/angular2/src/compiler/runtime_compiler.ts @@ -1,23 +1,23 @@ -import {Compiler, Compiler_, internalCreateProtoView} from 'angular2/src/core/linker/compiler'; -import {ProtoViewRef} from 'angular2/src/core/linker/view_ref'; -import {ProtoViewFactory} from 'angular2/src/core/linker/proto_view_factory'; +import {Compiler, Compiler_} from 'angular2/src/core/linker/compiler'; +import {HostViewFactoryRef, HostViewFactoryRef_} from 'angular2/src/core/linker/view_ref'; import {TemplateCompiler} from './template_compiler'; import {Injectable} from 'angular2/src/core/di'; import {Type} from 'angular2/src/facade/lang'; import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; -export abstract class RuntimeCompiler extends Compiler {} +export abstract class RuntimeCompiler extends Compiler { + abstract compileInHost(componentType: Type): Promise; + abstract clearCache(); +} @Injectable() export class RuntimeCompiler_ extends Compiler_ implements RuntimeCompiler { - constructor(_protoViewFactory: ProtoViewFactory, private _templateCompiler: TemplateCompiler) { - super(_protoViewFactory); - } + constructor(private _templateCompiler: TemplateCompiler) { super(); } - compileInHost(componentType: Type): Promise { + compileInHost(componentType: Type): Promise { return this._templateCompiler.compileHostComponentRuntime(componentType) - .then(compiledHostTemplate => internalCreateProtoView(this, compiledHostTemplate)); + .then(hostViewFactory => new HostViewFactoryRef_(hostViewFactory)); } clearCache() { diff --git a/modules/angular2/src/compiler/runtime_metadata.ts b/modules/angular2/src/compiler/runtime_metadata.ts index 1fceb0e859..51b3494b4e 100644 --- a/modules/angular2/src/compiler/runtime_metadata.ts +++ b/modules/angular2/src/compiler/runtime_metadata.ts @@ -11,25 +11,29 @@ import {BaseException} from 'angular2/src/facade/exceptions'; import * as cpl from './directive_metadata'; import * as md from 'angular2/src/core/metadata/directives'; import {DirectiveResolver} from 'angular2/src/core/linker/directive_resolver'; +import {PipeResolver} from 'angular2/src/core/linker/pipe_resolver'; import {ViewResolver} from 'angular2/src/core/linker/view_resolver'; import {ViewMetadata} from 'angular2/src/core/metadata/view'; import {hasLifecycleHook} from 'angular2/src/core/linker/directive_lifecycle_reflector'; import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/linker/interfaces'; import {reflector} from 'angular2/src/core/reflection/reflection'; import {Injectable, Inject, Optional} from 'angular2/src/core/di'; -import {PLATFORM_DIRECTIVES} from 'angular2/src/core/platform_directives_and_pipes'; +import {PLATFORM_DIRECTIVES, PLATFORM_PIPES} from 'angular2/src/core/platform_directives_and_pipes'; import {MODULE_SUFFIX} from './util'; import {getUrlScheme} from 'angular2/src/compiler/url_resolver'; @Injectable() export class RuntimeMetadataResolver { - private _cache = new Map(); + private _directiveCache = new Map(); + private _pipeCache = new Map(); - constructor(private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver, - @Optional() @Inject(PLATFORM_DIRECTIVES) private _platformDirectives: Type[]) {} + constructor(private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver, + private _viewResolver: ViewResolver, + @Optional() @Inject(PLATFORM_DIRECTIVES) private _platformDirectives: Type[], + @Optional() @Inject(PLATFORM_PIPES) private _platformPipes: Type[]) {} - getMetadata(directiveType: Type): cpl.CompileDirectiveMetadata { - var meta = this._cache.get(directiveType); + getDirectiveMetadata(directiveType: Type): cpl.CompileDirectiveMetadata { + var meta = this._directiveCache.get(directiveType); if (isBlank(meta)) { var dirMeta = this._directiveResolver.resolve(directiveType); var moduleUrl = null; @@ -63,7 +67,23 @@ export class RuntimeMetadataResolver { host: dirMeta.host, lifecycleHooks: LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(hook, directiveType)) }); - this._cache.set(directiveType, meta); + this._directiveCache.set(directiveType, meta); + } + return meta; + } + + getPipeMetadata(pipeType: Type): cpl.CompilePipeMetadata { + var meta = this._pipeCache.get(pipeType); + if (isBlank(meta)) { + var pipeMeta = this._pipeResolver.resolve(pipeType); + var moduleUrl = reflector.importUri(pipeType); + meta = new cpl.CompilePipeMetadata({ + type: new cpl.CompileTypeMetadata( + {name: stringify(pipeType), moduleUrl: moduleUrl, runtime: pipeType}), + name: pipeMeta.name, + pure: pipeMeta.pure + }); + this._pipeCache.set(pipeType, meta); } return meta; } @@ -72,13 +92,25 @@ export class RuntimeMetadataResolver { var view = this._viewResolver.resolve(component); var directives = flattenDirectives(view, this._platformDirectives); for (var i = 0; i < directives.length; i++) { - if (!isValidDirective(directives[i])) { + if (!isValidType(directives[i])) { throw new BaseException( `Unexpected directive value '${stringify(directives[i])}' on the View of component '${stringify(component)}'`); } } - return directives.map(type => this.getMetadata(type)); + return directives.map(type => this.getDirectiveMetadata(type)); + } + + getViewPipesMetadata(component: Type): cpl.CompilePipeMetadata[] { + var view = this._viewResolver.resolve(component); + var pipes = flattenPipes(view, this._platformPipes); + for (var i = 0; i < pipes.length; i++) { + if (!isValidType(pipes[i])) { + throw new BaseException( + `Unexpected piped value '${stringify(pipes[i])}' on the View of component '${stringify(component)}'`); + } + } + return pipes.map(type => this.getPipeMetadata(type)); } } @@ -93,6 +125,17 @@ function flattenDirectives(view: ViewMetadata, platformDirectives: any[]): Type[ return directives; } +function flattenPipes(view: ViewMetadata, platformPipes: any[]): Type[] { + let pipes = []; + if (isPresent(platformPipes)) { + flattenArray(platformPipes, pipes); + } + if (isPresent(view.pipes)) { + flattenArray(view.pipes, pipes); + } + return pipes; +} + function flattenArray(tree: any[], out: Array): void { for (var i = 0; i < tree.length; i++) { var item = resolveForwardRef(tree[i]); @@ -104,7 +147,7 @@ function flattenArray(tree: any[], out: Array): void { } } -function isValidDirective(value: Type): boolean { +function isValidType(value: Type): boolean { return isPresent(value) && (value instanceof Type); } diff --git a/modules/angular2/src/compiler/source_module.ts b/modules/angular2/src/compiler/source_module.ts index 49dc23a9bf..c100d36c29 100644 --- a/modules/angular2/src/compiler/source_module.ts +++ b/modules/angular2/src/compiler/source_module.ts @@ -10,6 +10,10 @@ export function moduleRef(moduleUrl): string { * Represents generated source code with module references. Internal to the Angular compiler. */ export class SourceModule { + static getSourceWithoutImports(sourceWithModuleRefs: string): string { + return StringWrapper.replaceAllMapped(sourceWithModuleRefs, MODULE_REGEXP, (match) => ''); + } + constructor(public moduleUrl: string, public sourceWithModuleRefs: string) {} getSourceWithImports(): SourceWithImports { diff --git a/modules/angular2/src/compiler/style_compiler.ts b/modules/angular2/src/compiler/style_compiler.ts index a4ee6fd0c9..8cc5437214 100644 --- a/modules/angular2/src/compiler/style_compiler.ts +++ b/modules/angular2/src/compiler/style_compiler.ts @@ -14,7 +14,10 @@ import { MODULE_SUFFIX } from './util'; import {Injectable} from 'angular2/src/core/di'; -import {COMPONENT_VARIABLE, HOST_ATTR, CONTENT_ATTR} from 'angular2/src/core/render/view_factory'; + +const COMPONENT_VARIABLE = '%COMP%'; +const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`; +const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`; @Injectable() export class StyleCompiler { diff --git a/modules/angular2/src/compiler/template_compiler.ts b/modules/angular2/src/compiler/template_compiler.ts index 06b6300000..54a5fccef8 100644 --- a/modules/angular2/src/compiler/template_compiler.ts +++ b/modules/angular2/src/compiler/template_compiler.ts @@ -1,37 +1,74 @@ -import {IS_DART, Type, Json, isBlank, stringify} from 'angular2/src/facade/lang'; -import {BaseException} from 'angular2/src/facade/exceptions'; -import {ListWrapper, SetWrapper, MapWrapper} from 'angular2/src/facade/collection'; -import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import { - CompiledComponentTemplate, - TemplateCmd, - CompiledHostTemplate, - BeginComponentCmd -} from 'angular2/src/core/linker/template_commands'; + IS_DART, + Type, + Json, + isBlank, + isPresent, + stringify, + evalExpression +} from 'angular2/src/facade/lang'; +import {BaseException} from 'angular2/src/facade/exceptions'; +import { + ListWrapper, + SetWrapper, + MapWrapper, + StringMapWrapper +} from 'angular2/src/facade/collection'; +import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import { createHostComponentMeta, CompileDirectiveMetadata, CompileTypeMetadata, - CompileTemplateMetadata + CompileTemplateMetadata, + CompilePipeMetadata, + CompileMetadataWithType } from './directive_metadata'; -import {TemplateAst} from './template_ast'; +import { + TemplateAst, + TemplateAstVisitor, + NgContentAst, + EmbeddedTemplateAst, + ElementAst, + VariableAst, + BoundEventAst, + BoundElementPropertyAst, + AttrAst, + BoundTextAst, + TextAst, + DirectiveAst, + BoundDirectivePropertyAst, + templateVisitAll +} from './template_ast'; import {Injectable} from 'angular2/src/core/di'; -import {SourceModule, moduleRef} from './source_module'; -import {ChangeDetectionCompiler} from './change_detector_compiler'; +import {SourceModule, moduleRef, SourceExpression} from './source_module'; +import {ChangeDetectionCompiler, CHANGE_DETECTION_JIT_IMPORTS} from './change_detector_compiler'; import {StyleCompiler} from './style_compiler'; -import {CommandCompiler} from './command_compiler'; -import {TemplateParser} from './template_parser'; +import {ViewCompiler, VIEW_JIT_IMPORTS} from './view_compiler'; +import { + ProtoViewCompiler, + APP_VIEW_MODULE_REF, + CompileProtoView, + PROTO_VIEW_JIT_IMPORTS +} from './proto_view_compiler'; +import {TemplateParser, PipeCollector} from './template_parser'; import {TemplateNormalizer} from './template_normalizer'; import {RuntimeMetadataResolver} from './runtime_metadata'; +import {HostViewFactory} from 'angular2/src/core/linker/view'; +import {ChangeDetectorGenConfig} from 'angular2/src/core/change_detection/change_detection'; +import {ResolvedMetadataCache} from 'angular2/src/core/linker/resolved_metadata_cache'; -import {TEMPLATE_COMMANDS_MODULE_REF} from './command_compiler'; import { codeGenExportVariable, escapeSingleQuoteString, codeGenValueFn, - MODULE_SUFFIX + MODULE_SUFFIX, + addAll, + Expression } from './util'; +export var METADATA_CACHE_MODULE_REF = + moduleRef('package:angular2/src/core/linker/resolved_metadata_cache' + MODULE_SUFFIX); + /** * An internal module of the Angular compiler that begins with component types, * extracts templates, and eventually produces a compiled version of the component @@ -40,15 +77,16 @@ import { @Injectable() export class TemplateCompiler { private _hostCacheKeys = new Map(); - private _compiledTemplateCache = new Map(); - private _compiledTemplateDone = new Map>(); - private _nextTemplateId: number = 0; + private _compiledTemplateCache = new Map(); + private _compiledTemplateDone = new Map>(); constructor(private _runtimeMetadataResolver: RuntimeMetadataResolver, private _templateNormalizer: TemplateNormalizer, private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, - private _commandCompiler: CommandCompiler, - private _cdCompiler: ChangeDetectionCompiler) {} + private _cdCompiler: ChangeDetectionCompiler, + private _protoViewCompiler: ProtoViewCompiler, private _viewCompiler: ViewCompiler, + private _resolvedMetadataCache: ResolvedMetadataCache, + private _genConfig: ChangeDetectorGenConfig) {} normalizeDirectiveMetadata(directive: CompileDirectiveMetadata): Promise { @@ -75,99 +113,29 @@ export class TemplateCompiler { })); } - compileHostComponentRuntime(type: Type): Promise { + compileHostComponentRuntime(type: Type): Promise { + var compMeta: CompileDirectiveMetadata = + this._runtimeMetadataResolver.getDirectiveMetadata(type); var hostCacheKey = this._hostCacheKeys.get(type); if (isBlank(hostCacheKey)) { hostCacheKey = new Object(); this._hostCacheKeys.set(type, hostCacheKey); - var compMeta: CompileDirectiveMetadata = this._runtimeMetadataResolver.getMetadata(type); assertComponent(compMeta); var hostMeta: CompileDirectiveMetadata = createHostComponentMeta(compMeta.type, compMeta.selector); - this._compileComponentRuntime(hostCacheKey, hostMeta, [compMeta], new Set()); + this._compileComponentRuntime(hostCacheKey, hostMeta, [compMeta], [], new Set()); } return this._compiledTemplateDone.get(hostCacheKey) - .then(compiledTemplate => new CompiledHostTemplate(compiledTemplate)); + .then((compiledTemplate: CompiledTemplate) => + new HostViewFactory(compMeta.selector, compiledTemplate.viewFactory)); } clearCache() { - this._hostCacheKeys.clear(); this._styleCompiler.clearCache(); this._compiledTemplateCache.clear(); this._compiledTemplateDone.clear(); - } - - private _compileComponentRuntime( - cacheKey: any, compMeta: CompileDirectiveMetadata, viewDirectives: CompileDirectiveMetadata[], - compilingComponentCacheKeys: Set): CompiledComponentTemplate { - let uniqViewDirectives = removeDuplicates(viewDirectives); - var compiledTemplate = this._compiledTemplateCache.get(cacheKey); - var done = this._compiledTemplateDone.get(cacheKey); - if (isBlank(compiledTemplate)) { - var styles = []; - var changeDetectorFactory; - var commands = []; - var templateId = `${stringify(compMeta.type.runtime)}Template${this._nextTemplateId++}`; - compiledTemplate = new CompiledComponentTemplate( - templateId, (dispatcher) => changeDetectorFactory(dispatcher), commands, styles); - this._compiledTemplateCache.set(cacheKey, compiledTemplate); - compilingComponentCacheKeys.add(cacheKey); - done = PromiseWrapper - .all([this._styleCompiler.compileComponentRuntime(compMeta.template)].concat( - uniqViewDirectives.map(dirMeta => this.normalizeDirectiveMetadata(dirMeta)))) - .then((stylesAndNormalizedViewDirMetas: any[]) => { - var childPromises = []; - var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1); - var parsedTemplate = this._templateParser.parse( - compMeta.template.template, normalizedViewDirMetas, compMeta.type.name); - - var changeDetectorFactories = this._cdCompiler.compileComponentRuntime( - compMeta.type, compMeta.changeDetection, parsedTemplate); - changeDetectorFactory = changeDetectorFactories[0]; - var tmpStyles: string[] = stylesAndNormalizedViewDirMetas[0]; - tmpStyles.forEach(style => styles.push(style)); - var tmpCommands: TemplateCmd[] = this._compileCommandsRuntime( - compMeta, parsedTemplate, changeDetectorFactories, - compilingComponentCacheKeys, childPromises); - tmpCommands.forEach(cmd => commands.push(cmd)); - return PromiseWrapper.all(childPromises); - }) - .then((_) => { - SetWrapper.delete(compilingComponentCacheKeys, cacheKey); - return compiledTemplate; - }); - this._compiledTemplateDone.set(cacheKey, done); - } - return compiledTemplate; - } - - private _compileCommandsRuntime(compMeta: CompileDirectiveMetadata, parsedTemplate: TemplateAst[], - changeDetectorFactories: Function[], - compilingComponentCacheKeys: Set, - childPromises: Promise[]): TemplateCmd[] { - var cmds: TemplateCmd[] = this._commandCompiler.compileComponentRuntime( - compMeta, parsedTemplate, changeDetectorFactories, - (childComponentDir: CompileDirectiveMetadata) => { - var childCacheKey = childComponentDir.type.runtime; - var childViewDirectives: CompileDirectiveMetadata[] = - this._runtimeMetadataResolver.getViewDirectivesMetadata( - childComponentDir.type.runtime); - var childIsRecursive = SetWrapper.has(compilingComponentCacheKeys, childCacheKey); - var childTemplate = this._compileComponentRuntime( - childCacheKey, childComponentDir, childViewDirectives, compilingComponentCacheKeys); - if (!childIsRecursive) { - // Only wait for a child if it is not a cycle - childPromises.push(this._compiledTemplateDone.get(childCacheKey)); - } - return () => childTemplate; - }); - cmds.forEach(cmd => { - if (cmd instanceof BeginComponentCmd) { - cmd.templateGetter(); - } - }); - return cmds; + this._hostCacheKeys.clear(); } compileTemplatesCodeGen(components: NormalizedComponentWithViewDirectives[]): SourceModule { @@ -175,38 +143,22 @@ export class TemplateCompiler { throw new BaseException('No components given'); } var declarations = []; - var templateArguments = []; - var componentMetas: CompileDirectiveMetadata[] = []; components.forEach(componentWithDirs => { var compMeta = componentWithDirs.component; assertComponent(compMeta); - componentMetas.push(compMeta); - - this._processTemplateCodeGen(compMeta, componentWithDirs.directives, declarations, - templateArguments); + this._compileComponentCodeGen(compMeta, componentWithDirs.directives, componentWithDirs.pipes, + declarations); if (compMeta.dynamicLoadable) { var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector); - componentMetas.push(hostMeta); - this._processTemplateCodeGen(hostMeta, [compMeta], declarations, templateArguments); + var viewFactoryExpression = + this._compileComponentCodeGen(hostMeta, [compMeta], [], declarations); + var constructionKeyword = IS_DART ? 'const' : 'new'; + var compiledTemplateExpr = + `${constructionKeyword} ${APP_VIEW_MODULE_REF}HostViewFactory('${compMeta.selector}',${viewFactoryExpression})`; + var varName = codeGenHostViewFactoryName(compMeta.type); + declarations.push(`${codeGenExportVariable(varName)}${compiledTemplateExpr};`); } }); - ListWrapper.forEachWithIndex(componentMetas, (compMeta: CompileDirectiveMetadata, - index: number) => { - var templateId = `${compMeta.type.moduleUrl}|${compMeta.type.name}`; - var constructionKeyword = IS_DART ? 'const' : 'new'; - var compiledTemplateExpr = - `${constructionKeyword} ${TEMPLATE_COMMANDS_MODULE_REF}CompiledComponentTemplate('${templateId}',${(templateArguments[index]).join(',')})`; - var variableValueExpr; - if (compMeta.type.isHost) { - variableValueExpr = - `${constructionKeyword} ${TEMPLATE_COMMANDS_MODULE_REF}CompiledHostTemplate(${compiledTemplateExpr})`; - } else { - variableValueExpr = compiledTemplateExpr; - } - var varName = templateVariableName(compMeta.type); - declarations.push(`${codeGenExportVariable(varName)}${variableValueExpr};`); - declarations.push(`${codeGenValueFn([], varName, templateGetterName(compMeta.type))};`); - }); var moduleUrl = components[0].component.type.moduleUrl; return new SourceModule(`${templateModuleUrl(moduleUrl)}`, declarations.join('\n')); } @@ -215,31 +167,149 @@ export class TemplateCompiler { return this._styleCompiler.compileStylesheetCodeGen(stylesheetUrl, cssText); } - private _processTemplateCodeGen(compMeta: CompileDirectiveMetadata, - directives: CompileDirectiveMetadata[], - targetDeclarations: string[], targetTemplateArguments: any[][]) { - let uniqueDirectives = removeDuplicates(directives); + + + private _compileComponentRuntime(cacheKey: any, compMeta: CompileDirectiveMetadata, + viewDirectives: CompileDirectiveMetadata[], + pipes: CompilePipeMetadata[], + compilingComponentCacheKeys: Set): CompiledTemplate { + let uniqViewDirectives = removeDuplicates(viewDirectives); + let uniqViewPipes = removeDuplicates(pipes); + var compiledTemplate = this._compiledTemplateCache.get(cacheKey); + var done = this._compiledTemplateDone.get(cacheKey); + if (isBlank(compiledTemplate)) { + compiledTemplate = new CompiledTemplate(); + this._compiledTemplateCache.set(cacheKey, compiledTemplate); + compilingComponentCacheKeys.add(cacheKey); + done = PromiseWrapper + .all([this._styleCompiler.compileComponentRuntime(compMeta.template)].concat( + uniqViewDirectives.map(dirMeta => this.normalizeDirectiveMetadata(dirMeta)))) + .then((stylesAndNormalizedViewDirMetas: any[]) => { + var normalizedViewDirMetas = stylesAndNormalizedViewDirMetas.slice(1); + var styles = stylesAndNormalizedViewDirMetas[0]; + var parsedTemplate = this._templateParser.parse( + compMeta.template.template, normalizedViewDirMetas, uniqViewPipes, + compMeta.type.name); + + var childPromises = []; + var usedDirectives = DirectiveCollector.findUsedDirectives(parsedTemplate); + usedDirectives.components.forEach( + component => this._compileNestedComponentRuntime( + component, compilingComponentCacheKeys, childPromises)); + return PromiseWrapper.all(childPromises) + .then((_) => { + var filteredPipes = filterPipes(parsedTemplate, uniqViewPipes); + compiledTemplate.init(this._createViewFactoryRuntime( + compMeta, parsedTemplate, usedDirectives.directives, styles, + filteredPipes)); + SetWrapper.delete(compilingComponentCacheKeys, cacheKey); + return compiledTemplate; + }); + }); + this._compiledTemplateDone.set(cacheKey, done); + } + return compiledTemplate; + } + + private _compileNestedComponentRuntime(childComponentDir: CompileDirectiveMetadata, + compilingComponentCacheKeys: Set, + childPromises: Promise[]) { + var childCacheKey = childComponentDir.type.runtime; + var childViewDirectives: CompileDirectiveMetadata[] = + this._runtimeMetadataResolver.getViewDirectivesMetadata(childComponentDir.type.runtime); + var childViewPipes: CompilePipeMetadata[] = + this._runtimeMetadataResolver.getViewPipesMetadata(childComponentDir.type.runtime); + var childIsRecursive = SetWrapper.has(compilingComponentCacheKeys, childCacheKey); + this._compileComponentRuntime(childCacheKey, childComponentDir, childViewDirectives, + childViewPipes, compilingComponentCacheKeys); + if (!childIsRecursive) { + // Only wait for a child if it is not a cycle + childPromises.push(this._compiledTemplateDone.get(childCacheKey)); + } + } + + private _createViewFactoryRuntime(compMeta: CompileDirectiveMetadata, + parsedTemplate: TemplateAst[], + directives: CompileDirectiveMetadata[], styles: string[], + pipes: CompilePipeMetadata[]): Function { + if (IS_DART || !this._genConfig.useJit) { + var changeDetectorFactories = this._cdCompiler.compileComponentRuntime( + compMeta.type, compMeta.changeDetection, parsedTemplate); + var protoViews = this._protoViewCompiler.compileProtoViewRuntime( + this._resolvedMetadataCache, compMeta, parsedTemplate, pipes); + return this._viewCompiler.compileComponentRuntime( + compMeta, parsedTemplate, styles, protoViews.protoViews, changeDetectorFactories, + (compMeta) => this._getNestedComponentViewFactory(compMeta)); + } else { + var declarations = []; + var viewFactoryExpr = this._createViewFactoryCodeGen('resolvedMetadataCache', compMeta, + new SourceExpression([], 'styles'), + parsedTemplate, pipes, declarations); + var vars: {[key: string]: any} = + {'exports': {}, 'styles': styles, 'resolvedMetadataCache': this._resolvedMetadataCache}; + directives.forEach(dirMeta => { + vars[dirMeta.type.name] = dirMeta.type.runtime; + if (dirMeta.isComponent && dirMeta.type.runtime !== compMeta.type.runtime) { + vars[`viewFactory_${dirMeta.type.name}0`] = this._getNestedComponentViewFactory(dirMeta); + } + }); + pipes.forEach(pipeMeta => vars[pipeMeta.type.name] = pipeMeta.type.runtime); + var declarationsWithoutImports = + SourceModule.getSourceWithoutImports(declarations.join('\n')); + return evalExpression( + `viewFactory_${compMeta.type.name}`, viewFactoryExpr, declarationsWithoutImports, + mergeStringMaps( + [vars, CHANGE_DETECTION_JIT_IMPORTS, PROTO_VIEW_JIT_IMPORTS, VIEW_JIT_IMPORTS])); + } + } + + private _getNestedComponentViewFactory(compMeta: CompileDirectiveMetadata): Function { + return this._compiledTemplateCache.get(compMeta.type.runtime).viewFactory; + } + + private _compileComponentCodeGen(compMeta: CompileDirectiveMetadata, + directives: CompileDirectiveMetadata[], + pipes: CompilePipeMetadata[], + targetDeclarations: string[]): string { + let uniqueDirectives = removeDuplicates(directives); + let uniqPipes = removeDuplicates(pipes); var styleExpr = this._styleCompiler.compileComponentCodeGen(compMeta.template); var parsedTemplate = this._templateParser.parse(compMeta.template.template, uniqueDirectives, - compMeta.type.name); + uniqPipes, compMeta.type.name); + var filteredPipes = filterPipes(parsedTemplate, uniqPipes); + return this._createViewFactoryCodeGen( + `${METADATA_CACHE_MODULE_REF}CODEGEN_RESOLVED_METADATA_CACHE`, compMeta, styleExpr, + parsedTemplate, filteredPipes, targetDeclarations); + } + + private _createViewFactoryCodeGen(resolvedMetadataCacheExpr: string, + compMeta: CompileDirectiveMetadata, styleExpr: SourceExpression, + parsedTemplate: TemplateAst[], pipes: CompilePipeMetadata[], + targetDeclarations: string[]): string { var changeDetectorsExprs = this._cdCompiler.compileComponentCodeGen( compMeta.type, compMeta.changeDetection, parsedTemplate); - var commandsExpr = this._commandCompiler.compileComponentCodeGen( - compMeta, parsedTemplate, changeDetectorsExprs.expressions, - codeGenComponentTemplateFactory); + var protoViewExprs = this._protoViewCompiler.compileProtoViewCodeGen( + new Expression(resolvedMetadataCacheExpr), compMeta, parsedTemplate, pipes); + var viewFactoryExpr = this._viewCompiler.compileComponentCodeGen( + compMeta, parsedTemplate, styleExpr, protoViewExprs.protoViews, changeDetectorsExprs, + codeGenComponentViewFactoryName); - addAll(styleExpr.declarations, targetDeclarations); addAll(changeDetectorsExprs.declarations, targetDeclarations); - addAll(commandsExpr.declarations, targetDeclarations); + addAll(protoViewExprs.declarations, targetDeclarations); + addAll(viewFactoryExpr.declarations, targetDeclarations); - targetTemplateArguments.push( - [changeDetectorsExprs.expressions[0], commandsExpr.expression, styleExpr.expression]); + return viewFactoryExpr.expression; } } export class NormalizedComponentWithViewDirectives { constructor(public component: CompileDirectiveMetadata, - public directives: CompileDirectiveMetadata[]) {} + public directives: CompileDirectiveMetadata[], public pipes: CompilePipeMetadata[]) {} +} + +class CompiledTemplate { + viewFactory: Function = null; + init(viewFactory: Function) { this.viewFactory = viewFactory; } } function assertComponent(meta: CompileDirectiveMetadata) { @@ -248,30 +318,28 @@ function assertComponent(meta: CompileDirectiveMetadata) { } } -function templateVariableName(type: CompileTypeMetadata): string { - return `${type.name}Template`; -} - -function templateGetterName(type: CompileTypeMetadata): string { - return `${templateVariableName(type)}Getter`; -} - function templateModuleUrl(moduleUrl: string): string { var urlWithoutSuffix = moduleUrl.substring(0, moduleUrl.length - MODULE_SUFFIX.length); return `${urlWithoutSuffix}.template${MODULE_SUFFIX}`; } -function addAll(source: any[], target: any[]) { - for (var i = 0; i < source.length; i++) { - target.push(source[i]); - } + +function codeGenHostViewFactoryName(type: CompileTypeMetadata): string { + return `hostViewFactory_${type.name}`; } -function codeGenComponentTemplateFactory(nestedCompType: CompileDirectiveMetadata): string { - return `${moduleRef(templateModuleUrl(nestedCompType.type.moduleUrl))}${templateGetterName(nestedCompType.type)}`; +function codeGenComponentViewFactoryName(nestedCompType: CompileDirectiveMetadata): string { + return `${moduleRef(templateModuleUrl(nestedCompType.type.moduleUrl))}viewFactory_${nestedCompType.type.name}0`; } -function removeDuplicates(items: CompileDirectiveMetadata[]): CompileDirectiveMetadata[] { +function mergeStringMaps(maps: Array<{[key: string]: any}>): {[key: string]: any} { + var result = {}; + maps.forEach( + (map) => { StringMapWrapper.forEach(map, (value, key) => { result[key] = value; }); }); + return result; +} + +function removeDuplicates(items: CompileMetadataWithType[]): CompileMetadataWithType[] { let res = []; items.forEach(item => { let hasMatch = @@ -284,3 +352,100 @@ function removeDuplicates(items: CompileDirectiveMetadata[]): CompileDirectiveMe }); return res; } + +class DirectiveCollector implements TemplateAstVisitor { + static findUsedDirectives(parsedTemplate: TemplateAst[]): DirectiveCollector { + var collector = new DirectiveCollector(); + templateVisitAll(collector, parsedTemplate); + return collector; + } + + directives: CompileDirectiveMetadata[] = []; + components: CompileDirectiveMetadata[] = []; + + visitBoundText(ast: BoundTextAst, context: any): any { return null; } + visitText(ast: TextAst, context: any): any { return null; } + + visitNgContent(ast: NgContentAst, context: any): any { return null; } + + visitElement(ast: ElementAst, context: any): any { + templateVisitAll(this, ast.directives); + templateVisitAll(this, ast.children); + return null; + } + + visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { + templateVisitAll(this, ast.directives); + templateVisitAll(this, ast.children); + return null; + } + visitVariable(ast: VariableAst, ctx: any): any { return null; } + visitAttr(ast: AttrAst, attrNameAndValues: {[key: string]: string}): any { return null; } + visitDirective(ast: DirectiveAst, ctx: any): any { + if (ast.directive.isComponent) { + this.components.push(ast.directive); + } + this.directives.push(ast.directive); + return null; + } + visitEvent(ast: BoundEventAst, eventTargetAndNames: Map): any { + return null; + } + visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; } + visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; } +} + + +function filterPipes(template: TemplateAst[], + allPipes: CompilePipeMetadata[]): CompilePipeMetadata[] { + var visitor = new PipeVisitor(); + templateVisitAll(visitor, template); + return allPipes.filter((pipeMeta) => SetWrapper.has(visitor.collector.pipes, pipeMeta.name)); +} + +class PipeVisitor implements TemplateAstVisitor { + collector: PipeCollector = new PipeCollector(); + + visitBoundText(ast: BoundTextAst, context: any): any { + ast.value.visit(this.collector); + return null; + } + visitText(ast: TextAst, context: any): any { return null; } + + visitNgContent(ast: NgContentAst, context: any): any { return null; } + + visitElement(ast: ElementAst, context: any): any { + templateVisitAll(this, ast.inputs); + templateVisitAll(this, ast.outputs); + templateVisitAll(this, ast.directives); + templateVisitAll(this, ast.children); + return null; + } + + visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { + templateVisitAll(this, ast.outputs); + templateVisitAll(this, ast.directives); + templateVisitAll(this, ast.children); + return null; + } + visitVariable(ast: VariableAst, ctx: any): any { return null; } + visitAttr(ast: AttrAst, attrNameAndValues: {[key: string]: string}): any { return null; } + visitDirective(ast: DirectiveAst, ctx: any): any { + templateVisitAll(this, ast.inputs); + templateVisitAll(this, ast.hostEvents); + templateVisitAll(this, ast.hostProperties); + return null; + } + visitEvent(ast: BoundEventAst, eventTargetAndNames: Map): any { + ast.handler.visit(this.collector); + return null; + } + visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { + ast.value.visit(this.collector); + return null; + } + visitElementProperty(ast: BoundElementPropertyAst, context: any): any { + ast.value.visit(this.collector); + return null; + } +} diff --git a/modules/angular2/src/compiler/template_parser.ts b/modules/angular2/src/compiler/template_parser.ts index c93307378b..deaae63693 100644 --- a/modules/angular2/src/compiler/template_parser.ts +++ b/modules/angular2/src/compiler/template_parser.ts @@ -5,10 +5,11 @@ import {CONST_EXPR} from 'angular2/src/facade/lang'; import {BaseException} from 'angular2/src/facade/exceptions'; import {Parser, AST, ASTWithSource} from 'angular2/src/core/change_detection/change_detection'; import {TemplateBinding} from 'angular2/src/core/change_detection/parser/ast'; -import {CompileDirectiveMetadata} from './directive_metadata'; +import {CompileDirectiveMetadata, CompilePipeMetadata} from './directive_metadata'; import {HtmlParser} from './html_parser'; import {splitNsName} from './html_tags'; import {ParseSourceSpan, ParseError, ParseLocation} from './parse_util'; +import {RecursiveAstVisitor, BindingPipe} from 'angular2/src/core/change_detection/parser/ast'; import { @@ -88,9 +89,10 @@ export class TemplateParser { private _htmlParser: HtmlParser, @Optional() @Inject(TEMPLATE_TRANSFORMS) public transforms: TemplateAstVisitor[]) {} - parse(template: string, directives: CompileDirectiveMetadata[], + parse(template: string, directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[], templateUrl: string): TemplateAst[] { - var parseVisitor = new TemplateParseVisitor(directives, this._exprParser, this._schemaRegistry); + var parseVisitor = + new TemplateParseVisitor(directives, pipes, this._exprParser, this._schemaRegistry); var htmlAstWithErrors = this._htmlParser.parse(template, templateUrl); var result = htmlVisitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_COMPONENT); var errors: ParseError[] = htmlAstWithErrors.errors.concat(parseVisitor.errors); @@ -111,9 +113,10 @@ class TemplateParseVisitor implements HtmlAstVisitor { errors: TemplateParseError[] = []; directivesIndex = new Map(); ngContentCount: number = 0; + pipesByName: Map; - constructor(directives: CompileDirectiveMetadata[], private _exprParser: Parser, - private _schemaRegistry: ElementSchemaRegistry) { + constructor(directives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[], + private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry) { this.selectorMatcher = new SelectorMatcher(); ListWrapper.forEachWithIndex(directives, (directive: CompileDirectiveMetadata, index: number) => { @@ -121,6 +124,8 @@ class TemplateParseVisitor implements HtmlAstVisitor { this.selectorMatcher.addSelectables(selector, directive); this.directivesIndex.set(directive, index); }); + this.pipesByName = new Map(); + pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe)); } private _reportError(message: string, sourceSpan: ParseSourceSpan) { @@ -130,7 +135,9 @@ class TemplateParseVisitor implements HtmlAstVisitor { private _parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource { var sourceInfo = sourceSpan.start.toString(); try { - return this._exprParser.parseInterpolation(value, sourceInfo); + var ast = this._exprParser.parseInterpolation(value, sourceInfo); + this._checkPipes(ast, sourceSpan); + return ast; } catch (e) { this._reportError(`${e}`, sourceSpan); return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo); @@ -140,7 +147,9 @@ class TemplateParseVisitor implements HtmlAstVisitor { private _parseAction(value: string, sourceSpan: ParseSourceSpan): ASTWithSource { var sourceInfo = sourceSpan.start.toString(); try { - return this._exprParser.parseAction(value, sourceInfo); + var ast = this._exprParser.parseAction(value, sourceInfo); + this._checkPipes(ast, sourceSpan); + return ast; } catch (e) { this._reportError(`${e}`, sourceSpan); return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo); @@ -150,7 +159,9 @@ class TemplateParseVisitor implements HtmlAstVisitor { private _parseBinding(value: string, sourceSpan: ParseSourceSpan): ASTWithSource { var sourceInfo = sourceSpan.start.toString(); try { - return this._exprParser.parseBinding(value, sourceInfo); + var ast = this._exprParser.parseBinding(value, sourceInfo); + this._checkPipes(ast, sourceSpan); + return ast; } catch (e) { this._reportError(`${e}`, sourceSpan); return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo); @@ -160,13 +171,31 @@ class TemplateParseVisitor implements HtmlAstVisitor { private _parseTemplateBindings(value: string, sourceSpan: ParseSourceSpan): TemplateBinding[] { var sourceInfo = sourceSpan.start.toString(); try { - return this._exprParser.parseTemplateBindings(value, sourceInfo); + var bindings = this._exprParser.parseTemplateBindings(value, sourceInfo); + bindings.forEach((binding) => { + if (isPresent(binding.expression)) { + this._checkPipes(binding.expression, sourceSpan); + } + }); + return bindings; } catch (e) { this._reportError(`${e}`, sourceSpan); return []; } } + private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) { + if (isPresent(ast)) { + var collector = new PipeCollector(); + ast.visit(collector); + collector.pipes.forEach((pipeName) => { + if (!this.pipesByName.has(pipeName)) { + this._reportError(`The pipe '${pipeName}' could not be found`, sourceSpan); + } + }); + } + } + visitText(ast: HtmlTextAst, component: Component): any { var ngContentIndex = component.findNgContentIndex(TEXT_CSS_SELECTOR); var expr = this._parseInterpolation(ast.value, ast.sourceSpan); @@ -714,3 +743,14 @@ function createElementCssSelector(elementName: string, matchableAttrs: string[][ var EMPTY_COMPONENT = new Component(new SelectorMatcher(), null); var NON_BINDABLE_VISITOR = new NonBindableVisitor(); + + +export class PipeCollector extends RecursiveAstVisitor { + pipes: Set = new Set(); + visitPipe(ast: BindingPipe): any { + this.pipes.add(ast.name); + ast.exp.visit(this); + this.visitAll(ast.args); + return null; + } +} diff --git a/modules/angular2/src/compiler/util.ts b/modules/angular2/src/compiler/util.ts index ca0c6e8005..f3ebdfd1a7 100644 --- a/modules/angular2/src/compiler/util.ts +++ b/modules/angular2/src/compiler/util.ts @@ -1,4 +1,11 @@ -import {IS_DART, StringWrapper, isBlank} from 'angular2/src/facade/lang'; +import { + IS_DART, + StringWrapper, + isBlank, + isPresent, + isString, + isArray +} from 'angular2/src/facade/lang'; var CAMEL_CASE_REGEXP = /([A-Z])/g; var DASH_CASE_REGEXP = /-([a-z])/g; @@ -7,6 +14,8 @@ var DOUBLE_QUOTE_ESCAPE_STRING_RE = /"|\\|\n|\r|\$/g; export var MODULE_SUFFIX = IS_DART ? '.dart' : '.js'; +export var CONST_VAR = IS_DART ? 'const' : 'var'; + export function camelCaseToDashCase(input: string): string { return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP, (m) => { return '-' + m[1].toLowerCase(); }); @@ -63,12 +72,19 @@ export function codeGenConstConstructorCall(name: string): string { export function codeGenValueFn(params: string[], value: string, fnName: string = ''): string { if (IS_DART) { - return `${fnName}(${params.join(',')}) => ${value}`; + return `${codeGenFnHeader(params, fnName)} => ${value}`; } else { - return `function ${fnName}(${params.join(',')}) { return ${value}; }`; + return `${codeGenFnHeader(params, fnName)} { return ${value}; }`; } } +export function codeGenFnHeader(params: string[], fnName: string = ''): string { + if (IS_DART) { + return `${fnName}(${params.join(',')})`; + } else { + return `function ${fnName}(${params.join(',')})`; + } +} export function codeGenToString(expr: string): string { if (IS_DART) { return `'\${${expr}}'`; @@ -86,3 +102,77 @@ export function splitAtColon(input: string, defaultValues: string[]): string[] { return defaultValues; } } + + +export class Statement { + constructor(public statement: string) {} +} + +export class Expression { + constructor(public expression: string, public isArray = false) {} +} + +export function escapeValue(value: any): string { + if (value instanceof Expression) { + return value.expression; + } else if (isString(value)) { + return escapeSingleQuoteString(value); + } else if (isBlank(value)) { + return 'null'; + } else { + return `${value}`; + } +} + +export function codeGenArray(data: any[]): string { + return `[${data.map(escapeValue).join(',')}]`; +} + +export function codeGenFlatArray(values: any[]): string { + var result = '(['; + var isFirstArrayEntry = true; + var concatFn = IS_DART ? '.addAll' : 'concat'; + for (var i = 0; i < values.length; i++) { + var value = values[i]; + if (value instanceof Expression && (value).isArray) { + result += `]).${concatFn}(${value.expression}).${concatFn}([`; + isFirstArrayEntry = true; + } else { + if (!isFirstArrayEntry) { + result += ','; + } + isFirstArrayEntry = false; + result += escapeValue(value); + } + } + result += '])'; + return result; +} + +export function codeGenStringMap(keyValueArray: any[][]): string { + return `{${keyValueArray.map(codeGenKeyValue).join(',')}}`; +} + +function codeGenKeyValue(keyValue: any[]): string { + return `${escapeValue(keyValue[0])}:${escapeValue(keyValue[1])}`; +} + +export function addAll(source: any[], target: any[]) { + for (var i = 0; i < source.length; i++) { + target.push(source[i]); + } +} + +export function flattenArray(source: any[], target: any[]): any[] { + if (isPresent(source)) { + for (var i = 0; i < source.length; i++) { + var item = source[i]; + if (isArray(item)) { + flattenArray(item, target); + } else { + target.push(item); + } + } + } + return target; +} diff --git a/modules/angular2/src/compiler/view_compiler.ts b/modules/angular2/src/compiler/view_compiler.ts new file mode 100644 index 0000000000..2f6ec19697 --- /dev/null +++ b/modules/angular2/src/compiler/view_compiler.ts @@ -0,0 +1,600 @@ +import { + isPresent, + isBlank, + Type, + isString, + StringWrapper, + IS_DART, + CONST_EXPR +} from 'angular2/src/facade/lang'; +import {SetWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection'; +import { + TemplateAst, + TemplateAstVisitor, + NgContentAst, + EmbeddedTemplateAst, + ElementAst, + VariableAst, + BoundEventAst, + BoundElementPropertyAst, + AttrAst, + BoundTextAst, + TextAst, + DirectiveAst, + BoundDirectivePropertyAst, + templateVisitAll +} from './template_ast'; +import {CompileTypeMetadata, CompileDirectiveMetadata} from './directive_metadata'; +import {SourceExpressions, SourceExpression, moduleRef} from './source_module'; +import { + AppProtoView, + AppView, + flattenNestedViewRenderNodes, + checkSlotCount +} from 'angular2/src/core/linker/view'; +import {ViewType} from 'angular2/src/core/linker/view_type'; +import {AppViewManager_} from 'angular2/src/core/linker/view_manager'; +import {AppProtoElement, AppElement} from 'angular2/src/core/linker/element'; +import {Renderer, ParentRenderer} from 'angular2/src/core/render/api'; +import {ViewEncapsulation} from 'angular2/src/core/metadata/view'; +import { + escapeSingleQuoteString, + codeGenConstConstructorCall, + codeGenValueFn, + codeGenFnHeader, + MODULE_SUFFIX, + Statement, + escapeValue, + codeGenArray, + codeGenFlatArray, + Expression, + flattenArray, + CONST_VAR +} from './util'; +import {ResolvedProvider, Injectable, Injector} from 'angular2/src/core/di'; + +import { + APP_VIEW_MODULE_REF, + APP_EL_MODULE_REF, + METADATA_MODULE_REF, + CompileProtoView, + CompileProtoElement +} from './proto_view_compiler'; + +export const VIEW_JIT_IMPORTS = CONST_EXPR({ + 'AppView': AppView, + 'AppElement': AppElement, + 'flattenNestedViewRenderNodes': flattenNestedViewRenderNodes, + 'checkSlotCount': checkSlotCount +}); + + +@Injectable() +export class ViewCompiler { + constructor() {} + + compileComponentRuntime(component: CompileDirectiveMetadata, template: TemplateAst[], + styles: Array, + protoViews: CompileProtoView[], + changeDetectorFactories: Function[], + componentViewFactory: Function): Function { + var viewFactory = new RuntimeViewFactory(component, styles, protoViews, changeDetectorFactories, + componentViewFactory); + return viewFactory.createViewFactory(template, 0, []); + } + + compileComponentCodeGen(component: CompileDirectiveMetadata, template: TemplateAst[], + styles: SourceExpression, + protoViews: CompileProtoView[], + changeDetectorFactoryExpressions: SourceExpressions, + componentViewFactory: Function): SourceExpression { + var viewFactory = new CodeGenViewFactory( + component, styles, protoViews, changeDetectorFactoryExpressions, componentViewFactory); + var targetStatements: Statement[] = []; + var viewFactoryExpression = viewFactory.createViewFactory(template, 0, targetStatements); + return new SourceExpression(targetStatements.map(stmt => stmt.statement), + viewFactoryExpression.expression); + } +} + +interface ViewFactory { + createText(renderer: EXPRESSION, parent: EXPRESSION, text: string, + targetStatements: STATEMENT[]): EXPRESSION; + + createElement(renderer: EXPRESSION, parent: EXPRESSION, name: string, rootSelector: EXPRESSION, + targetStatements: STATEMENT[]): EXPRESSION; + + createTemplateAnchor(renderer: EXPRESSION, parent: EXPRESSION, + targetStatements: STATEMENT[]): EXPRESSION; + + createGlobalEventListener(renderer: EXPRESSION, view: EXPRESSION, boundElementIndex: number, + eventAst: BoundEventAst, targetStatements: STATEMENT[]): EXPRESSION; + + createElementEventListener(renderer: EXPRESSION, view: EXPRESSION, boundElementIndex: number, + renderNode: EXPRESSION, eventAst: BoundEventAst, + targetStatements: STATEMENT[]); + + setElementAttribute(renderer: EXPRESSION, renderNode: EXPRESSION, attrName: string, + attrValue: string, targetStatements: STATEMENT[]); + + createAppElement(appProtoEl: EXPRESSION, view: EXPRESSION, renderNode: EXPRESSION, + parentAppEl: EXPRESSION, embeddedViewFactory: EXPRESSION, + targetStatements: STATEMENT[]): EXPRESSION; + + createAndSetComponentView(renderer: EXPRESSION, viewManager: EXPRESSION, view: EXPRESSION, + appEl: EXPRESSION, component: CompileDirectiveMetadata, + contentNodesByNgContentIndex: EXPRESSION[][], + targetStatements: STATEMENT[]); + + getProjectedNodes(projectableNodes: EXPRESSION, ngContentIndex: number): EXPRESSION; + + appendProjectedNodes(renderer: EXPRESSION, parent: EXPRESSION, nodes: EXPRESSION, + targetStatements: STATEMENT[]); + + createViewFactory(asts: TemplateAst[], embeddedTemplateIndex: number, + targetStatements: STATEMENT[]): EXPRESSION; +} + +class CodeGenViewFactory implements ViewFactory { + private _nextVarId: number = 0; + constructor(public component: CompileDirectiveMetadata, public styles: SourceExpression, + public protoViews: CompileProtoView[], + public changeDetectorExpressions: SourceExpressions, + public componentViewFactory: Function) {} + + private _nextVar(prefix: string): string { + return `${prefix}${this._nextVarId++}_${this.component.type.name}`; + } + + private _nextRenderVar(): string { return this._nextVar('render'); } + + private _nextAppVar(): string { return this._nextVar('app'); } + + private _nextDisposableVar(): string { + return `disposable${this._nextVarId++}_${this.component.type.name}`; + } + + createText(renderer: Expression, parent: Expression, text: string, + targetStatements: Statement[]): Expression { + var varName = this._nextRenderVar(); + var statement = + `var ${varName} = ${renderer.expression}.createText(${isPresent(parent) ? parent.expression : null}, ${escapeSingleQuoteString(text)});`; + targetStatements.push(new Statement(statement)); + return new Expression(varName); + } + + createElement(renderer: Expression, parentRenderNode: Expression, name: string, + rootSelector: Expression, targetStatements: Statement[]): Expression { + var varName = this._nextRenderVar(); + var valueExpr; + if (isPresent(rootSelector)) { + valueExpr = `${rootSelector.expression} == null ? + ${renderer.expression}.createElement(${isPresent(parentRenderNode) ? parentRenderNode.expression : null}, ${escapeSingleQuoteString(name)}) : + ${renderer.expression}.selectRootElement(${rootSelector.expression});`; + } else { + valueExpr = + `${renderer.expression}.createElement(${isPresent(parentRenderNode) ? parentRenderNode.expression : null}, ${escapeSingleQuoteString(name)})`; + } + var statement = `var ${varName} = ${valueExpr};`; + targetStatements.push(new Statement(statement)); + return new Expression(varName); + } + + createTemplateAnchor(renderer: Expression, parentRenderNode: Expression, + targetStatements: Statement[]): Expression { + var varName = this._nextRenderVar(); + var valueExpr = + `${renderer.expression}.createTemplateAnchor(${isPresent(parentRenderNode) ? parentRenderNode.expression : null});`; + targetStatements.push(new Statement(`var ${varName} = ${valueExpr}`)); + return new Expression(varName); + } + + createGlobalEventListener(renderer: Expression, appView: Expression, boundElementIndex: number, + eventAst: BoundEventAst, targetStatements: Statement[]): Expression { + var disposableVar = this._nextDisposableVar(); + var eventHandlerExpr = codeGenEventHandler(appView, boundElementIndex, eventAst.fullName); + targetStatements.push(new Statement( + `var ${disposableVar} = ${renderer.expression}.listenGlobal(${escapeValue(eventAst.target)}, ${escapeValue(eventAst.name)}, ${eventHandlerExpr});`)); + return new Expression(disposableVar); + } + + createElementEventListener(renderer: Expression, appView: Expression, boundElementIndex: number, + renderNode: Expression, eventAst: BoundEventAst, + targetStatements: Statement[]) { + var eventHandlerExpr = codeGenEventHandler(appView, boundElementIndex, eventAst.fullName); + targetStatements.push(new Statement( + `${renderer.expression}.listen(${renderNode.expression}, ${escapeValue(eventAst.name)}, ${eventHandlerExpr});`)); + } + + setElementAttribute(renderer: Expression, renderNode: Expression, attrName: string, + attrValue: string, targetStatements: Statement[]) { + targetStatements.push(new Statement( + `${renderer.expression}.setElementAttribute(${renderNode.expression}, ${escapeSingleQuoteString(attrName)}, ${escapeSingleQuoteString(attrValue)});`)); + } + + createAppElement(appProtoEl: Expression, appView: Expression, renderNode: Expression, + parentAppEl: Expression, embeddedViewFactory: Expression, + targetStatements: Statement[]): Expression { + var appVar = this._nextAppVar(); + var varValue = + `new ${APP_EL_MODULE_REF}AppElement(${appProtoEl.expression}, ${appView.expression}, + ${isPresent(parentAppEl) ? parentAppEl.expression : null}, ${renderNode.expression}, ${isPresent(embeddedViewFactory) ? embeddedViewFactory.expression : null})`; + targetStatements.push(new Statement(`var ${appVar} = ${varValue};`)); + return new Expression(appVar); + } + + createAndSetComponentView(renderer: Expression, viewManager: Expression, view: Expression, + appEl: Expression, component: CompileDirectiveMetadata, + contentNodesByNgContentIndex: Expression[][], + targetStatements: Statement[]) { + var codeGenContentNodes; + if (this.component.type.isHost) { + codeGenContentNodes = `${view.expression}.projectableNodes`; + } else { + codeGenContentNodes = + `[${contentNodesByNgContentIndex.map( nodes => codeGenFlatArray(nodes) ).join(',')}]`; + } + targetStatements.push(new Statement( + `${this.componentViewFactory(component)}(${renderer.expression}, ${viewManager.expression}, ${appEl.expression}, ${codeGenContentNodes}, null, null, null);`)); + } + + getProjectedNodes(projectableNodes: Expression, ngContentIndex: number): Expression { + return new Expression(`${projectableNodes.expression}[${ngContentIndex}]`, true); + } + + appendProjectedNodes(renderer: Expression, parent: Expression, nodes: Expression, + targetStatements: Statement[]) { + targetStatements.push(new Statement( + `${renderer.expression}.projectNodes(${parent.expression}, ${APP_VIEW_MODULE_REF}flattenNestedViewRenderNodes(${nodes.expression}));`)); + } + + createViewFactory(asts: TemplateAst[], embeddedTemplateIndex: number, + targetStatements: Statement[]): Expression { + var compileProtoView = this.protoViews[embeddedTemplateIndex]; + var isHostView = this.component.type.isHost; + var isComponentView = embeddedTemplateIndex === 0 && !isHostView; + var visitor = new ViewBuilderVisitor( + new Expression('renderer'), new Expression('viewManager'), + new Expression('projectableNodes'), isHostView ? new Expression('rootSelector') : null, + new Expression('view'), compileProtoView, targetStatements, this); + + templateVisitAll( + visitor, asts, + new ParentElement(isComponentView ? new Expression('parentRenderNode') : null, null, null)); + + var appProtoView = compileProtoView.protoView.expression; + var viewFactoryName = codeGenViewFactoryName(this.component, embeddedTemplateIndex); + var changeDetectorFactory = this.changeDetectorExpressions.expressions[embeddedTemplateIndex]; + var factoryArgs = [ + 'parentRenderer', + 'viewManager', + 'containerEl', + 'projectableNodes', + 'rootSelector', + 'dynamicallyCreatedProviders', + 'rootInjector' + ]; + var initRendererStmts = []; + var rendererExpr = `parentRenderer`; + if (embeddedTemplateIndex === 0) { + var renderCompTypeVar = this._nextVar('renderType'); + targetStatements.push(new Statement(`var ${renderCompTypeVar} = null;`)); + var stylesVar = this._nextVar('styles'); + targetStatements.push( + new Statement(`${CONST_VAR} ${stylesVar} = ${this.styles.expression};`)); + var encapsulation = this.component.template.encapsulation; + initRendererStmts.push(`if (${renderCompTypeVar} == null) { + ${renderCompTypeVar} = viewManager.createRenderComponentType(${codeGenViewEncapsulation(encapsulation)}, ${stylesVar}); + }`); + rendererExpr = `parentRenderer.renderComponent(${renderCompTypeVar})`; + } + var statement = ` +${codeGenFnHeader(factoryArgs, viewFactoryName)}{ + ${initRendererStmts.join('\n')} + var renderer = ${rendererExpr}; + var view = new ${APP_VIEW_MODULE_REF}AppView( + ${appProtoView}, renderer, viewManager, + projectableNodes, + containerEl, + dynamicallyCreatedProviders, rootInjector, + ${changeDetectorFactory}() + ); + ${APP_VIEW_MODULE_REF}checkSlotCount(${escapeValue(this.component.type.name)}, ${this.component.template.ngContentSelectors.length}, projectableNodes); + ${isComponentView ? 'var parentRenderNode = renderer.createViewRoot(view.containerAppElement.nativeElement);' : ''} + ${visitor.renderStmts.map(stmt => stmt.statement).join('\n')} + ${visitor.appStmts.map(stmt => stmt.statement).join('\n')} + + view.init(${codeGenFlatArray(visitor.rootNodesOrAppElements)}, ${codeGenArray(visitor.renderNodes)}, ${codeGenArray(visitor.appDisposables)}, + ${codeGenArray(visitor.appElements)}); + return view; +}`; + targetStatements.push(new Statement(statement)); + return new Expression(viewFactoryName); + } +} + +class RuntimeViewFactory implements ViewFactory { + constructor(public component: CompileDirectiveMetadata, public styles: Array, + public protoViews: CompileProtoView[], + public changeDetectorFactories: Function[], public componentViewFactory: Function) {} + + createText(renderer: Renderer, parent: any, text: string, targetStatements: any[]): any { + return renderer.createText(parent, text); + } + + createElement(renderer: Renderer, parent: any, name: string, rootSelector: string, + targetStatements: any[]): any { + var el; + if (isPresent(rootSelector)) { + el = renderer.selectRootElement(rootSelector); + } else { + el = renderer.createElement(parent, name); + } + return el; + } + + createTemplateAnchor(renderer: Renderer, parent: any, targetStatements: any[]): any { + return renderer.createTemplateAnchor(parent); + } + + createGlobalEventListener(renderer: Renderer, appView: AppView, boundElementIndex: number, + eventAst: BoundEventAst, targetStatements: any[]): any { + return renderer.listenGlobal( + eventAst.target, eventAst.name, + (event) => appView.triggerEventHandlers(eventAst.fullName, event, boundElementIndex)); + } + + createElementEventListener(renderer: Renderer, appView: AppView, boundElementIndex: number, + renderNode: any, eventAst: BoundEventAst, targetStatements: any[]) { + renderer.listen(renderNode, eventAst.name, (event) => appView.triggerEventHandlers( + eventAst.fullName, event, boundElementIndex)); + } + + setElementAttribute(renderer: Renderer, renderNode: any, attrName: string, attrValue: string, + targetStatements: any[]) { + renderer.setElementAttribute(renderNode, attrName, attrValue); + } + + createAppElement(appProtoEl: AppProtoElement, appView: AppView, renderNode: any, + parentAppEl: AppElement, embeddedViewFactory: Function, + targetStatements: any[]): any { + return new AppElement(appProtoEl, appView, parentAppEl, renderNode, embeddedViewFactory); + } + + createAndSetComponentView(renderer: Renderer, viewManager: AppViewManager_, appView: AppView, + appEl: AppElement, component: CompileDirectiveMetadata, + contentNodesByNgContentIndex: Array>, + targetStatements: any[]) { + var flattenedContentNodes; + if (this.component.type.isHost) { + flattenedContentNodes = appView.projectableNodes; + } else { + flattenedContentNodes = ListWrapper.createFixedSize(contentNodesByNgContentIndex.length); + for (var i = 0; i < contentNodesByNgContentIndex.length; i++) { + flattenedContentNodes[i] = flattenArray(contentNodesByNgContentIndex[i], []); + } + } + this.componentViewFactory(component)(renderer, viewManager, appEl, flattenedContentNodes); + } + + getProjectedNodes(projectableNodes: any[][], ngContentIndex: number): any[] { + return projectableNodes[ngContentIndex]; + } + + appendProjectedNodes(renderer: Renderer, parent: any, nodes: any[], targetStatements: any[]) { + renderer.projectNodes(parent, flattenNestedViewRenderNodes(nodes)); + } + + createViewFactory(asts: TemplateAst[], embeddedTemplateIndex: number, + targetStatements: any[]): Function { + var compileProtoView = this.protoViews[embeddedTemplateIndex]; + var isComponentView = compileProtoView.protoView.type === ViewType.COMPONENT; + var renderComponentType = null; + return (parentRenderer: ParentRenderer, viewManager: AppViewManager_, containerEl: AppElement, + projectableNodes: any[][], rootSelector: string = null, + dynamicallyCreatedProviders: ResolvedProvider[] = null, + rootInjector: Injector = null) => { + checkSlotCount(this.component.type.name, this.component.template.ngContentSelectors.length, + projectableNodes); + var renderer; + if (embeddedTemplateIndex === 0) { + if (isBlank(renderComponentType)) { + renderComponentType = viewManager.createRenderComponentType( + this.component.template.encapsulation, this.styles); + } + renderer = parentRenderer.renderComponent(renderComponentType); + } else { + renderer = parentRenderer; + } + var changeDetector = this.changeDetectorFactories[embeddedTemplateIndex](); + var view = + new AppView(compileProtoView.protoView, renderer, viewManager, projectableNodes, + containerEl, dynamicallyCreatedProviders, rootInjector, changeDetector); + var visitor = new ViewBuilderVisitor( + renderer, viewManager, projectableNodes, rootSelector, view, compileProtoView, [], this); + var parentRenderNode = + isComponentView ? renderer.createViewRoot(containerEl.nativeElement) : null; + templateVisitAll(visitor, asts, new ParentElement(parentRenderNode, null, null)); + view.init(flattenArray(visitor.rootNodesOrAppElements, []), visitor.renderNodes, + visitor.appDisposables, visitor.appElements); + return view; + }; + } +} + +class ParentElement { + public contentNodesByNgContentIndex: Array[]; + + constructor(public renderNode: EXPRESSION, public appEl: EXPRESSION, + public component: CompileDirectiveMetadata) { + if (isPresent(component)) { + this.contentNodesByNgContentIndex = + ListWrapper.createFixedSize(component.template.ngContentSelectors.length); + for (var i = 0; i < this.contentNodesByNgContentIndex.length; i++) { + this.contentNodesByNgContentIndex[i] = []; + } + } else { + this.contentNodesByNgContentIndex = null; + } + } + + addContentNode(ngContentIndex: number, nodeExpr: EXPRESSION) { + this.contentNodesByNgContentIndex[ngContentIndex].push(nodeExpr); + } +} + +class ViewBuilderVisitor implements TemplateAstVisitor { + renderStmts: Array = []; + renderNodes: EXPRESSION[] = []; + appStmts: Array = []; + appElements: EXPRESSION[] = []; + appDisposables: EXPRESSION[] = []; + + rootNodesOrAppElements: EXPRESSION[] = []; + + elementCount: number = 0; + + constructor(public renderer: EXPRESSION, public viewManager: EXPRESSION, + public projectableNodes: EXPRESSION, public rootSelector: EXPRESSION, + public view: EXPRESSION, public protoView: CompileProtoView, + public targetStatements: STATEMENT[], + public factory: ViewFactory) {} + + private _addRenderNode(renderNode: EXPRESSION, appEl: EXPRESSION, ngContentIndex: number, + parent: ParentElement) { + this.renderNodes.push(renderNode); + if (isPresent(parent.component)) { + if (isPresent(ngContentIndex)) { + parent.addContentNode(ngContentIndex, isPresent(appEl) ? appEl : renderNode); + } + } else if (isBlank(parent.renderNode)) { + this.rootNodesOrAppElements.push(isPresent(appEl) ? appEl : renderNode); + } + } + + private _getParentRenderNode(ngContentIndex: number, + parent: ParentElement): EXPRESSION { + return isPresent(parent.component) && + parent.component.template.encapsulation !== ViewEncapsulation.Native ? + null : + parent.renderNode; + } + + visitBoundText(ast: BoundTextAst, parent: ParentElement): any { + return this._visitText('', ast.ngContentIndex, parent); + } + visitText(ast: TextAst, parent: ParentElement): any { + return this._visitText(ast.value, ast.ngContentIndex, parent); + } + private _visitText(value: string, ngContentIndex: number, parent: ParentElement) { + var renderNode = this.factory.createText( + this.renderer, this._getParentRenderNode(ngContentIndex, parent), value, this.renderStmts); + this._addRenderNode(renderNode, null, ngContentIndex, parent); + return null; + } + + visitNgContent(ast: NgContentAst, parent: ParentElement): any { + var nodesExpression = this.factory.getProjectedNodes(this.projectableNodes, ast.index); + if (isPresent(parent.component)) { + if (isPresent(ast.ngContentIndex)) { + parent.addContentNode(ast.ngContentIndex, nodesExpression); + } + } else { + if (isPresent(parent.renderNode)) { + this.factory.appendProjectedNodes(this.renderer, parent.renderNode, nodesExpression, + this.renderStmts); + } else { + this.rootNodesOrAppElements.push(nodesExpression); + } + } + return null; + } + + visitElement(ast: ElementAst, parent: ParentElement): any { + var renderNode = this.factory.createElement( + this.renderer, this._getParentRenderNode(ast.ngContentIndex, parent), ast.name, + this.rootSelector, this.renderStmts); + + var component = ast.getComponent(); + var elementIndex = this.elementCount++; + var protoEl = this.protoView.protoElements[elementIndex]; + + protoEl.renderEvents.forEach((eventAst) => { + if (isPresent(eventAst.target)) { + var disposable = this.factory.createGlobalEventListener( + this.renderer, this.view, protoEl.boundElementIndex, eventAst, this.renderStmts); + this.appDisposables.push(disposable); + } else { + this.factory.createElementEventListener(this.renderer, this.view, protoEl.boundElementIndex, + renderNode, eventAst, this.renderStmts); + } + }); + for (var i = 0; i < protoEl.attrNameAndValues.length; i++) { + var attrName = protoEl.attrNameAndValues[i][0]; + var attrValue = protoEl.attrNameAndValues[i][1]; + this.factory.setElementAttribute(this.renderer, renderNode, attrName, attrValue, + this.renderStmts); + } + var appEl = null; + if (isPresent(protoEl.appProtoEl)) { + appEl = this.factory.createAppElement(protoEl.appProtoEl, this.view, renderNode, parent.appEl, + null, this.appStmts); + this.appElements.push(appEl); + } + this._addRenderNode(renderNode, appEl, ast.ngContentIndex, parent); + + var newParent = new ParentElement( + renderNode, isPresent(appEl) ? appEl : parent.appEl, component); + templateVisitAll(this, ast.children, newParent); + if (isPresent(appEl) && isPresent(component)) { + this.factory.createAndSetComponentView(this.renderer, this.viewManager, this.view, appEl, + component, newParent.contentNodesByNgContentIndex, + this.appStmts); + } + return null; + } + + visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: ParentElement): any { + var renderNode = this.factory.createTemplateAnchor( + this.renderer, this._getParentRenderNode(ast.ngContentIndex, parent), this.renderStmts); + + var elementIndex = this.elementCount++; + var protoEl = this.protoView.protoElements[elementIndex]; + var embeddedViewFactory = this.factory.createViewFactory( + ast.children, protoEl.embeddedTemplateIndex, this.targetStatements); + + var appEl = this.factory.createAppElement(protoEl.appProtoEl, this.view, renderNode, + parent.appEl, embeddedViewFactory, this.appStmts); + this._addRenderNode(renderNode, appEl, ast.ngContentIndex, parent); + this.appElements.push(appEl); + return null; + } + + visitVariable(ast: VariableAst, ctx: any): any { return null; } + visitAttr(ast: AttrAst, ctx: any): any { return null; } + visitDirective(ast: DirectiveAst, ctx: any): any { return null; } + visitEvent(ast: BoundEventAst, ctx: any): any { return null; } + visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; } + visitElementProperty(ast: BoundElementPropertyAst, context: any): any { return null; } +} + + +function codeGenEventHandler(view: Expression, boundElementIndex: number, + eventName: string): string { + return codeGenValueFn( + ['event'], + `${view.expression}.triggerEventHandlers(${escapeValue(eventName)}, event, ${boundElementIndex})`); +} + +function codeGenViewFactoryName(component: CompileDirectiveMetadata, + embeddedTemplateIndex: number): string { + return `viewFactory_${component.type.name}${embeddedTemplateIndex}`; +} + +function codeGenViewEncapsulation(value: ViewEncapsulation): string { + if (IS_DART) { + return `${METADATA_MODULE_REF}${value}`; + } else { + return `${value}`; + } +} diff --git a/modules/angular2/src/core/application_common_providers.ts b/modules/angular2/src/core/application_common_providers.ts index b0aa60bc71..bb75bf517e 100644 --- a/modules/angular2/src/core/application_common_providers.ts +++ b/modules/angular2/src/core/application_common_providers.ts @@ -11,13 +11,11 @@ import { KeyValueDiffers, defaultKeyValueDiffers } from './change_detection/change_detection'; -import {AppViewPool, APP_VIEW_POOL_CAPACITY} from './linker/view_pool'; +import {ResolvedMetadataCache} from 'angular2/src/core/linker/resolved_metadata_cache'; import {AppViewManager} from './linker/view_manager'; import {AppViewManager_} from "./linker/view_manager"; -import {AppViewManagerUtils} from './linker/view_manager_utils'; import {ViewResolver} from './linker/view_resolver'; import {AppViewListener} from './linker/view_listener'; -import {ProtoViewFactory} from './linker/proto_view_factory'; import {DirectiveResolver} from './linker/directive_resolver'; import {PipeResolver} from './linker/pipe_resolver'; import {Compiler} from './linker/compiler'; @@ -32,12 +30,9 @@ import {DynamicComponentLoader_} from "./linker/dynamic_component_loader"; export const APPLICATION_COMMON_PROVIDERS: Array = CONST_EXPR([ new Provider(Compiler, {useClass: Compiler_}), APP_ID_RANDOM_PROVIDER, - AppViewPool, - new Provider(APP_VIEW_POOL_CAPACITY, {useValue: 10000}), + ResolvedMetadataCache, new Provider(AppViewManager, {useClass: AppViewManager_}), - AppViewManagerUtils, AppViewListener, - ProtoViewFactory, ViewResolver, new Provider(IterableDiffers, {useValue: defaultIterableDiffers}), new Provider(KeyValueDiffers, {useValue: defaultKeyValueDiffers}), diff --git a/modules/angular2/src/core/application_ref.ts b/modules/angular2/src/core/application_ref.ts index 426c56853f..d4d799bea0 100644 --- a/modules/angular2/src/core/application_ref.ts +++ b/modules/angular2/src/core/application_ref.ts @@ -33,11 +33,11 @@ import { ExceptionHandler, unimplemented } from 'angular2/src/facade/exceptions'; -import {internalView} from 'angular2/src/core/linker/view_ref'; import {Console} from 'angular2/src/core/console'; import {wtfLeave, wtfCreateScope, WtfScopeFn} from './profile/profile'; import {ChangeDetectorRef} from 'angular2/src/core/change_detection/change_detector_ref'; import {lockMode} from 'angular2/src/facade/lang'; +import {ElementRef_} from 'angular2/src/core/linker/element_ref'; /** * Construct providers specific to an individual root component. @@ -56,10 +56,10 @@ function _componentProviders(appComponentType: Type): Array { appRef._unloadComponent(ref); }) .then((componentRef) => { ref = componentRef; - if (isPresent(componentRef.location.nativeElement)) { + var testability = injector.getOptional(Testability); + if (isPresent(testability)) { injector.get(TestabilityRegistry) - .registerApplication(componentRef.location.nativeElement, - injector.get(Testability)); + .registerApplication(componentRef.location.nativeElement, testability); } return componentRef; }); @@ -439,7 +439,7 @@ export class ApplicationRef_ extends ApplicationRef { /** @internal */ _loadComponent(ref): void { - var appChangeDetector = internalView(ref.hostView).changeDetector; + var appChangeDetector = (ref.location).internalElement.parentView.changeDetector; this._changeDetectorRefs.push(appChangeDetector.ref); this.tick(); this._rootComponents.push(ref); @@ -451,7 +451,8 @@ export class ApplicationRef_ extends ApplicationRef { if (!ListWrapper.contains(this._rootComponents, ref)) { return; } - this.unregisterChangeDetector(internalView(ref.hostView).changeDetector.ref); + this.unregisterChangeDetector( + (ref.location).internalElement.parentView.changeDetector.ref); ListWrapper.remove(this._rootComponents, ref); } diff --git a/modules/angular2/src/core/change_detection/abstract_change_detector.ts b/modules/angular2/src/core/change_detection/abstract_change_detector.ts index 4a1ced947e..e3ec39cb87 100644 --- a/modules/angular2/src/core/change_detection/abstract_change_detector.ts +++ b/modules/angular2/src/core/change_detection/abstract_change_detector.ts @@ -8,7 +8,9 @@ import {Pipes} from './pipes'; import { ChangeDetectionError, ExpressionChangedAfterItHasBeenCheckedException, - DehydratedException + DehydratedException, + EventEvaluationErrorContext, + EventEvaluationError } from './exceptions'; import {BindingTarget} from './binding_record'; import {Locals} from './parser/locals'; @@ -43,9 +45,12 @@ export class AbstractChangeDetector implements ChangeDetector { subscriptions: any[]; streams: any[]; - constructor(public id: string, public dispatcher: ChangeDispatcher, - public numberOfPropertyProtoRecords: number, public bindingTargets: BindingTarget[], - public directiveIndices: DirectiveIndex[], public strategy: ChangeDetectionStrategy) { + dispatcher: ChangeDispatcher; + + + constructor(public id: string, public numberOfPropertyProtoRecords: number, + public bindingTargets: BindingTarget[], public directiveIndices: DirectiveIndex[], + public strategy: ChangeDetectionStrategy) { this.ref = new ChangeDetectorRef_(this); } @@ -65,10 +70,24 @@ export class AbstractChangeDetector implements ChangeDetector { remove(): void { this.parent.removeContentChild(this); } - handleEvent(eventName: string, elIndex: number, locals: Locals): boolean { - var res = this.handleEventInternal(eventName, elIndex, locals); - this.markPathToRootAsCheckOnce(); - return res; + handleEvent(eventName: string, elIndex: number, event: any): boolean { + if (!this.hydrated()) { + return true; + } + try { + var locals = new Map(); + locals.set('$event', event); + var res = !this.handleEventInternal(eventName, elIndex, new Locals(this.locals, locals)); + this.markPathToRootAsCheckOnce(); + return res; + } catch (e) { + var c = this.dispatcher.getDebugContext(null, elIndex, null); + var context = isPresent(c) ? + new EventEvaluationErrorContext(c.element, c.componentElement, c.context, + c.locals, c.injector) : + null; + throw new EventEvaluationError(eventName, e, e.stack, context); + } } handleEventInternal(eventName: string, elIndex: number, locals: Locals): boolean { return false; } @@ -133,7 +152,8 @@ export class AbstractChangeDetector implements ChangeDetector { // This method is not intended to be overridden. Subclasses should instead provide an // implementation of `hydrateDirectives`. - hydrate(context: T, locals: Locals, directives: any, pipes: Pipes): void { + hydrate(context: T, locals: Locals, dispatcher: ChangeDispatcher, pipes: Pipes): void { + this.dispatcher = dispatcher; this.mode = ChangeDetectionUtil.changeDetectionMode(this.strategy); this.context = context; @@ -143,12 +163,12 @@ export class AbstractChangeDetector implements ChangeDetector { this.locals = locals; this.pipes = pipes; - this.hydrateDirectives(directives); + this.hydrateDirectives(dispatcher); this.state = ChangeDetectorState.NeverChecked; } // Subclasses should override this method to hydrate any directives. - hydrateDirectives(directives: any): void {} + hydrateDirectives(dispatcher: ChangeDispatcher): void {} // This method is not intended to be overridden. Subclasses should instead provide an // implementation of `dehydrateDirectives`. @@ -160,6 +180,7 @@ export class AbstractChangeDetector implements ChangeDetector { this._unsubsribeFromObservables(); } + this.dispatcher = null; this.context = null; this.locals = null; this.pipes = null; @@ -171,6 +192,19 @@ export class AbstractChangeDetector implements ChangeDetector { hydrated(): boolean { return isPresent(this.context); } + destroyRecursive(): void { + this.dispatcher.notifyOnDestroy(); + this.dehydrate(); + var children = this.contentChildren; + for (var i = 0; i < children.length; i++) { + children[i].destroyRecursive(); + } + children = this.viewChildren; + for (var i = 0; i < children.length; i++) { + children[i].destroyRecursive(); + } + } + afterContentLifecycleCallbacks(): void { this.dispatcher.notifyAfterContentChecked(); this.afterContentLifecycleCallbacksInternal(); @@ -298,7 +332,7 @@ export class AbstractChangeDetector implements ChangeDetector { private _throwError(exception: any, stack: any): void { var error; try { - var c = this.dispatcher.getDebugContext(this._currentBinding().elementIndex, null); + var c = this.dispatcher.getDebugContext(null, this._currentBinding().elementIndex, null); var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals, c.injector, this._currentBinding().debug) : null; diff --git a/modules/angular2/src/core/change_detection/change_detection_jit_generator.ts b/modules/angular2/src/core/change_detection/change_detection_jit_generator.ts index 14c9cafc7e..98615d94ae 100644 --- a/modules/angular2/src/core/change_detection/change_detection_jit_generator.ts +++ b/modules/angular2/src/core/change_detection/change_detection_jit_generator.ts @@ -66,8 +66,8 @@ export class ChangeDetectorJITGenerator { generate(): Function { var factorySource = ` ${this.generateSource()} - return function(dispatcher) { - return new ${this.typeName}(dispatcher); + return function() { + return new ${this.typeName}(); } `; return new Function(this.abstractChangeDetectorVarName, this.changeDetectionUtilVarName, @@ -77,9 +77,9 @@ export class ChangeDetectorJITGenerator { generateSource(): string { return ` - var ${this.typeName} = function ${this.typeName}(dispatcher) { + var ${this.typeName} = function ${this.typeName}() { ${this.abstractChangeDetectorVarName}.call( - this, ${JSON.stringify(this.id)}, dispatcher, ${this.records.length}, + this, ${JSON.stringify(this.id)}, ${this.records.length}, ${this.typeName}.gen_propertyBindingTargets, ${this.typeName}.gen_directiveIndices, ${codify(this.changeDetectionStrategy)}); this.dehydrateDirectives(false); @@ -199,13 +199,14 @@ export class ChangeDetectorJITGenerator { /** @internal */ _maybeGenDehydrateDirectives(): string { var destroyPipesCode = this._names.genPipeOnDestroy(); - if (destroyPipesCode) { - destroyPipesCode = `if (destroyPipes) { ${destroyPipesCode} }`; - } + var destroyDirectivesCode = this._logic.genDirectivesOnDestroy(this.directiveRecords); var dehydrateFieldsCode = this._names.genDehydrateFields(); - if (!destroyPipesCode && !dehydrateFieldsCode) return ''; + if (!destroyPipesCode && !destroyDirectivesCode && !dehydrateFieldsCode) return ''; return `${this.typeName}.prototype.dehydrateDirectives = function(destroyPipes) { - ${destroyPipesCode} + if (destroyPipes) { + ${destroyPipesCode} + ${destroyDirectivesCode} + } ${dehydrateFieldsCode} }`; } diff --git a/modules/angular2/src/core/change_detection/change_detector_ref.ts b/modules/angular2/src/core/change_detection/change_detector_ref.ts index e128dcaac2..e34fe44857 100644 --- a/modules/angular2/src/core/change_detection/change_detector_ref.ts +++ b/modules/angular2/src/core/change_detection/change_detector_ref.ts @@ -205,4 +205,4 @@ export class ChangeDetectorRef_ extends ChangeDetectorRef { this._cd.mode = ChangeDetectionStrategy.CheckAlways; this.markForCheck(); } -} +} \ No newline at end of file diff --git a/modules/angular2/src/core/change_detection/codegen_logic_util.ts b/modules/angular2/src/core/change_detection/codegen_logic_util.ts index 2c2e2ab741..17cf95cfc2 100644 --- a/modules/angular2/src/core/change_detection/codegen_logic_util.ts +++ b/modules/angular2/src/core/change_detection/codegen_logic_util.ts @@ -155,17 +155,49 @@ export class CodegenLogicUtil { var res = []; for (var i = 0; i < directiveRecords.length; ++i) { var r = directiveRecords[i]; - res.push(`${this._names.getDirectiveName(r.directiveIndex)} = ${this._genReadDirective(i)};`); + var dirVarName = this._names.getDirectiveName(r.directiveIndex); + res.push(`${dirVarName} = ${this._genReadDirective(i)};`); + if (isPresent(r.outputs)) { + r.outputs.forEach(output => { + var eventHandlerExpr = this._genEventHandler(r.directiveIndex.elementIndex, output[1]); + if (IS_DART) { + res.push(`${dirVarName}.${output[0]}.listen(${eventHandlerExpr});`); + } else { + res.push(`${dirVarName}.${output[0]}.subscribe({next: ${eventHandlerExpr}});`); + } + }); + } } return res.join("\n"); } + genDirectivesOnDestroy(directiveRecords: DirectiveRecord[]): string { + var res = []; + for (var i = 0; i < directiveRecords.length; ++i) { + var r = directiveRecords[i]; + if (r.callOnDestroy) { + var dirVarName = this._names.getDirectiveName(r.directiveIndex); + res.push(`${dirVarName}.ngOnDestroy();`); + } + } + return res.join("\n"); + } + + private _genEventHandler(boundElementIndex: number, eventName: string): string { + if (IS_DART) { + return `(event) => this.handleEvent('${eventName}', ${boundElementIndex}, event)`; + } else { + return `(function(event) { return this.handleEvent('${eventName}', ${boundElementIndex}, event); }).bind(this)`; + } + } + private _genReadDirective(index: number) { + var directiveExpr = `this.getDirectiveFor(directives, ${index})`; // This is an experimental feature. Works only in Dart. if (this._changeDetection === ChangeDetectionStrategy.OnPushObserve) { - return `this.observeDirective(this.getDirectiveFor(directives, ${index}), ${index})`; + return `this.observeDirective(${directiveExpr}, ${index})`; } else { - return `this.getDirectiveFor(directives, ${index})`; + return directiveExpr; } } diff --git a/modules/angular2/src/core/change_detection/directive_record.ts b/modules/angular2/src/core/change_detection/directive_record.ts index 967034f07e..53c6f2e6cd 100644 --- a/modules/angular2/src/core/change_detection/directive_record.ts +++ b/modules/angular2/src/core/change_detection/directive_record.ts @@ -16,10 +16,14 @@ export class DirectiveRecord { callOnChanges: boolean; callDoCheck: boolean; callOnInit: boolean; + callOnDestroy: boolean; changeDetection: ChangeDetectionStrategy; + // array of [emitter property name, eventName] + outputs: string[][]; constructor({directiveIndex, callAfterContentInit, callAfterContentChecked, callAfterViewInit, - callAfterViewChecked, callOnChanges, callDoCheck, callOnInit, changeDetection}: { + callAfterViewChecked, callOnChanges, callDoCheck, callOnInit, callOnDestroy, + changeDetection, outputs}: { directiveIndex?: DirectiveIndex, callAfterContentInit?: boolean, callAfterContentChecked?: boolean, @@ -28,7 +32,9 @@ export class DirectiveRecord { callOnChanges?: boolean, callDoCheck?: boolean, callOnInit?: boolean, - changeDetection?: ChangeDetectionStrategy + callOnDestroy?: boolean, + changeDetection?: ChangeDetectionStrategy, + outputs?: string[][] } = {}) { this.directiveIndex = directiveIndex; this.callAfterContentInit = normalizeBool(callAfterContentInit); @@ -38,7 +44,9 @@ export class DirectiveRecord { this.callAfterViewChecked = normalizeBool(callAfterViewChecked); this.callDoCheck = normalizeBool(callDoCheck); this.callOnInit = normalizeBool(callOnInit); + this.callOnDestroy = normalizeBool(callOnDestroy); this.changeDetection = changeDetection; + this.outputs = outputs; } isDefaultChangeDetection(): boolean { diff --git a/modules/angular2/src/core/change_detection/dynamic_change_detector.ts b/modules/angular2/src/core/change_detection/dynamic_change_detector.ts index 344b71ff5a..0b07abe62f 100644 --- a/modules/angular2/src/core/change_detection/dynamic_change_detector.ts +++ b/modules/angular2/src/core/change_detection/dynamic_change_detector.ts @@ -11,21 +11,21 @@ import {ChangeDispatcher, ChangeDetectorGenConfig} from './interfaces'; import {ChangeDetectionUtil, SimpleChange} from './change_detection_util'; import {ChangeDetectionStrategy, ChangeDetectorState} from './constants'; import {ProtoRecord, RecordType} from './proto_record'; +import {reflector} from 'angular2/src/core/reflection/reflection'; +import {ObservableWrapper} from 'angular2/src/facade/async'; export class DynamicChangeDetector extends AbstractChangeDetector { values: any[]; changes: any[]; localPipes: any[]; prevContexts: any[]; - directives: any = null; - constructor(id: string, dispatcher: ChangeDispatcher, numberOfPropertyProtoRecords: number, + constructor(id: string, numberOfPropertyProtoRecords: number, propertyBindingTargets: BindingTarget[], directiveIndices: DirectiveIndex[], strategy: ChangeDetectionStrategy, private _records: ProtoRecord[], private _eventBindings: EventBinding[], private _directiveRecords: DirectiveRecord[], private _genConfig: ChangeDetectorGenConfig) { - super(id, dispatcher, numberOfPropertyProtoRecords, propertyBindingTargets, directiveIndices, - strategy); + super(id, numberOfPropertyProtoRecords, propertyBindingTargets, directiveIndices, strategy); var len = _records.length + 1; this.values = ListWrapper.createFixedSize(len); this.localPipes = ListWrapper.createFixedSize(len); @@ -104,24 +104,41 @@ export class DynamicChangeDetector extends AbstractChangeDetector { return this._eventBindings.filter(eb => eb.eventName == eventName && eb.elIndex === elIndex); } - hydrateDirectives(directives: any): void { + hydrateDirectives(dispatcher: ChangeDispatcher): void { this.values[0] = this.context; - this.directives = directives; + this.dispatcher = dispatcher; if (this.strategy === ChangeDetectionStrategy.OnPushObserve) { for (var i = 0; i < this.directiveIndices.length; ++i) { var index = this.directiveIndices[i]; - super.observeDirective(directives.getDirectiveFor(index), i); + super.observeDirective(this._getDirectiveFor(index), i); + } + } + for (var i = 0; i < this._directiveRecords.length; ++i) { + var r = this._directiveRecords[i]; + if (isPresent(r.outputs)) { + r.outputs.forEach(output => { + var eventHandler = + this._createEventHandler(r.directiveIndex.elementIndex, output[1]); + var directive = this._getDirectiveFor(r.directiveIndex); + var getter = reflector.getter(output[0]); + ObservableWrapper.subscribe(getter(directive), eventHandler); + }); } } } + private _createEventHandler(boundElementIndex: number, eventName: string): Function { + return (event) => this.handleEvent(eventName, boundElementIndex, event); + } + + dehydrateDirectives(destroyPipes: boolean) { if (destroyPipes) { this._destroyPipes(); + this._destroyDirectives(); } this.values[0] = null; - this.directives = null; ListWrapper.fill(this.values, ChangeDetectionUtil.uninitialized, 1); ListWrapper.fill(this.changes, false); ListWrapper.fill(this.localPipes, null); @@ -137,6 +154,16 @@ export class DynamicChangeDetector extends AbstractChangeDetector { } } + /** @internal */ + _destroyDirectives() { + for (var i = 0; i < this._directiveRecords.length; ++i) { + var record = this._directiveRecords[i]; + if (record.callOnDestroy) { + this._getDirectiveFor(record.directiveIndex).ngOnDestroy(); + } + } + } + checkNoChanges(): void { this.runDetectChanges(true); } detectChangesInRecordsInternal(throwOnChange: boolean) { @@ -241,12 +268,14 @@ export class DynamicChangeDetector extends AbstractChangeDetector { } /** @internal */ - private _getDirectiveFor(directiveIndex) { - return this.directives.getDirectiveFor(directiveIndex); + private _getDirectiveFor(directiveIndex: DirectiveIndex) { + return this.dispatcher.getDirectiveFor(directiveIndex); } /** @internal */ - private _getDetectorFor(directiveIndex) { return this.directives.getDetectorFor(directiveIndex); } + private _getDetectorFor(directiveIndex: DirectiveIndex) { + return this.dispatcher.getDetectorFor(directiveIndex); + } /** @internal */ private _check(proto: ProtoRecord, throwOnChange: boolean, values: any[], diff --git a/modules/angular2/src/core/change_detection/exceptions.ts b/modules/angular2/src/core/change_detection/exceptions.ts index b8c7454447..11d04ee273 100644 --- a/modules/angular2/src/core/change_detection/exceptions.ts +++ b/modules/angular2/src/core/change_detection/exceptions.ts @@ -93,3 +93,20 @@ export class ChangeDetectionError extends WrappedException { export class DehydratedException extends BaseException { constructor() { super('Attempt to detect changes on a dehydrated detector.'); } } + +/** + * Wraps an exception thrown by an event handler. + */ +export class EventEvaluationError extends WrappedException { + constructor(eventName: string, originalException: any, originalStack: any, context: any) { + super(`Error during evaluation of "${eventName}"`, originalException, originalStack, context); + } +} + +/** + * Error context included when an event handler throws an exception. + */ +export class EventEvaluationErrorContext { + constructor(public element: any, public componentElement: any, public context: any, + public locals: any, public injector: any) {} +} diff --git a/modules/angular2/src/core/change_detection/interfaces.ts b/modules/angular2/src/core/change_detection/interfaces.ts index 803d962c56..0f3c165f56 100644 --- a/modules/angular2/src/core/change_detection/interfaces.ts +++ b/modules/angular2/src/core/change_detection/interfaces.ts @@ -1,6 +1,6 @@ import {Locals} from './parser/locals'; import {BindingTarget, BindingRecord} from './binding_record'; -import {DirectiveIndex, DirectiveRecord} from './directive_record'; +import {DirectiveRecord, DirectiveIndex} from './directive_record'; import {ChangeDetectionStrategy} from './constants'; import {ChangeDetectorRef} from './change_detector_ref'; @@ -10,11 +10,14 @@ export class DebugContext { } export interface ChangeDispatcher { - getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext; + getDebugContext(appElement: any, elementIndex: number, directiveIndex: number): DebugContext; notifyOnBinding(bindingTarget: BindingTarget, value: any): void; logBindingUpdate(bindingTarget: BindingTarget, value: any): void; notifyAfterContentChecked(): void; notifyAfterViewChecked(): void; + notifyOnDestroy(): void; + getDetectorFor(directiveIndex: DirectiveIndex): ChangeDetector; + getDirectiveFor(directiveIndex: DirectiveIndex): any; } export interface ChangeDetector { @@ -27,16 +30,18 @@ export interface ChangeDetector { removeContentChild(cd: ChangeDetector): void; removeViewChild(cd: ChangeDetector): void; remove(): void; - hydrate(context: any, locals: Locals, directives: any, pipes: any): void; + hydrate(context: any, locals: Locals, dispatcher: ChangeDispatcher, pipes: any): void; dehydrate(): void; markPathToRootAsCheckOnce(): void; - handleEvent(eventName: string, elIndex: number, locals: Locals); + handleEvent(eventName: string, elIndex: number, event: any); detectChanges(): void; checkNoChanges(): void; + destroyRecursive(): void; + markAsCheckOnce(): void; } -export interface ProtoChangeDetector { instantiate(dispatcher: ChangeDispatcher): ChangeDetector; } +export interface ProtoChangeDetector { instantiate(): ChangeDetector; } export class ChangeDetectorGenConfig { constructor(public genDebugInfo: boolean, public logBindingUpdate: boolean, diff --git a/modules/angular2/src/core/change_detection/jit_proto_change_detector.dart b/modules/angular2/src/core/change_detection/jit_proto_change_detector.dart index 46a3251a5b..5fb02a8e4a 100644 --- a/modules/angular2/src/core/change_detection/jit_proto_change_detector.dart +++ b/modules/angular2/src/core/change_detection/jit_proto_change_detector.dart @@ -3,11 +3,11 @@ library change_detection.jit_proto_change_detector; import 'interfaces.dart' show ChangeDetector, ProtoChangeDetector; class JitProtoChangeDetector implements ProtoChangeDetector { - JitProtoChangeDetector(definition) : super(); + JitProtoChangeDetector(definition); static bool isSupported() => false; - ChangeDetector instantiate(dispatcher) { + ChangeDetector instantiate() { throw "Jit Change Detection not supported in Dart"; } } diff --git a/modules/angular2/src/core/change_detection/jit_proto_change_detector.ts b/modules/angular2/src/core/change_detection/jit_proto_change_detector.ts index 3d4636e7e3..bd747c4931 100644 --- a/modules/angular2/src/core/change_detection/jit_proto_change_detector.ts +++ b/modules/angular2/src/core/change_detection/jit_proto_change_detector.ts @@ -14,7 +14,7 @@ export class JitProtoChangeDetector implements ProtoChangeDetector { static isSupported(): boolean { return true; } - instantiate(dispatcher: any): ChangeDetector { return this._factory(dispatcher); } + instantiate(): ChangeDetector { return this._factory(); } /** @internal */ _createFactory(definition: ChangeDetectorDefinition) { diff --git a/modules/angular2/src/core/change_detection/parser/locals.ts b/modules/angular2/src/core/change_detection/parser/locals.ts index 92b1aaa862..fb7fbd2fbd 100644 --- a/modules/angular2/src/core/change_detection/parser/locals.ts +++ b/modules/angular2/src/core/change_detection/parser/locals.ts @@ -41,5 +41,5 @@ export class Locals { } } - clearValues(): void { MapWrapper.clearValues(this.current); } + clearLocalValues(): void { MapWrapper.clearValues(this.current); } } diff --git a/modules/angular2/src/core/change_detection/pregen_proto_change_detector.dart b/modules/angular2/src/core/change_detection/pregen_proto_change_detector.dart index bf95c75e1d..301801ddba 100644 --- a/modules/angular2/src/core/change_detection/pregen_proto_change_detector.dart +++ b/modules/angular2/src/core/change_detection/pregen_proto_change_detector.dart @@ -1,8 +1,5 @@ library angular2.src.change_detection.pregen_proto_change_detector; -import 'package:angular2/src/core/change_detection/interfaces.dart'; -import 'package:angular2/src/facade/lang.dart' show looseIdentical; - export 'dart:core' show List; export 'package:angular2/src/core/change_detection/abstract_change_detector.dart' show AbstractChangeDetector; @@ -20,34 +17,3 @@ export 'package:angular2/src/core/change_detection/proto_record.dart' export 'package:angular2/src/core/change_detection/change_detection_util.dart' show ChangeDetectionUtil; export 'package:angular2/src/facade/lang.dart' show assertionsEnabled, looseIdentical; - -typedef ProtoChangeDetector PregenProtoChangeDetectorFactory( - ChangeDetectorDefinition definition); - -typedef ChangeDetector InstantiateMethod(dynamic dispatcher); - -/// Implementation of [ProtoChangeDetector] for use by pre-generated change -/// detectors in Angular 2 Dart. -/// Classes generated by the `TemplateCompiler` use this. The `export`s above -/// allow the generated code to `import` a single library and get all -/// dependencies. -class PregenProtoChangeDetector extends ProtoChangeDetector { - /// The [ChangeDetectorDefinition#id]. Strictly informational. - final String id; - - /// Closure used to generate an actual [ChangeDetector]. - final InstantiateMethod _instantiateMethod; - - /// Internal ctor. - PregenProtoChangeDetector._(this.id, this._instantiateMethod); - - static bool isSupported() => true; - - factory PregenProtoChangeDetector( - InstantiateMethod instantiateMethod, ChangeDetectorDefinition def) { - return new PregenProtoChangeDetector._(def.id, instantiateMethod); - } - - @override - instantiate(dynamic dispatcher) => _instantiateMethod(dispatcher); -} diff --git a/modules/angular2/src/core/change_detection/pregen_proto_change_detector.ts b/modules/angular2/src/core/change_detection/pregen_proto_change_detector.ts index 5ef1eadb5b..da60b225c9 100644 --- a/modules/angular2/src/core/change_detection/pregen_proto_change_detector.ts +++ b/modules/angular2/src/core/change_detection/pregen_proto_change_detector.ts @@ -1,14 +1 @@ -import {BaseException} from 'angular2/src/facade/exceptions'; - -import {ProtoChangeDetector, ChangeDetector} from './interfaces'; -import {coalesce} from './coalesce'; - -export {Function as PregenProtoChangeDetectorFactory}; - -export class PregenProtoChangeDetector implements ProtoChangeDetector { - static isSupported(): boolean { return false; } - - instantiate(dispatcher: any): ChangeDetector { - throw new BaseException('Pregen change detection not supported in Js'); - } -} +// empty file as we only need the dart version \ No newline at end of file diff --git a/modules/angular2/src/core/change_detection/proto_change_detector.ts b/modules/angular2/src/core/change_detection/proto_change_detector.ts index c25cc54867..ee0016a9df 100644 --- a/modules/angular2/src/core/change_detection/proto_change_detector.ts +++ b/modules/angular2/src/core/change_detection/proto_change_detector.ts @@ -54,12 +54,11 @@ export class DynamicProtoChangeDetector implements ProtoChangeDetector { this._directiveIndices = this._definition.directiveRecords.map(d => d.directiveIndex); } - instantiate(dispatcher: any): ChangeDetector { + instantiate(): ChangeDetector { return new DynamicChangeDetector( - this._definition.id, dispatcher, this._propertyBindingRecords.length, - this._propertyBindingTargets, this._directiveIndices, this._definition.strategy, - this._propertyBindingRecords, this._eventBindingRecords, this._definition.directiveRecords, - this._definition.genConfig); + this._definition.id, this._propertyBindingRecords.length, this._propertyBindingTargets, + this._directiveIndices, this._definition.strategy, this._propertyBindingRecords, + this._eventBindingRecords, this._definition.directiveRecords, this._definition.genConfig); } } diff --git a/modules/angular2/src/core/debug/debug_element.ts b/modules/angular2/src/core/debug/debug_element.ts index c26acb8188..88b196258e 100644 --- a/modules/angular2/src/core/debug/debug_element.ts +++ b/modules/angular2/src/core/debug/debug_element.ts @@ -1,9 +1,9 @@ import {Type, isPresent, isBlank} from 'angular2/src/facade/lang'; import {ListWrapper, MapWrapper, Predicate} from 'angular2/src/facade/collection'; import {unimplemented} from 'angular2/src/facade/exceptions'; -import {ElementInjector} from 'angular2/src/core/linker/element_injector'; -import {AppView, ViewType} from 'angular2/src/core/linker/view'; -import {internalView} from 'angular2/src/core/linker/view_ref'; + +import {AppElement} from 'angular2/src/core/linker/element'; +import {AppView} from 'angular2/src/core/linker/view'; import {ElementRef, ElementRef_} from 'angular2/src/core/linker/element_ref'; /** @@ -103,79 +103,68 @@ export abstract class DebugElement { } export class DebugElement_ extends DebugElement { - /** @internal */ - _elementInjector: ElementInjector; - - constructor(private _parentView: AppView, private _boundElementIndex: number) { - super(); - this._elementInjector = this._parentView.elementInjectors[this._boundElementIndex]; - } + constructor(private _appElement: AppElement) { super(); } get componentInstance(): any { - if (!isPresent(this._elementInjector)) { + if (!isPresent(this._appElement)) { return null; } - return this._elementInjector.getComponent(); + return this._appElement.getComponent(); } get nativeElement(): any { return this.elementRef.nativeElement; } - get elementRef(): ElementRef { return this._parentView.elementRefs[this._boundElementIndex]; } + get elementRef(): ElementRef { return this._appElement.ref; } getDirectiveInstance(directiveIndex: number): any { - return this._elementInjector.getDirectiveAtIndex(directiveIndex); + return this._appElement.getDirectiveAtIndex(directiveIndex); } get children(): DebugElement[] { - return this._getChildElements(this._parentView, this._boundElementIndex); + return this._getChildElements(this._appElement.parentView, this._appElement); } get componentViewChildren(): DebugElement[] { - var shadowView = this._parentView.getNestedView(this._boundElementIndex); - - if (!isPresent(shadowView) || shadowView.proto.type !== ViewType.COMPONENT) { + if (!isPresent(this._appElement.componentView)) { // The current element is not a component. return []; } - return this._getChildElements(shadowView, null); + return this._getChildElements(this._appElement.componentView, null); } triggerEventHandler(eventName: string, eventObj: Event): void { - this._parentView.triggerEventHandlers(eventName, eventObj, this._boundElementIndex); + this._appElement.parentView.triggerEventHandlers(eventName, eventObj, + this._appElement.proto.index); } hasDirective(type: Type): boolean { - if (!isPresent(this._elementInjector)) { + if (!isPresent(this._appElement)) { return false; } - return this._elementInjector.hasDirective(type); + return this._appElement.hasDirective(type); } inject(type: Type): any { - if (!isPresent(this._elementInjector)) { + if (!isPresent(this._appElement)) { return null; } - return this._elementInjector.get(type); + return this._appElement.get(type); } - getLocal(name: string): any { return this._parentView.locals.get(name); } + getLocal(name: string): any { return this._appElement.parentView.locals.get(name); } /** @internal */ - _getChildElements(view: AppView, parentBoundElementIndex: number): DebugElement[] { + _getChildElements(view: AppView, parentAppElement: AppElement): DebugElement[] { var els = []; - var parentElementBinder = null; - if (isPresent(parentBoundElementIndex)) { - parentElementBinder = view.proto.elementBinders[parentBoundElementIndex - view.elementOffset]; - } - for (var i = 0; i < view.proto.elementBinders.length; ++i) { - var binder = view.proto.elementBinders[i]; - if (binder.parent == parentElementBinder) { - els.push(new DebugElement_(view, view.elementOffset + i)); + for (var i = 0; i < view.appElements.length; ++i) { + var appEl = view.appElements[i]; + if (appEl.parent == parentAppElement) { + els.push(new DebugElement_(appEl)); - var views = view.viewContainers[view.elementOffset + i]; + var views = appEl.nestedViews; if (isPresent(views)) { - views.views.forEach( + views.forEach( (nextView) => { els = els.concat(this._getChildElements(nextView, null)); }); } } @@ -191,8 +180,7 @@ export class DebugElement_ extends DebugElement { * @return {DebugElement} */ export function inspectElement(elementRef: ElementRef): DebugElement { - return new DebugElement_(internalView((elementRef).parentView), - (elementRef).boundElementIndex); + return new DebugElement_((elementRef).internalElement); } /** diff --git a/modules/angular2/src/core/di/injector.ts b/modules/angular2/src/core/di/injector.ts index 40c7e20493..4d499cc8a6 100644 --- a/modules/angular2/src/core/di/injector.ts +++ b/modules/angular2/src/core/di/injector.ts @@ -194,6 +194,11 @@ export class ProtoInjectorDynamicStrategy implements ProtoInjectorStrategy { } export class ProtoInjector { + static fromResolvedProviders(providers: ResolvedProvider[]): ProtoInjector { + var bd = providers.map(b => new ProviderWithVisibility(b, Visibility.Public)); + return new ProtoInjector(bd); + } + /** @internal */ _strategy: ProtoInjectorStrategy; numberOfProviders: number; @@ -215,7 +220,6 @@ export interface InjectorStrategy { getObjAtIndex(index: number): any; getMaxNumberOfObjects(): number; - attach(parent: Injector, isHost: boolean): void; resetConstructionCounter(): void; instantiateProvider(provider: ResolvedProvider, visibility: Visibility): any; } @@ -240,12 +244,6 @@ export class InjectorInlineStrategy implements InjectorStrategy { return this.injector._new(provider, visibility); } - attach(parent: Injector, isHost: boolean): void { - var inj = this.injector; - inj._parent = parent; - inj._isHost = isHost; - } - getObjByKeyId(keyId: number, visibility: Visibility): any { var p = this.protoStrategy; var inj = this.injector; @@ -346,12 +344,6 @@ export class InjectorDynamicStrategy implements InjectorStrategy { return this.injector._new(provider, visibility); } - attach(parent: Injector, isHost: boolean): void { - var inj = this.injector; - inj._parent = parent; - inj._isHost = isHost; - } - getObjByKeyId(keyId: number, visibility: Visibility): any { var p = this.protoStrategy; @@ -516,9 +508,7 @@ export class Injector { * ``` */ static fromResolvedProviders(providers: ResolvedProvider[]): Injector { - var bd = providers.map(b => new ProviderWithVisibility(b, Visibility.Public)); - var proto = new ProtoInjector(bd); - return new Injector(proto, null, null); + return new Injector(ProtoInjector.fromResolvedProviders(providers)); } /** @@ -531,8 +521,6 @@ export class Injector { /** @internal */ _strategy: InjectorStrategy; /** @internal */ - _isHost: boolean = false; - /** @internal */ _constructionCounter: number = 0; /** @internal */ public _proto: any /* ProtoInjector */; @@ -542,6 +530,7 @@ export class Injector { * Private */ constructor(_proto: any /* ProtoInjector */, _parent: Injector = null, + private _isHostBoundary: boolean = false, private _depProvider: any /* DependencyProvider */ = null, private _debugContext: Function = null) { this._proto = _proto; @@ -549,6 +538,12 @@ export class Injector { this._strategy = _proto._strategy.createInjectorStrategy(this); } + /** + * Whether this injector is a boundary to a host. + * @internal + */ + get hostBoundary() { return this._isHostBoundary; } + /** * @internal */ @@ -692,7 +687,7 @@ export class Injector { createChildFromResolved(providers: ResolvedProvider[]): Injector { var bd = providers.map(b => new ProviderWithVisibility(b, Visibility.Public)); var proto = new ProtoInjector(bd); - var inj = new Injector(proto, null, null); + var inj = new Injector(proto); inj._parent = this; return inj; } @@ -935,7 +930,7 @@ export class Injector { var inj: Injector = this; if (lowerBoundVisibility instanceof SkipSelfMetadata) { - if (inj._isHost) { + if (inj._isHostBoundary) { return this._getPrivateDependency(key, optional, inj); } else { inj = inj._parent; @@ -946,7 +941,7 @@ export class Injector { var obj = inj._strategy.getObjByKeyId(key.id, providerVisibility); if (obj !== UNDEFINED) return obj; - if (isPresent(inj._parent) && inj._isHost) { + if (isPresent(inj._parent) && inj._isHostBoundary) { return this._getPrivateDependency(key, optional, inj); } else { inj = inj._parent; @@ -968,7 +963,7 @@ export class Injector { var inj: Injector = this; if (lowerBoundVisibility instanceof SkipSelfMetadata) { - providerVisibility = inj._isHost ? Visibility.PublicAndPrivate : Visibility.Public; + providerVisibility = inj._isHostBoundary ? Visibility.PublicAndPrivate : Visibility.Public; inj = inj._parent; } @@ -976,7 +971,7 @@ export class Injector { var obj = inj._strategy.getObjByKeyId(key.id, providerVisibility); if (obj !== UNDEFINED) return obj; - providerVisibility = inj._isHost ? Visibility.PublicAndPrivate : Visibility.Public; + providerVisibility = inj._isHostBoundary ? Visibility.PublicAndPrivate : Visibility.Public; inj = inj._parent; } diff --git a/modules/angular2/src/core/di/provider.ts b/modules/angular2/src/core/di/provider.ts index 267381244d..753f0fd4d8 100644 --- a/modules/angular2/src/core/di/provider.ts +++ b/modules/angular2/src/core/di/provider.ts @@ -538,50 +538,62 @@ export function resolveFactory(provider: Provider): ResolvedFactory { * convenience provider syntax. */ export function resolveProvider(provider: Provider): ResolvedProvider { - return new ResolvedProvider_(Key.get(provider.token), [resolveFactory(provider)], false); + return new ResolvedProvider_(Key.get(provider.token), [resolveFactory(provider)], provider.multi); } /** * Resolve a list of Providers. */ export function resolveProviders(providers: Array): ResolvedProvider[] { - var normalized = _createListOfProviders(_normalizeProviders( - providers, new Map())); - return normalized.map(b => { - if (b instanceof _NormalizedProvider) { - return new ResolvedProvider_(b.key, [b.resolvedFactory], false); - - } else { - var arr = <_NormalizedProvider[]>b; - return new ResolvedProvider_(arr[0].key, arr.map(_ => _.resolvedFactory), true); - } - }); + var normalized = _normalizeProviders(providers, []); + var resolved = normalized.map(resolveProvider); + return MapWrapper.values(mergeResolvedProviders(resolved, new Map())); } /** - * The algorithm works as follows: - * - * [Provider] -> [_NormalizedProvider|[_NormalizedProvider]] -> [ResolvedProvider] - * - * _NormalizedProvider is essentially a resolved provider before it was grouped by key. + * Merges a list of ResolvedProviders into a list where + * each key is contained exactly once and multi providers + * have been merged. */ -class _NormalizedProvider { - constructor(public key: Key, public resolvedFactory: ResolvedFactory) {} -} - -function _createListOfProviders(flattenedProviders: Map): any[] { - return MapWrapper.values(flattenedProviders); +export function mergeResolvedProviders( + providers: ResolvedProvider[], + normalizedProvidersMap: Map): Map { + for (var i = 0; i < providers.length; i++) { + var provider = providers[i]; + var existing = normalizedProvidersMap.get(provider.key.id); + if (isPresent(existing)) { + if (provider.multiProvider !== existing.multiProvider) { + throw new MixingMultiProvidersWithRegularProvidersError(existing, provider); + } + if (provider.multiProvider) { + for (var j = 0; j < provider.resolvedFactories.length; j++) { + existing.resolvedFactories.push(provider.resolvedFactories[j]); + } + } else { + normalizedProvidersMap.set(provider.key.id, provider); + } + } else { + var resolvedProvider; + if (provider.multiProvider) { + resolvedProvider = new ResolvedProvider_( + provider.key, ListWrapper.clone(provider.resolvedFactories), provider.multiProvider); + } else { + resolvedProvider = provider; + } + normalizedProvidersMap.set(provider.key.id, resolvedProvider); + } + } + return normalizedProvidersMap; } function _normalizeProviders(providers: Array, - res: Map): - Map { + res: Provider[]): Provider[] { providers.forEach(b => { if (b instanceof Type) { - _normalizeProvider(provide(b, {useClass: b}), res); + res.push(provide(b, {useClass: b})); } else if (b instanceof Provider) { - _normalizeProvider(b, res); + res.push(b); } else if (b instanceof Array) { _normalizeProviders(b, res); @@ -597,36 +609,6 @@ function _normalizeProviders(providers: Array): void { - var key = Key.get(b.token); - var factory = resolveFactory(b); - var normalized = new _NormalizedProvider(key, factory); - - if (b.multi) { - var existingProvider = res.get(key.id); - - if (existingProvider instanceof Array) { - existingProvider.push(normalized); - - } else if (isBlank(existingProvider)) { - res.set(key.id, [normalized]); - - } else { - throw new MixingMultiProvidersWithRegularProvidersError(existingProvider, b); - } - - } else { - var existingProvider = res.get(key.id); - - if (existingProvider instanceof Array) { - throw new MixingMultiProvidersWithRegularProvidersError(existingProvider, b); - } - - res.set(key.id, normalized); - } -} - function _constructDependencies(factoryFunction: Function, dependencies: any[]): Dependency[] { if (isBlank(dependencies)) { return _dependenciesFor(factoryFunction); diff --git a/modules/angular2/src/core/linker.ts b/modules/angular2/src/core/linker.ts index b43b69776f..b04229c4a4 100644 --- a/modules/angular2/src/core/linker.ts +++ b/modules/angular2/src/core/linker.ts @@ -17,6 +17,6 @@ export {QueryList} from './linker/query_list'; export {DynamicComponentLoader} from './linker/dynamic_component_loader'; export {ElementRef} from './linker/element_ref'; export {TemplateRef} from './linker/template_ref'; -export {ViewRef, HostViewRef, ProtoViewRef} from './linker/view_ref'; +export {EmbeddedViewRef, HostViewRef, ViewRef, HostViewFactoryRef} from './linker/view_ref'; export {ViewContainerRef} from './linker/view_container_ref'; export {ComponentRef} from './linker/dynamic_component_loader'; \ No newline at end of file diff --git a/modules/angular2/src/core/linker/compiler.ts b/modules/angular2/src/core/linker/compiler.ts index c4fdaea971..9447952ffd 100644 --- a/modules/angular2/src/core/linker/compiler.ts +++ b/modules/angular2/src/core/linker/compiler.ts @@ -1,12 +1,12 @@ -import {ProtoViewRef} from 'angular2/src/core/linker/view_ref'; -import {ProtoViewFactory} from 'angular2/src/core/linker/proto_view_factory'; +import {HostViewFactoryRef} from 'angular2/src/core/linker/view_ref'; import {Injectable} from 'angular2/src/core/di'; import {Type, isBlank, stringify} from 'angular2/src/facade/lang'; import {BaseException} from 'angular2/src/facade/exceptions'; import {Promise, PromiseWrapper} from 'angular2/src/facade/async'; import {reflector} from 'angular2/src/core/reflection/reflection'; -import {CompiledHostTemplate} from 'angular2/src/core/linker/template_commands'; +import {HostViewFactory} from 'angular2/src/core/linker/view'; +import {HostViewFactoryRef_} from 'angular2/src/core/linker/view_ref'; /** * Low-level service for compiling {@link Component}s into {@link ProtoViewRef ProtoViews}s, which @@ -16,37 +16,25 @@ import {CompiledHostTemplate} from 'angular2/src/core/linker/template_commands'; * both compiles and instantiates a Component. */ export abstract class Compiler { - abstract compileInHost(componentType: Type): Promise; + abstract compileInHost(componentType: Type): Promise; abstract clearCache(); } -function _isCompiledHostTemplate(type: any): boolean { - return type instanceof CompiledHostTemplate; +function isHostViewFactory(type: any): boolean { + return type instanceof HostViewFactory; } @Injectable() export class Compiler_ extends Compiler { - constructor(private _protoViewFactory: ProtoViewFactory) { super(); } - - compileInHost(componentType: Type): Promise { + compileInHost(componentType: Type): Promise { var metadatas = reflector.annotations(componentType); - var compiledHostTemplate = metadatas.find(_isCompiledHostTemplate); + var hostViewFactory = metadatas.find(isHostViewFactory); - if (isBlank(compiledHostTemplate)) { - throw new BaseException( - `No precompiled template for component ${stringify(componentType)} found`); + if (isBlank(hostViewFactory)) { + throw new BaseException(`No precompiled component ${stringify(componentType)} found`); } - return PromiseWrapper.resolve(this._createProtoView(compiledHostTemplate)); + return PromiseWrapper.resolve(new HostViewFactoryRef_(hostViewFactory)); } - private _createProtoView(compiledHostTemplate: CompiledHostTemplate): ProtoViewRef { - return this._protoViewFactory.createHost(compiledHostTemplate).ref; - } - - clearCache() { this._protoViewFactory.clearCache(); } -} - -export function internalCreateProtoView(compiler: Compiler, - compiledHostTemplate: CompiledHostTemplate): ProtoViewRef { - return (compiler)._createProtoView(compiledHostTemplate); + clearCache() {} } diff --git a/modules/angular2/src/core/linker/directive_resolver.ts b/modules/angular2/src/core/linker/directive_resolver.ts index 1d19c4e9fe..ae52c0cfc5 100644 --- a/modules/angular2/src/core/linker/directive_resolver.ts +++ b/modules/angular2/src/core/linker/directive_resolver.ts @@ -138,3 +138,5 @@ export class DirectiveResolver { } } } + +export var CODEGEN_DIRECTIVE_RESOLVER = new DirectiveResolver(); diff --git a/modules/angular2/src/core/linker/dynamic_component_loader.ts b/modules/angular2/src/core/linker/dynamic_component_loader.ts index 7613835930..e885ab892f 100644 --- a/modules/angular2/src/core/linker/dynamic_component_loader.ts +++ b/modules/angular2/src/core/linker/dynamic_component_loader.ts @@ -3,8 +3,8 @@ import {Compiler} from './compiler'; import {isType, Type, stringify, isPresent} from 'angular2/src/facade/lang'; import {Promise} from 'angular2/src/facade/async'; import {AppViewManager} from 'angular2/src/core/linker/view_manager'; -import {ElementRef} from './element_ref'; -import {ViewRef, HostViewRef} from './view_ref'; +import {ElementRef, ElementRef_} from './element_ref'; +import {HostViewRef} from './view_ref'; /** * Represents an instance of a Component created via {@link DynamicComponentLoader}. @@ -42,7 +42,9 @@ export abstract class ComponentRef { /** * The {@link ViewRef} of the Host View of this Component instance. */ - get hostView(): HostViewRef { return this.location.parentView; } + get hostView(): HostViewRef { + return (this.location).internalElement.parentView.ref; + } /** * @internal @@ -140,7 +142,7 @@ export abstract class DynamicComponentLoader { * ``` */ abstract loadAsRoot(type: Type, overrideSelector: string, injector: Injector, - onDispose?: () => void): Promise; + onDispose?: () => void, projectableNodes?: any[][]): Promise; /** * Creates an instance of a Component and attaches it to a View Container located inside of the @@ -190,7 +192,8 @@ export abstract class DynamicComponentLoader { * ``` */ abstract loadIntoLocation(type: Type, hostLocation: ElementRef, anchorName: string, - providers?: ResolvedProvider[]): Promise; + providers?: ResolvedProvider[], + projectableNodes?: any[][]): Promise; /** * Creates an instance of a Component and attaches it to the View Container found at the @@ -232,19 +235,19 @@ export abstract class DynamicComponentLoader { * Child * ``` */ - abstract loadNextToLocation(type: Type, location: ElementRef, - providers?: ResolvedProvider[]): Promise; + abstract loadNextToLocation(type: Type, location: ElementRef, providers?: ResolvedProvider[], + projectableNodes?: any[][]): Promise; } @Injectable() export class DynamicComponentLoader_ extends DynamicComponentLoader { constructor(private _compiler: Compiler, private _viewManager: AppViewManager) { super(); } - loadAsRoot(type: Type, overrideSelector: string, injector: Injector, - onDispose?: () => void): Promise { + loadAsRoot(type: Type, overrideSelector: string, injector: Injector, onDispose?: () => void, + projectableNodes?: any[][]): Promise { return this._compiler.compileInHost(type).then(hostProtoViewRef => { - var hostViewRef = - this._viewManager.createRootHostView(hostProtoViewRef, overrideSelector, injector); + var hostViewRef = this._viewManager.createRootHostView(hostProtoViewRef, overrideSelector, + injector, projectableNodes); var newLocation = this._viewManager.getHostElement(hostViewRef); var component = this._viewManager.getComponent(newLocation); @@ -259,24 +262,25 @@ export class DynamicComponentLoader_ extends DynamicComponentLoader { } loadIntoLocation(type: Type, hostLocation: ElementRef, anchorName: string, - providers: ResolvedProvider[] = null): Promise { + providers: ResolvedProvider[] = null, + projectableNodes: any[][] = null): Promise { return this.loadNextToLocation( - type, this._viewManager.getNamedElementInComponentView(hostLocation, anchorName), - providers); + type, this._viewManager.getNamedElementInComponentView(hostLocation, anchorName), providers, + projectableNodes); } - loadNextToLocation(type: Type, location: ElementRef, - providers: ResolvedProvider[] = null): Promise { + loadNextToLocation(type: Type, location: ElementRef, providers: ResolvedProvider[] = null, + projectableNodes: any[][] = null): Promise { return this._compiler.compileInHost(type).then(hostProtoViewRef => { var viewContainer = this._viewManager.getViewContainer(location); - var hostViewRef = - viewContainer.createHostView(hostProtoViewRef, viewContainer.length, providers); + var hostViewRef = viewContainer.createHostView(hostProtoViewRef, viewContainer.length, + providers, projectableNodes); var newLocation = this._viewManager.getHostElement(hostViewRef); var component = this._viewManager.getComponent(newLocation); var dispose = () => { - var index = viewContainer.indexOf(hostViewRef); - if (index !== -1) { + var index = viewContainer.indexOf(hostViewRef); + if (!hostViewRef.destroyed && index !== -1) { viewContainer.remove(index); } }; diff --git a/modules/angular2/src/core/linker/element.ts b/modules/angular2/src/core/linker/element.ts new file mode 100644 index 0000000000..7911e35c60 --- /dev/null +++ b/modules/angular2/src/core/linker/element.ts @@ -0,0 +1,867 @@ +import { + isPresent, + isBlank, + Type, + stringify, + CONST_EXPR, + StringWrapper +} from 'angular2/src/facade/lang'; +import {BaseException} from 'angular2/src/facade/exceptions'; +import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; +import { + Injector, + Key, + Dependency, + provide, + Provider, + ResolvedProvider, + NoProviderError, + AbstractProviderError, + CyclicDependencyError, + resolveForwardRef, + Injectable +} from 'angular2/src/core/di'; +import {mergeResolvedProviders} from 'angular2/src/core/di/provider'; +import { + UNDEFINED, + ProtoInjector, + Visibility, + InjectorInlineStrategy, + InjectorDynamicStrategy, + ProviderWithVisibility, + DependencyProvider +} from 'angular2/src/core/di/injector'; +import {resolveProvider, ResolvedFactory, ResolvedProvider_} from 'angular2/src/core/di/provider'; + +import {AttributeMetadata, QueryMetadata} from '../metadata/di'; + +import {AppView} from './view'; +import {ViewType} from './view_type'; +import {ElementRef_} from './element_ref'; + +import {ViewContainerRef} from './view_container_ref'; +import {ElementRef} from './element_ref'; +import {Renderer} from 'angular2/src/core/render/api'; +import {TemplateRef, TemplateRef_} from './template_ref'; +import {DirectiveMetadata, ComponentMetadata} from '../metadata/directives'; +import { + ChangeDetector, + ChangeDetectorRef +} from 'angular2/src/core/change_detection/change_detection'; +import {QueryList} from './query_list'; +import {reflector} from 'angular2/src/core/reflection/reflection'; +import {SetterFn} from 'angular2/src/core/reflection/types'; +import {AfterViewChecked} from 'angular2/src/core/linker/interfaces'; +import {PipeProvider} from 'angular2/src/core/pipes/pipe_provider'; + +import {ViewContainerRef_} from "./view_container_ref"; +import {ResolvedMetadataCache} from './resolved_metadata_cache'; + +var _staticKeys; + +export class StaticKeys { + templateRefId: number; + viewContainerId: number; + changeDetectorRefId: number; + elementRefId: number; + rendererId: number; + + constructor() { + this.templateRefId = Key.get(TemplateRef).id; + this.viewContainerId = Key.get(ViewContainerRef).id; + this.changeDetectorRefId = Key.get(ChangeDetectorRef).id; + this.elementRefId = Key.get(ElementRef).id; + this.rendererId = Key.get(Renderer).id; + } + + static instance(): StaticKeys { + if (isBlank(_staticKeys)) _staticKeys = new StaticKeys(); + return _staticKeys; + } +} + +export class DirectiveDependency extends Dependency { + constructor(key: Key, optional: boolean, lowerBoundVisibility: Object, + upperBoundVisibility: Object, properties: any[], public attributeName: string, + public queryDecorator: QueryMetadata) { + super(key, optional, lowerBoundVisibility, upperBoundVisibility, properties); + this._verify(); + } + + /** @internal */ + _verify(): void { + var count = 0; + if (isPresent(this.queryDecorator)) count++; + if (isPresent(this.attributeName)) count++; + if (count > 1) + throw new BaseException( + 'A directive injectable can contain only one of the following @Attribute or @Query.'); + } + + static createFrom(d: Dependency): DirectiveDependency { + return new DirectiveDependency( + d.key, d.optional, d.lowerBoundVisibility, d.upperBoundVisibility, d.properties, + DirectiveDependency._attributeName(d.properties), DirectiveDependency._query(d.properties)); + } + + /** @internal */ + static _attributeName(properties: any[]): string { + var p = properties.find(p => p instanceof AttributeMetadata); + return isPresent(p) ? p.attributeName : null; + } + + /** @internal */ + static _query(properties: any[]): QueryMetadata { + return properties.find(p => p instanceof QueryMetadata); + } +} + +export class DirectiveProvider extends ResolvedProvider_ { + constructor(key: Key, factory: Function, deps: Dependency[], public isComponent: boolean, + public providers: ResolvedProvider[], public viewProviders: ResolvedProvider[], + public queries: QueryMetadataWithSetter[]) { + super(key, [new ResolvedFactory(factory, deps)], false); + } + + get displayName(): string { return this.key.displayName; } + + static createFromType(type: Type, meta: DirectiveMetadata): DirectiveProvider { + var provider = new Provider(type, {useClass: type}); + if (isBlank(meta)) { + meta = new DirectiveMetadata(); + } + var rb = resolveProvider(provider); + var rf = rb.resolvedFactories[0]; + var deps: DirectiveDependency[] = rf.dependencies.map(DirectiveDependency.createFrom); + var isComponent = meta instanceof ComponentMetadata; + var resolvedProviders = isPresent(meta.providers) ? Injector.resolve(meta.providers) : null; + var resolvedViewProviders = meta instanceof ComponentMetadata && isPresent(meta.viewProviders) ? + Injector.resolve(meta.viewProviders) : + null; + var queries = []; + if (isPresent(meta.queries)) { + StringMapWrapper.forEach(meta.queries, (meta, fieldName) => { + var setter = reflector.setter(fieldName); + queries.push(new QueryMetadataWithSetter(setter, meta)); + }); + } + // queries passed into the constructor. + // TODO: remove this after constructor queries are no longer supported + deps.forEach(d => { + if (isPresent(d.queryDecorator)) { + queries.push(new QueryMetadataWithSetter(null, d.queryDecorator)); + } + }); + return new DirectiveProvider(rb.key, rf.factory, deps, isComponent, resolvedProviders, + resolvedViewProviders, queries); + } +} + +export class QueryMetadataWithSetter { + constructor(public setter: SetterFn, public metadata: QueryMetadata) {} +} + + +function setProvidersVisibility(providers: ResolvedProvider[], visibility: Visibility, + result: Map) { + for (var i = 0; i < providers.length; i++) { + result.set(providers[i].key.id, visibility); + } +} + +export class AppProtoElement { + protoInjector: ProtoInjector; + + static create(metadataCache: ResolvedMetadataCache, index: number, + attributes: {[key: string]: string}, directiveTypes: Type[], + directiveVariableBindings: {[key: string]: number}): AppProtoElement { + var componentDirProvider = null; + var mergedProvidersMap: Map = new Map(); + var providerVisibilityMap: Map = new Map(); + var providers = ListWrapper.createGrowableSize(directiveTypes.length); + + var protoQueryRefs = []; + for (var i = 0; i < directiveTypes.length; i++) { + var dirProvider = metadataCache.getResolvedDirectiveMetadata(directiveTypes[i]); + providers[i] = new ProviderWithVisibility( + dirProvider, dirProvider.isComponent ? Visibility.PublicAndPrivate : Visibility.Public); + + if (dirProvider.isComponent) { + componentDirProvider = dirProvider; + } else { + if (isPresent(dirProvider.providers)) { + mergeResolvedProviders(dirProvider.providers, mergedProvidersMap); + setProvidersVisibility(dirProvider.providers, Visibility.Public, providerVisibilityMap); + } + } + if (isPresent(dirProvider.viewProviders)) { + mergeResolvedProviders(dirProvider.viewProviders, mergedProvidersMap); + setProvidersVisibility(dirProvider.viewProviders, Visibility.Private, + providerVisibilityMap); + } + for (var queryIdx = 0; queryIdx < dirProvider.queries.length; queryIdx++) { + var q = dirProvider.queries[queryIdx]; + protoQueryRefs.push(new ProtoQueryRef(i, q.setter, q.metadata)); + } + } + if (isPresent(componentDirProvider) && isPresent(componentDirProvider.providers)) { + // directive providers need to be prioritized over component providers + mergeResolvedProviders(componentDirProvider.providers, mergedProvidersMap); + setProvidersVisibility(componentDirProvider.providers, Visibility.Public, + providerVisibilityMap); + } + mergedProvidersMap.forEach((provider, _) => { + providers.push( + new ProviderWithVisibility(provider, providerVisibilityMap.get(provider.key.id))); + }); + + return new AppProtoElement(isPresent(componentDirProvider), index, attributes, providers, + protoQueryRefs, directiveVariableBindings); + } + + constructor(public firstProviderIsComponent: boolean, public index: number, + public attributes: {[key: string]: string}, pwvs: ProviderWithVisibility[], + public protoQueryRefs: ProtoQueryRef[], + public directiveVariableBindings: {[key: string]: number}) { + var length = pwvs.length; + if (length > 0) { + this.protoInjector = new ProtoInjector(pwvs); + } else { + this.protoInjector = null; + this.protoQueryRefs = []; + } + } + + getProviderAtIndex(index: number): any { return this.protoInjector.getProviderAtIndex(index); } +} + +class _Context { + constructor(public element: any, public componentElement: any, public injector: any) {} +} + +export class InjectorWithHostBoundary { + constructor(public injector: Injector, public hostInjectorBoundary: boolean) {} +} + +export class AppElement implements DependencyProvider, ElementRef, AfterViewChecked { + static getViewParentInjector(parentViewType: ViewType, containerAppElement: AppElement, + imperativelyCreatedProviders: ResolvedProvider[], + rootInjector: Injector): InjectorWithHostBoundary { + var parentInjector; + var hostInjectorBoundary; + switch (parentViewType) { + case ViewType.COMPONENT: + parentInjector = containerAppElement._injector; + hostInjectorBoundary = true; + break; + case ViewType.EMBEDDED: + parentInjector = isPresent(containerAppElement.proto.protoInjector) ? + containerAppElement._injector.parent : + containerAppElement._injector; + hostInjectorBoundary = containerAppElement._injector.hostBoundary; + break; + case ViewType.HOST: + if (isPresent(containerAppElement)) { + // host view is attached to a container + parentInjector = isPresent(containerAppElement.proto.protoInjector) ? + containerAppElement._injector.parent : + containerAppElement._injector; + if (isPresent(imperativelyCreatedProviders)) { + var imperativeProvidersWithVisibility = imperativelyCreatedProviders.map( + p => new ProviderWithVisibility(p, Visibility.Public)); + // The imperative injector is similar to having an element between + // the dynamic-loaded component and its parent => no boundary between + // the component and imperativelyCreatedInjector. + parentInjector = new Injector(new ProtoInjector(imperativeProvidersWithVisibility), + parentInjector, true, null, null); + hostInjectorBoundary = false; + } else { + hostInjectorBoundary = containerAppElement._injector.hostBoundary; + } + } else { + // bootstrap + parentInjector = rootInjector; + hostInjectorBoundary = true; + } + break; + } + return new InjectorWithHostBoundary(parentInjector, hostInjectorBoundary); + } + + public nestedViews: AppView[] = null; + public componentView: AppView = null; + + private _queryStrategy: _QueryStrategy; + private _injector: Injector; + private _strategy: _ElementDirectiveStrategy; + public ref: ElementRef_; + + constructor(public proto: AppProtoElement, public parentView: AppView, public parent: AppElement, + public nativeElement: any, public embeddedViewFactory: Function) { + this.ref = new ElementRef_(this); + var parentInjector = isPresent(parent) ? parent._injector : parentView.parentInjector; + if (isPresent(this.proto.protoInjector)) { + var isBoundary; + if (isPresent(parent) && isPresent(parent.proto.protoInjector)) { + isBoundary = false; + } else { + isBoundary = parentView.hostInjectorBoundary; + } + this._queryStrategy = this._buildQueryStrategy(); + this._injector = new Injector(this.proto.protoInjector, parentInjector, isBoundary, this, + () => this._debugContext()); + + // we couple ourselves to the injector strategy to avoid polymorphic calls + var injectorStrategy = this._injector.internalStrategy; + this._strategy = injectorStrategy instanceof InjectorInlineStrategy ? + new ElementDirectiveInlineStrategy(injectorStrategy, this) : + new ElementDirectiveDynamicStrategy(injectorStrategy, this); + this._strategy.init(); + } else { + this._queryStrategy = null; + this._injector = parentInjector; + this._strategy = null; + } + } + + attachComponentView(componentView: AppView) { this.componentView = componentView; } + + private _debugContext(): any { + var c = this.parentView.getDebugContext(this, null, null); + return isPresent(c) ? new _Context(c.element, c.componentElement, c.injector) : null; + } + + hasVariableBinding(name: string): boolean { + var vb = this.proto.directiveVariableBindings; + return isPresent(vb) && StringMapWrapper.contains(vb, name); + } + + getVariableBinding(name: string): any { + var index = this.proto.directiveVariableBindings[name]; + return isPresent(index) ? this.getDirectiveAtIndex(index) : this.getElementRef(); + } + + get(token: any): any { return this._injector.get(token); } + + hasDirective(type: Type): boolean { return isPresent(this._injector.getOptional(type)); } + + getComponent(): any { return isPresent(this._strategy) ? this._strategy.getComponent() : null; } + + getInjector(): Injector { return this._injector; } + + getElementRef(): ElementRef { return this.ref; } + + getViewContainerRef(): ViewContainerRef { return new ViewContainerRef_(this); } + + getTemplateRef(): TemplateRef { + if (isPresent(this.embeddedViewFactory)) { + return new TemplateRef_(this.ref); + } + return null; + } + + getDependency(injector: Injector, provider: ResolvedProvider, dep: Dependency): any { + if (provider instanceof DirectiveProvider) { + var dirDep = dep; + + if (isPresent(dirDep.attributeName)) return this._buildAttribute(dirDep); + + if (isPresent(dirDep.queryDecorator)) + return this._queryStrategy.findQuery(dirDep.queryDecorator).list; + + if (dirDep.key.id === StaticKeys.instance().changeDetectorRefId) { + // We provide the component's view change detector to components and + // the surrounding component's change detector to directives. + if (this.proto.firstProviderIsComponent) { + // Note: The component view is not yet created when + // this method is called! + return new _ComponentViewChangeDetectorRef(this); + } else { + return this.parentView.changeDetector.ref; + } + } + + if (dirDep.key.id === StaticKeys.instance().elementRefId) { + return this.getElementRef(); + } + + if (dirDep.key.id === StaticKeys.instance().viewContainerId) { + return this.getViewContainerRef(); + } + + if (dirDep.key.id === StaticKeys.instance().templateRefId) { + var tr = this.getTemplateRef(); + if (isBlank(tr) && !dirDep.optional) { + throw new NoProviderError(null, dirDep.key); + } + return tr; + } + + if (dirDep.key.id === StaticKeys.instance().rendererId) { + return this.parentView.renderer; + } + + } else if (provider instanceof PipeProvider) { + if (dep.key.id === StaticKeys.instance().changeDetectorRefId) { + // We provide the component's view change detector to components and + // the surrounding component's change detector to directives. + if (this.proto.firstProviderIsComponent) { + // Note: The component view is not yet created when + // this method is called! + return new _ComponentViewChangeDetectorRef(this); + } else { + return this.parentView.changeDetector; + } + } + } + + return UNDEFINED; + } + + private _buildAttribute(dep: DirectiveDependency): string { + var attributes = this.proto.attributes; + if (isPresent(attributes) && StringMapWrapper.contains(attributes, dep.attributeName)) { + return attributes[dep.attributeName]; + } else { + return null; + } + } + + addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void { + var templateRef = this.getTemplateRef(); + if (query.selector === TemplateRef && isPresent(templateRef)) { + list.push(templateRef); + } + if (this._strategy != null) { + this._strategy.addDirectivesMatchingQuery(query, list); + } + } + + private _buildQueryStrategy(): _QueryStrategy { + if (this.proto.protoQueryRefs.length === 0) { + return _emptyQueryStrategy; + } else if (this.proto.protoQueryRefs.length <= + InlineQueryStrategy.NUMBER_OF_SUPPORTED_QUERIES) { + return new InlineQueryStrategy(this); + } else { + return new DynamicQueryStrategy(this); + } + } + + + getDirectiveAtIndex(index: number): any { return this._injector.getAt(index); } + + ngAfterViewChecked(): void { + if (isPresent(this._queryStrategy)) this._queryStrategy.updateViewQueries(); + } + + ngAfterContentChecked(): void { + if (isPresent(this._queryStrategy)) this._queryStrategy.updateContentQueries(); + } + + traverseAndSetQueriesAsDirty(): void { + var inj: AppElement = this; + while (isPresent(inj)) { + inj._setQueriesAsDirty(); + inj = inj.parent; + } + } + + private _setQueriesAsDirty(): void { + if (isPresent(this._queryStrategy)) { + this._queryStrategy.setContentQueriesAsDirty(); + } + if (this.parentView.proto.type === ViewType.COMPONENT) { + this.parentView.containerAppElement._queryStrategy.setViewQueriesAsDirty(); + } + } +} + +interface _QueryStrategy { + setContentQueriesAsDirty(): void; + setViewQueriesAsDirty(): void; + updateContentQueries(): void; + updateViewQueries(): void; + findQuery(query: QueryMetadata): QueryRef; +} + +class _EmptyQueryStrategy implements _QueryStrategy { + setContentQueriesAsDirty(): void {} + setViewQueriesAsDirty(): void {} + updateContentQueries(): void {} + updateViewQueries(): void {} + findQuery(query: QueryMetadata): QueryRef { + throw new BaseException(`Cannot find query for directive ${query}.`); + } +} + +var _emptyQueryStrategy = new _EmptyQueryStrategy(); + +class InlineQueryStrategy implements _QueryStrategy { + static NUMBER_OF_SUPPORTED_QUERIES = 3; + + query0: QueryRef; + query1: QueryRef; + query2: QueryRef; + + constructor(ei: AppElement) { + var protoRefs = ei.proto.protoQueryRefs; + if (protoRefs.length > 0) this.query0 = new QueryRef(protoRefs[0], ei); + if (protoRefs.length > 1) this.query1 = new QueryRef(protoRefs[1], ei); + if (protoRefs.length > 2) this.query2 = new QueryRef(protoRefs[2], ei); + } + + setContentQueriesAsDirty(): void { + if (isPresent(this.query0) && !this.query0.isViewQuery) this.query0.dirty = true; + if (isPresent(this.query1) && !this.query1.isViewQuery) this.query1.dirty = true; + if (isPresent(this.query2) && !this.query2.isViewQuery) this.query2.dirty = true; + } + + setViewQueriesAsDirty(): void { + if (isPresent(this.query0) && this.query0.isViewQuery) this.query0.dirty = true; + if (isPresent(this.query1) && this.query1.isViewQuery) this.query1.dirty = true; + if (isPresent(this.query2) && this.query2.isViewQuery) this.query2.dirty = true; + } + + updateContentQueries() { + if (isPresent(this.query0) && !this.query0.isViewQuery) { + this.query0.update(); + } + if (isPresent(this.query1) && !this.query1.isViewQuery) { + this.query1.update(); + } + if (isPresent(this.query2) && !this.query2.isViewQuery) { + this.query2.update(); + } + } + + updateViewQueries() { + if (isPresent(this.query0) && this.query0.isViewQuery) { + this.query0.update(); + } + if (isPresent(this.query1) && this.query1.isViewQuery) { + this.query1.update(); + } + if (isPresent(this.query2) && this.query2.isViewQuery) { + this.query2.update(); + } + } + + findQuery(query: QueryMetadata): QueryRef { + if (isPresent(this.query0) && this.query0.protoQueryRef.query === query) { + return this.query0; + } + if (isPresent(this.query1) && this.query1.protoQueryRef.query === query) { + return this.query1; + } + if (isPresent(this.query2) && this.query2.protoQueryRef.query === query) { + return this.query2; + } + throw new BaseException(`Cannot find query for directive ${query}.`); + } +} + +class DynamicQueryStrategy implements _QueryStrategy { + queries: QueryRef[]; + + constructor(ei: AppElement) { + this.queries = ei.proto.protoQueryRefs.map(p => new QueryRef(p, ei)); + } + + setContentQueriesAsDirty(): void { + for (var i = 0; i < this.queries.length; ++i) { + var q = this.queries[i]; + if (!q.isViewQuery) q.dirty = true; + } + } + + setViewQueriesAsDirty(): void { + for (var i = 0; i < this.queries.length; ++i) { + var q = this.queries[i]; + if (q.isViewQuery) q.dirty = true; + } + } + + updateContentQueries() { + for (var i = 0; i < this.queries.length; ++i) { + var q = this.queries[i]; + if (!q.isViewQuery) { + q.update(); + } + } + } + + updateViewQueries() { + for (var i = 0; i < this.queries.length; ++i) { + var q = this.queries[i]; + if (q.isViewQuery) { + q.update(); + } + } + } + + findQuery(query: QueryMetadata): QueryRef { + for (var i = 0; i < this.queries.length; ++i) { + var q = this.queries[i]; + if (q.protoQueryRef.query === query) { + return q; + } + } + throw new BaseException(`Cannot find query for directive ${query}.`); + } +} + +interface _ElementDirectiveStrategy { + getComponent(): any; + isComponentKey(key: Key): boolean; + addDirectivesMatchingQuery(q: QueryMetadata, res: any[]): void; + init(): void; +} + +/** + * Strategy used by the `ElementInjector` when the number of providers is 10 or less. + * In such a case, inlining fields is beneficial for performances. + */ +class ElementDirectiveInlineStrategy implements _ElementDirectiveStrategy { + constructor(public injectorStrategy: InjectorInlineStrategy, public _ei: AppElement) {} + + init(): void { + var i = this.injectorStrategy; + var p = i.protoStrategy; + i.resetConstructionCounter(); + + if (p.provider0 instanceof DirectiveProvider && isPresent(p.keyId0) && i.obj0 === UNDEFINED) + i.obj0 = i.instantiateProvider(p.provider0, p.visibility0); + if (p.provider1 instanceof DirectiveProvider && isPresent(p.keyId1) && i.obj1 === UNDEFINED) + i.obj1 = i.instantiateProvider(p.provider1, p.visibility1); + if (p.provider2 instanceof DirectiveProvider && isPresent(p.keyId2) && i.obj2 === UNDEFINED) + i.obj2 = i.instantiateProvider(p.provider2, p.visibility2); + if (p.provider3 instanceof DirectiveProvider && isPresent(p.keyId3) && i.obj3 === UNDEFINED) + i.obj3 = i.instantiateProvider(p.provider3, p.visibility3); + if (p.provider4 instanceof DirectiveProvider && isPresent(p.keyId4) && i.obj4 === UNDEFINED) + i.obj4 = i.instantiateProvider(p.provider4, p.visibility4); + if (p.provider5 instanceof DirectiveProvider && isPresent(p.keyId5) && i.obj5 === UNDEFINED) + i.obj5 = i.instantiateProvider(p.provider5, p.visibility5); + if (p.provider6 instanceof DirectiveProvider && isPresent(p.keyId6) && i.obj6 === UNDEFINED) + i.obj6 = i.instantiateProvider(p.provider6, p.visibility6); + if (p.provider7 instanceof DirectiveProvider && isPresent(p.keyId7) && i.obj7 === UNDEFINED) + i.obj7 = i.instantiateProvider(p.provider7, p.visibility7); + if (p.provider8 instanceof DirectiveProvider && isPresent(p.keyId8) && i.obj8 === UNDEFINED) + i.obj8 = i.instantiateProvider(p.provider8, p.visibility8); + if (p.provider9 instanceof DirectiveProvider && isPresent(p.keyId9) && i.obj9 === UNDEFINED) + i.obj9 = i.instantiateProvider(p.provider9, p.visibility9); + } + + getComponent(): any { return this.injectorStrategy.obj0; } + + isComponentKey(key: Key): boolean { + return this._ei.proto.firstProviderIsComponent && isPresent(key) && + key.id === this.injectorStrategy.protoStrategy.keyId0; + } + + addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void { + var i = this.injectorStrategy; + var p = i.protoStrategy; + if (isPresent(p.provider0) && p.provider0.key.token === query.selector) { + if (i.obj0 === UNDEFINED) i.obj0 = i.instantiateProvider(p.provider0, p.visibility0); + list.push(i.obj0); + } + if (isPresent(p.provider1) && p.provider1.key.token === query.selector) { + if (i.obj1 === UNDEFINED) i.obj1 = i.instantiateProvider(p.provider1, p.visibility1); + list.push(i.obj1); + } + if (isPresent(p.provider2) && p.provider2.key.token === query.selector) { + if (i.obj2 === UNDEFINED) i.obj2 = i.instantiateProvider(p.provider2, p.visibility2); + list.push(i.obj2); + } + if (isPresent(p.provider3) && p.provider3.key.token === query.selector) { + if (i.obj3 === UNDEFINED) i.obj3 = i.instantiateProvider(p.provider3, p.visibility3); + list.push(i.obj3); + } + if (isPresent(p.provider4) && p.provider4.key.token === query.selector) { + if (i.obj4 === UNDEFINED) i.obj4 = i.instantiateProvider(p.provider4, p.visibility4); + list.push(i.obj4); + } + if (isPresent(p.provider5) && p.provider5.key.token === query.selector) { + if (i.obj5 === UNDEFINED) i.obj5 = i.instantiateProvider(p.provider5, p.visibility5); + list.push(i.obj5); + } + if (isPresent(p.provider6) && p.provider6.key.token === query.selector) { + if (i.obj6 === UNDEFINED) i.obj6 = i.instantiateProvider(p.provider6, p.visibility6); + list.push(i.obj6); + } + if (isPresent(p.provider7) && p.provider7.key.token === query.selector) { + if (i.obj7 === UNDEFINED) i.obj7 = i.instantiateProvider(p.provider7, p.visibility7); + list.push(i.obj7); + } + if (isPresent(p.provider8) && p.provider8.key.token === query.selector) { + if (i.obj8 === UNDEFINED) i.obj8 = i.instantiateProvider(p.provider8, p.visibility8); + list.push(i.obj8); + } + if (isPresent(p.provider9) && p.provider9.key.token === query.selector) { + if (i.obj9 === UNDEFINED) i.obj9 = i.instantiateProvider(p.provider9, p.visibility9); + list.push(i.obj9); + } + } +} + +/** + * Strategy used by the `ElementInjector` when the number of bindings is 11 or more. + * In such a case, there are too many fields to inline (see ElementInjectorInlineStrategy). + */ +class ElementDirectiveDynamicStrategy implements _ElementDirectiveStrategy { + constructor(public injectorStrategy: InjectorDynamicStrategy, public _ei: AppElement) {} + + init(): void { + var inj = this.injectorStrategy; + var p = inj.protoStrategy; + inj.resetConstructionCounter(); + + for (var i = 0; i < p.keyIds.length; i++) { + if (p.providers[i] instanceof DirectiveProvider && isPresent(p.keyIds[i]) && + inj.objs[i] === UNDEFINED) { + inj.objs[i] = inj.instantiateProvider(p.providers[i], p.visibilities[i]); + } + } + } + + getComponent(): any { return this.injectorStrategy.objs[0]; } + + isComponentKey(key: Key): boolean { + var p = this.injectorStrategy.protoStrategy; + return this._ei.proto.firstProviderIsComponent && isPresent(key) && key.id === p.keyIds[0]; + } + + addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void { + var ist = this.injectorStrategy; + var p = ist.protoStrategy; + + for (var i = 0; i < p.providers.length; i++) { + if (p.providers[i].key.token === query.selector) { + if (ist.objs[i] === UNDEFINED) { + ist.objs[i] = ist.instantiateProvider(p.providers[i], p.visibilities[i]); + } + list.push(ist.objs[i]); + } + } + } +} + +export class ProtoQueryRef { + constructor(public dirIndex: number, public setter: SetterFn, public query: QueryMetadata) {} + + get usesPropertySyntax(): boolean { return isPresent(this.setter); } +} + +export class QueryRef { + public list: QueryList; + public dirty: boolean; + + constructor(public protoQueryRef: ProtoQueryRef, private originator: AppElement) { + this.list = new QueryList(); + this.dirty = true; + } + + get isViewQuery(): boolean { return this.protoQueryRef.query.isViewQuery; } + + update(): void { + if (!this.dirty) return; + this._update(); + this.dirty = false; + + // TODO delete the check once only field queries are supported + if (this.protoQueryRef.usesPropertySyntax) { + var dir = this.originator.getDirectiveAtIndex(this.protoQueryRef.dirIndex); + if (this.protoQueryRef.query.first) { + this.protoQueryRef.setter(dir, this.list.length > 0 ? this.list.first : null); + } else { + this.protoQueryRef.setter(dir, this.list); + } + } + + this.list.notifyOnChanges(); + } + + private _update(): void { + var aggregator = []; + if (this.protoQueryRef.query.isViewQuery) { + // intentionally skipping originator for view queries. + var nestedView = this.originator.componentView; + if (isPresent(nestedView)) this._visitView(nestedView, aggregator); + } else { + this._visit(this.originator, aggregator); + } + this.list.reset(aggregator); + }; + + private _visit(inj: AppElement, aggregator: any[]): void { + var view = inj.parentView; + var startIdx = inj.proto.index; + for (var i = startIdx; i < view.appElements.length; i++) { + var curInj = view.appElements[i]; + // The first injector after inj, that is outside the subtree rooted at + // inj has to have a null parent or a parent that is an ancestor of inj. + if (i > startIdx && (isBlank(curInj.parent) || curInj.parent.proto.index < startIdx)) { + break; + } + + if (!this.protoQueryRef.query.descendants && + !(curInj.parent == this.originator || curInj == this.originator)) + continue; + + // We visit the view container(VC) views right after the injector that contains + // the VC. Theoretically, that might not be the right order if there are + // child injectors of said injector. Not clear whether if such case can + // even be constructed with the current apis. + this._visitInjector(curInj, aggregator); + this._visitViewContainerViews(curInj.nestedViews, aggregator); + } + } + + private _visitInjector(inj: AppElement, aggregator: any[]) { + if (this.protoQueryRef.query.isVarBindingQuery) { + this._aggregateVariableBinding(inj, aggregator); + } else { + this._aggregateDirective(inj, aggregator); + } + } + + private _visitViewContainerViews(views: AppView[], aggregator: any[]) { + if (isPresent(views)) { + for (var j = 0; j < views.length; j++) { + this._visitView(views[j], aggregator); + } + } + } + + private _visitView(view: AppView, aggregator: any[]) { + for (var i = 0; i < view.appElements.length; i++) { + var inj = view.appElements[i]; + this._visitInjector(inj, aggregator); + this._visitViewContainerViews(inj.nestedViews, aggregator); + } + } + + private _aggregateVariableBinding(inj: AppElement, aggregator: any[]): void { + var vb = this.protoQueryRef.query.varBindings; + for (var i = 0; i < vb.length; ++i) { + if (inj.hasVariableBinding(vb[i])) { + aggregator.push(inj.getVariableBinding(vb[i])); + } + } + } + + private _aggregateDirective(inj: AppElement, aggregator: any[]): void { + inj.addDirectivesMatchingQuery(this.protoQueryRef.query, aggregator); + } +} + +class _ComponentViewChangeDetectorRef extends ChangeDetectorRef { + constructor(private _appElement: AppElement) { super(); } + + markForCheck(): void { this._appElement.componentView.changeDetector.ref.markForCheck(); } + detach(): void { this._appElement.componentView.changeDetector.ref.detach(); } + detectChanges(): void { this._appElement.componentView.changeDetector.ref.detectChanges(); } + checkNoChanges(): void { this._appElement.componentView.changeDetector.ref.checkNoChanges(); } + reattach(): void { this._appElement.componentView.changeDetector.ref.reattach(); } +} diff --git a/modules/angular2/src/core/linker/element_binder.ts b/modules/angular2/src/core/linker/element_binder.ts deleted file mode 100644 index 00da679b67..0000000000 --- a/modules/angular2/src/core/linker/element_binder.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {isBlank} from 'angular2/src/facade/lang'; -import {BaseException} from 'angular2/src/facade/exceptions'; -import * as eiModule from './element_injector'; -import {DirectiveProvider} from './element_injector'; -import * as viewModule from './view'; - -export class ElementBinder { - constructor(public index: number, public parent: ElementBinder, public distanceToParent: number, - public protoElementInjector: eiModule.ProtoElementInjector, - public componentDirective: DirectiveProvider, - public nestedProtoView: viewModule.AppProtoView) { - if (isBlank(index)) { - throw new BaseException('null index not allowed.'); - } - } -} diff --git a/modules/angular2/src/core/linker/element_injector.ts b/modules/angular2/src/core/linker/element_injector.ts deleted file mode 100644 index cbfa777174..0000000000 --- a/modules/angular2/src/core/linker/element_injector.ts +++ /dev/null @@ -1,1086 +0,0 @@ -import { - isPresent, - isBlank, - Type, - stringify, - CONST_EXPR, - StringWrapper -} from 'angular2/src/facade/lang'; -import {BaseException} from 'angular2/src/facade/exceptions'; -import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async'; -import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; -import { - Injector, - Key, - Dependency, - provide, - Provider, - ResolvedProvider, - NoProviderError, - AbstractProviderError, - CyclicDependencyError, - resolveForwardRef -} from 'angular2/src/core/di'; -import { - UNDEFINED, - ProtoInjector, - Visibility, - InjectorInlineStrategy, - InjectorDynamicStrategy, - ProviderWithVisibility, - DependencyProvider -} from 'angular2/src/core/di/injector'; -import {resolveProvider, ResolvedFactory, ResolvedProvider_} from 'angular2/src/core/di/provider'; - -import {AttributeMetadata, QueryMetadata} from '../metadata/di'; - -import {AppViewContainer, AppView} from './view'; -/* circular */ import * as avmModule from './view_manager'; -import {ViewContainerRef} from './view_container_ref'; -import {ElementRef} from './element_ref'; -import {TemplateRef} from './template_ref'; -import {DirectiveMetadata, ComponentMetadata} from '../metadata/directives'; -import {hasLifecycleHook} from './directive_lifecycle_reflector'; -import { - ChangeDetector, - ChangeDetectorRef -} from 'angular2/src/core/change_detection/change_detection'; -import {QueryList} from './query_list'; -import {reflector} from 'angular2/src/core/reflection/reflection'; -import {SetterFn} from 'angular2/src/core/reflection/types'; -import {EventConfig} from 'angular2/src/core/linker/event_config'; -import {AfterViewChecked} from 'angular2/src/core/linker/interfaces'; -import {PipeProvider} from 'angular2/src/core/pipes/pipe_provider'; - -import {LifecycleHooks} from './interfaces'; -import {ViewContainerRef_} from "./view_container_ref"; - -var _staticKeys; - -export class StaticKeys { - viewManagerId: number; - templateRefId: number; - viewContainerId: number; - changeDetectorRefId: number; - elementRefId: number; - - constructor() { - this.viewManagerId = Key.get(avmModule.AppViewManager).id; - this.templateRefId = Key.get(TemplateRef).id; - this.viewContainerId = Key.get(ViewContainerRef).id; - this.changeDetectorRefId = Key.get(ChangeDetectorRef).id; - this.elementRefId = Key.get(ElementRef).id; - } - - static instance(): StaticKeys { - if (isBlank(_staticKeys)) _staticKeys = new StaticKeys(); - return _staticKeys; - } -} - -export class TreeNode> { - /** @internal */ - _parent: T; - constructor(parent: T) { - if (isPresent(parent)) { - parent.addChild(this); - } else { - this._parent = null; - } - } - - addChild(child: T): void { child._parent = this; } - - remove(): void { this._parent = null; } - - get parent() { return this._parent; } -} - -export class DirectiveDependency extends Dependency { - constructor(key: Key, optional: boolean, lowerBoundVisibility: Object, - upperBoundVisibility: Object, properties: any[], public attributeName: string, - public queryDecorator: QueryMetadata) { - super(key, optional, lowerBoundVisibility, upperBoundVisibility, properties); - this._verify(); - } - - /** @internal */ - _verify(): void { - var count = 0; - if (isPresent(this.queryDecorator)) count++; - if (isPresent(this.attributeName)) count++; - if (count > 1) - throw new BaseException( - 'A directive injectable can contain only one of the following @Attribute or @Query.'); - } - - static createFrom(d: Dependency): Dependency { - return new DirectiveDependency( - d.key, d.optional, d.lowerBoundVisibility, d.upperBoundVisibility, d.properties, - DirectiveDependency._attributeName(d.properties), DirectiveDependency._query(d.properties)); - } - - /** @internal */ - static _attributeName(properties: any[]): string { - var p = properties.find(p => p instanceof AttributeMetadata); - return isPresent(p) ? p.attributeName : null; - } - - /** @internal */ - static _query(properties: any[]): QueryMetadata { - return properties.find(p => p instanceof QueryMetadata); - } -} - -export class DirectiveProvider extends ResolvedProvider_ { - public callOnDestroy: boolean; - - constructor(key: Key, factory: Function, deps: Dependency[], public metadata: DirectiveMetadata, - public providers: Array, - public viewProviders: Array) { - super(key, [new ResolvedFactory(factory, deps)], false); - this.callOnDestroy = hasLifecycleHook(LifecycleHooks.OnDestroy, key.token); - } - - get displayName(): string { return this.key.displayName; } - - get queries(): QueryMetadataWithSetter[] { - if (isBlank(this.metadata.queries)) return []; - - var res = []; - StringMapWrapper.forEach(this.metadata.queries, (meta, fieldName) => { - var setter = reflector.setter(fieldName); - res.push(new QueryMetadataWithSetter(setter, meta)); - }); - return res; - } - - get eventEmitters(): string[] { - return isPresent(this.metadata) && isPresent(this.metadata.outputs) ? this.metadata.outputs : - []; - } - - static createFromProvider(provider: Provider, meta: DirectiveMetadata): DirectiveProvider { - if (isBlank(meta)) { - meta = new DirectiveMetadata(); - } - - var rb = resolveProvider(provider); - var rf = rb.resolvedFactories[0]; - var deps = rf.dependencies.map(DirectiveDependency.createFrom); - - var providers = isPresent(meta.providers) ? meta.providers : []; - var viewBindigs = meta instanceof ComponentMetadata && isPresent(meta.viewProviders) ? - meta.viewProviders : - []; - return new DirectiveProvider(rb.key, rf.factory, deps, meta, providers, viewBindigs); - } - - static createFromType(type: Type, annotation: DirectiveMetadata): DirectiveProvider { - var provider = new Provider(type, {useClass: type}); - return DirectiveProvider.createFromProvider(provider, annotation); - } -} - -// TODO(rado): benchmark and consider rolling in as ElementInjector fields. -export class PreBuiltObjects { - nestedView: AppView = null; - constructor(public viewManager: avmModule.AppViewManager, public view: AppView, - public elementRef: ElementRef, public templateRef: TemplateRef) {} -} - -export class QueryMetadataWithSetter { - constructor(public setter: SetterFn, public metadata: QueryMetadata) {} -} - -export class EventEmitterAccessor { - constructor(public eventName: string, public getter: Function) {} - - subscribe(view: AppView, boundElementIndex: number, directive: Object): Object { - var eventEmitter = this.getter(directive); - return ObservableWrapper.subscribe( - eventEmitter, - eventObj => view.triggerEventHandlers(this.eventName, eventObj, boundElementIndex)); - } -} - -function _createEventEmitterAccessors(bwv: ProviderWithVisibility): EventEmitterAccessor[] { - var provider = bwv.provider; - if (!(provider instanceof DirectiveProvider)) return []; - var db = provider; - return db.eventEmitters.map(eventConfig => { - var parsedEvent = EventConfig.parse(eventConfig); - return new EventEmitterAccessor(parsedEvent.eventName, reflector.getter(parsedEvent.fieldName)); - }); -} - -function _createProtoQueryRefs(providers: ProviderWithVisibility[]): ProtoQueryRef[] { - var res = []; - ListWrapper.forEachWithIndex(providers, (b, i) => { - if (b.provider instanceof DirectiveProvider) { - var directiveProvider = b.provider; - // field queries - var queries: QueryMetadataWithSetter[] = directiveProvider.queries; - queries.forEach(q => res.push(new ProtoQueryRef(i, q.setter, q.metadata))); - - // queries passed into the constructor. - // TODO: remove this after constructor queries are no longer supported - var deps: DirectiveDependency[] = - directiveProvider.resolvedFactory.dependencies; - deps.forEach(d => { - if (isPresent(d.queryDecorator)) res.push(new ProtoQueryRef(i, null, d.queryDecorator)); - }); - } - }); - return res; -} - -export class ProtoElementInjector { - view: AppView; - attributes: Map; - eventEmitterAccessors: EventEmitterAccessor[][]; - protoQueryRefs: ProtoQueryRef[]; - protoInjector: ProtoInjector; - - static create(parent: ProtoElementInjector, index: number, providers: DirectiveProvider[], - firstProviderIsComponent: boolean, distanceToParent: number, - directiveVariableBindings: Map): ProtoElementInjector { - var bd = []; - - ProtoElementInjector._createDirectiveProviderWithVisibility(providers, bd, - firstProviderIsComponent); - if (firstProviderIsComponent) { - ProtoElementInjector._createViewProvidersWithVisibility(providers, bd); - } - - ProtoElementInjector._createProvidersWithVisibility(providers, bd); - return new ProtoElementInjector(parent, index, bd, distanceToParent, firstProviderIsComponent, - directiveVariableBindings); - } - - private static _createDirectiveProviderWithVisibility(dirProviders: DirectiveProvider[], - bd: ProviderWithVisibility[], - firstProviderIsComponent: boolean) { - dirProviders.forEach(dirProvider => { - bd.push(ProtoElementInjector._createProviderWithVisibility( - firstProviderIsComponent, dirProvider, dirProviders, dirProvider)); - }); - } - - private static _createProvidersWithVisibility(dirProviders: DirectiveProvider[], - bd: ProviderWithVisibility[]) { - var providersFromAllDirectives = []; - dirProviders.forEach(dirProvider => { - providersFromAllDirectives = - ListWrapper.concat(providersFromAllDirectives, dirProvider.providers); - }); - - var resolved = Injector.resolve(providersFromAllDirectives); - resolved.forEach(b => bd.push(new ProviderWithVisibility(b, Visibility.Public))); - } - - private static _createProviderWithVisibility(firstProviderIsComponent: boolean, - dirProvider: DirectiveProvider, - dirProviders: DirectiveProvider[], - provider: ResolvedProvider) { - var isComponent = firstProviderIsComponent && dirProviders[0] === dirProvider; - return new ProviderWithVisibility( - provider, isComponent ? Visibility.PublicAndPrivate : Visibility.Public); - } - - private static _createViewProvidersWithVisibility(dirProviders: DirectiveProvider[], - bd: ProviderWithVisibility[]) { - var resolvedViewProviders = Injector.resolve(dirProviders[0].viewProviders); - resolvedViewProviders.forEach(b => bd.push(new ProviderWithVisibility(b, Visibility.Private))); - } - - /** @internal */ - public _firstProviderIsComponent: boolean; - - - constructor(public parent: ProtoElementInjector, public index: number, - bwv: ProviderWithVisibility[], public distanceToParent: number, - _firstProviderIsComponent: boolean, - public directiveVariableBindings: Map) { - this._firstProviderIsComponent = _firstProviderIsComponent; - var length = bwv.length; - this.protoInjector = new ProtoInjector(bwv); - this.eventEmitterAccessors = ListWrapper.createFixedSize(length); - for (var i = 0; i < length; ++i) { - this.eventEmitterAccessors[i] = _createEventEmitterAccessors(bwv[i]); - } - this.protoQueryRefs = _createProtoQueryRefs(bwv); - } - - instantiate(parent: ElementInjector): ElementInjector { - return new ElementInjector(this, parent); - } - - directParent(): ProtoElementInjector { return this.distanceToParent < 2 ? this.parent : null; } - - get hasBindings(): boolean { return this.eventEmitterAccessors.length > 0; } - - getProviderAtIndex(index: number): any { return this.protoInjector.getProviderAtIndex(index); } -} - -class _Context { - constructor(public element: any, public componentElement: any, public injector: any) {} -} - -export class ElementInjector extends TreeNode implements DependencyProvider, - AfterViewChecked { - private _host: ElementInjector; - private _preBuiltObjects: PreBuiltObjects = null; - private _queryStrategy: _QueryStrategy; - - hydrated: boolean; - - private _injector: Injector; - private _strategy: _ElementInjectorStrategy; - /** @internal */ - public _proto: ProtoElementInjector; - - constructor(_proto: ProtoElementInjector, parent: ElementInjector) { - super(parent); - this._proto = _proto; - this._injector = - new Injector(this._proto.protoInjector, null, this, () => this._debugContext()); - - // we couple ourselves to the injector strategy to avoid polymoprhic calls - var injectorStrategy = this._injector.internalStrategy; - this._strategy = injectorStrategy instanceof InjectorInlineStrategy ? - new ElementInjectorInlineStrategy(injectorStrategy, this) : - new ElementInjectorDynamicStrategy(injectorStrategy, this); - - this.hydrated = false; - - this._queryStrategy = this._buildQueryStrategy(); - } - - dehydrate(): void { - this.hydrated = false; - this._host = null; - this._preBuiltObjects = null; - this._strategy.callOnDestroy(); - this._strategy.dehydrate(); - this._queryStrategy.dehydrate(); - } - - hydrate(imperativelyCreatedInjector: Injector, host: ElementInjector, - preBuiltObjects: PreBuiltObjects): void { - this._host = host; - this._preBuiltObjects = preBuiltObjects; - - this._reattachInjectors(imperativelyCreatedInjector); - this._queryStrategy.hydrate(); - this._strategy.hydrate(); - - this.hydrated = true; - } - - private _debugContext(): any { - var p = this._preBuiltObjects; - var index = p.elementRef.boundElementIndex - p.view.elementOffset; - var c = this._preBuiltObjects.view.getDebugContext(index, null); - return isPresent(c) ? new _Context(c.element, c.componentElement, c.injector) : null; - } - - private _reattachInjectors(imperativelyCreatedInjector: Injector): void { - // Dynamically-loaded component in the template. Not a root ElementInjector. - if (isPresent(this._parent)) { - if (isPresent(imperativelyCreatedInjector)) { - // The imperative injector is similar to having an element between - // the dynamic-loaded component and its parent => no boundaries. - this._reattachInjector(this._injector, imperativelyCreatedInjector, false); - this._reattachInjector(imperativelyCreatedInjector, this._parent._injector, false); - } else { - this._reattachInjector(this._injector, this._parent._injector, false); - } - - // Dynamically-loaded component in the template. A root ElementInjector. - } else if (isPresent(this._host)) { - // The imperative injector is similar to having an element between - // the dynamic-loaded component and its parent => no boundary between - // the component and imperativelyCreatedInjector. - // But since it is a root ElementInjector, we need to create a boundary - // between imperativelyCreatedInjector and _host. - if (isPresent(imperativelyCreatedInjector)) { - this._reattachInjector(this._injector, imperativelyCreatedInjector, false); - this._reattachInjector(imperativelyCreatedInjector, this._host._injector, true); - } else { - this._reattachInjector(this._injector, this._host._injector, true); - } - - // Bootstrap - } else { - if (isPresent(imperativelyCreatedInjector)) { - this._reattachInjector(this._injector, imperativelyCreatedInjector, true); - } - } - } - - private _reattachInjector(injector: Injector, parentInjector: Injector, isBoundary: boolean) { - injector.internalStrategy.attach(parentInjector, isBoundary); - } - - hasVariableBinding(name: string): boolean { - var vb = this._proto.directiveVariableBindings; - return isPresent(vb) && vb.has(name); - } - - getVariableBinding(name: string): any { - var index = this._proto.directiveVariableBindings.get(name); - return isPresent(index) ? this.getDirectiveAtIndex(index) : this.getElementRef(); - } - - get(token: any): any { return this._injector.get(token); } - - hasDirective(type: Type): boolean { return isPresent(this._injector.getOptional(type)); } - - getEventEmitterAccessors(): EventEmitterAccessor[][] { return this._proto.eventEmitterAccessors; } - - getDirectiveVariableBindings(): Map { - return this._proto.directiveVariableBindings; - } - - getComponent(): any { return this._strategy.getComponent(); } - - getInjector(): Injector { return this._injector; } - - getElementRef(): ElementRef { return this._preBuiltObjects.elementRef; } - - getViewContainerRef(): ViewContainerRef { - return new ViewContainerRef_(this._preBuiltObjects.viewManager, this.getElementRef()); - } - - getNestedView(): AppView { return this._preBuiltObjects.nestedView; } - - getView(): AppView { return this._preBuiltObjects.view; } - - directParent(): ElementInjector { return this._proto.distanceToParent < 2 ? this.parent : null; } - - isComponentKey(key: Key): boolean { return this._strategy.isComponentKey(key); } - - getDependency(injector: Injector, provider: ResolvedProvider, dep: Dependency): any { - var key: Key = dep.key; - - if (provider instanceof DirectiveProvider) { - var dirDep = dep; - var dirProvider = provider; - var staticKeys = StaticKeys.instance(); - - - if (key.id === staticKeys.viewManagerId) return this._preBuiltObjects.viewManager; - - if (isPresent(dirDep.attributeName)) return this._buildAttribute(dirDep); - - if (isPresent(dirDep.queryDecorator)) - return this._queryStrategy.findQuery(dirDep.queryDecorator).list; - - if (dirDep.key.id === StaticKeys.instance().changeDetectorRefId) { - // We provide the component's view change detector to components and - // the surrounding component's change detector to directives. - if (dirProvider.metadata instanceof ComponentMetadata) { - var componentView = this._preBuiltObjects.view.getNestedView( - this._preBuiltObjects.elementRef.boundElementIndex); - return componentView.changeDetector.ref; - } else { - return this._preBuiltObjects.view.changeDetector.ref; - } - } - - if (dirDep.key.id === StaticKeys.instance().elementRefId) { - return this.getElementRef(); - } - - if (dirDep.key.id === StaticKeys.instance().viewContainerId) { - return this.getViewContainerRef(); - } - - if (dirDep.key.id === StaticKeys.instance().templateRefId) { - if (isBlank(this._preBuiltObjects.templateRef)) { - if (dirDep.optional) { - return null; - } - - throw new NoProviderError(null, dirDep.key); - } - return this._preBuiltObjects.templateRef; - } - - } else if (provider instanceof PipeProvider) { - if (dep.key.id === StaticKeys.instance().changeDetectorRefId) { - var componentView = this._preBuiltObjects.view.getNestedView( - this._preBuiltObjects.elementRef.boundElementIndex); - return componentView.changeDetector.ref; - } - } - - return UNDEFINED; - } - - private _buildAttribute(dep: DirectiveDependency): string { - var attributes = this._proto.attributes; - if (isPresent(attributes) && attributes.has(dep.attributeName)) { - return attributes.get(dep.attributeName); - } else { - return null; - } - } - - addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void { - var templateRef = isBlank(this._preBuiltObjects) ? null : this._preBuiltObjects.templateRef; - if (query.selector === TemplateRef && isPresent(templateRef)) { - list.push(templateRef); - } - this._strategy.addDirectivesMatchingQuery(query, list); - } - - private _buildQueryStrategy(): _QueryStrategy { - if (this._proto.protoQueryRefs.length === 0) { - return _emptyQueryStrategy; - } else if (this._proto.protoQueryRefs.length <= - InlineQueryStrategy.NUMBER_OF_SUPPORTED_QUERIES) { - return new InlineQueryStrategy(this); - } else { - return new DynamicQueryStrategy(this); - } - } - - link(parent: ElementInjector): void { parent.addChild(this); } - - unlink(): void { this.remove(); } - - getDirectiveAtIndex(index: number): any { return this._injector.getAt(index); } - - hasInstances(): boolean { return this._proto.hasBindings && this.hydrated; } - - getHost(): ElementInjector { return this._host; } - - getBoundElementIndex(): number { return this._proto.index; } - - getRootViewInjectors(): ElementInjector[] { - if (!this.hydrated) return []; - var view = this._preBuiltObjects.view; - var nestedView = view.getNestedView(view.elementOffset + this.getBoundElementIndex()); - return isPresent(nestedView) ? nestedView.rootElementInjectors : []; - } - - ngAfterViewChecked(): void { this._queryStrategy.updateViewQueries(); } - - ngAfterContentChecked(): void { this._queryStrategy.updateContentQueries(); } - - traverseAndSetQueriesAsDirty(): void { - var inj: ElementInjector = this; - while (isPresent(inj)) { - inj._setQueriesAsDirty(); - inj = inj.parent; - } - } - - private _setQueriesAsDirty(): void { - this._queryStrategy.setContentQueriesAsDirty(); - if (isPresent(this._host)) this._host._queryStrategy.setViewQueriesAsDirty(); - } -} - -interface _QueryStrategy { - setContentQueriesAsDirty(): void; - setViewQueriesAsDirty(): void; - hydrate(): void; - dehydrate(): void; - updateContentQueries(): void; - updateViewQueries(): void; - findQuery(query: QueryMetadata): QueryRef; -} - -class _EmptyQueryStrategy implements _QueryStrategy { - setContentQueriesAsDirty(): void {} - setViewQueriesAsDirty(): void {} - hydrate(): void {} - dehydrate(): void {} - updateContentQueries(): void {} - updateViewQueries(): void {} - findQuery(query: QueryMetadata): QueryRef { - throw new BaseException(`Cannot find query for directive ${query}.`); - } -} - -var _emptyQueryStrategy = new _EmptyQueryStrategy(); - -class InlineQueryStrategy implements _QueryStrategy { - static NUMBER_OF_SUPPORTED_QUERIES = 3; - - query0: QueryRef; - query1: QueryRef; - query2: QueryRef; - - constructor(ei: ElementInjector) { - var protoRefs = ei._proto.protoQueryRefs; - if (protoRefs.length > 0) this.query0 = new QueryRef(protoRefs[0], ei); - if (protoRefs.length > 1) this.query1 = new QueryRef(protoRefs[1], ei); - if (protoRefs.length > 2) this.query2 = new QueryRef(protoRefs[2], ei); - } - - setContentQueriesAsDirty(): void { - if (isPresent(this.query0) && !this.query0.isViewQuery) this.query0.dirty = true; - if (isPresent(this.query1) && !this.query1.isViewQuery) this.query1.dirty = true; - if (isPresent(this.query2) && !this.query2.isViewQuery) this.query2.dirty = true; - } - - setViewQueriesAsDirty(): void { - if (isPresent(this.query0) && this.query0.isViewQuery) this.query0.dirty = true; - if (isPresent(this.query1) && this.query1.isViewQuery) this.query1.dirty = true; - if (isPresent(this.query2) && this.query2.isViewQuery) this.query2.dirty = true; - } - - hydrate(): void { - if (isPresent(this.query0)) this.query0.hydrate(); - if (isPresent(this.query1)) this.query1.hydrate(); - if (isPresent(this.query2)) this.query2.hydrate(); - } - - dehydrate(): void { - if (isPresent(this.query0)) this.query0.dehydrate(); - if (isPresent(this.query1)) this.query1.dehydrate(); - if (isPresent(this.query2)) this.query2.dehydrate(); - } - - updateContentQueries() { - if (isPresent(this.query0) && !this.query0.isViewQuery) { - this.query0.update(); - } - if (isPresent(this.query1) && !this.query1.isViewQuery) { - this.query1.update(); - } - if (isPresent(this.query2) && !this.query2.isViewQuery) { - this.query2.update(); - } - } - - updateViewQueries() { - if (isPresent(this.query0) && this.query0.isViewQuery) { - this.query0.update(); - } - if (isPresent(this.query1) && this.query1.isViewQuery) { - this.query1.update(); - } - if (isPresent(this.query2) && this.query2.isViewQuery) { - this.query2.update(); - } - } - - findQuery(query: QueryMetadata): QueryRef { - if (isPresent(this.query0) && this.query0.protoQueryRef.query === query) { - return this.query0; - } - if (isPresent(this.query1) && this.query1.protoQueryRef.query === query) { - return this.query1; - } - if (isPresent(this.query2) && this.query2.protoQueryRef.query === query) { - return this.query2; - } - throw new BaseException(`Cannot find query for directive ${query}.`); - } -} - -class DynamicQueryStrategy implements _QueryStrategy { - queries: QueryRef[]; - - constructor(ei: ElementInjector) { - this.queries = ei._proto.protoQueryRefs.map(p => new QueryRef(p, ei)); - } - - setContentQueriesAsDirty(): void { - for (var i = 0; i < this.queries.length; ++i) { - var q = this.queries[i]; - if (!q.isViewQuery) q.dirty = true; - } - } - - setViewQueriesAsDirty(): void { - for (var i = 0; i < this.queries.length; ++i) { - var q = this.queries[i]; - if (q.isViewQuery) q.dirty = true; - } - } - - hydrate(): void { - for (var i = 0; i < this.queries.length; ++i) { - var q = this.queries[i]; - q.hydrate(); - } - } - - dehydrate(): void { - for (var i = 0; i < this.queries.length; ++i) { - var q = this.queries[i]; - q.dehydrate(); - } - } - - updateContentQueries() { - for (var i = 0; i < this.queries.length; ++i) { - var q = this.queries[i]; - if (!q.isViewQuery) { - q.update(); - } - } - } - - updateViewQueries() { - for (var i = 0; i < this.queries.length; ++i) { - var q = this.queries[i]; - if (q.isViewQuery) { - q.update(); - } - } - } - - findQuery(query: QueryMetadata): QueryRef { - for (var i = 0; i < this.queries.length; ++i) { - var q = this.queries[i]; - if (q.protoQueryRef.query === query) { - return q; - } - } - throw new BaseException(`Cannot find query for directive ${query}.`); - } -} - -interface _ElementInjectorStrategy { - callOnDestroy(): void; - getComponent(): any; - isComponentKey(key: Key): boolean; - addDirectivesMatchingQuery(q: QueryMetadata, res: any[]): void; - hydrate(): void; - dehydrate(): void; -} - -/** - * Strategy used by the `ElementInjector` when the number of providers is 10 or less. - * In such a case, inlining fields is beneficial for performances. - */ -class ElementInjectorInlineStrategy implements _ElementInjectorStrategy { - constructor(public injectorStrategy: InjectorInlineStrategy, public _ei: ElementInjector) {} - - hydrate(): void { - var i = this.injectorStrategy; - var p = i.protoStrategy; - i.resetConstructionCounter(); - - if (p.provider0 instanceof DirectiveProvider && isPresent(p.keyId0) && i.obj0 === UNDEFINED) - i.obj0 = i.instantiateProvider(p.provider0, p.visibility0); - if (p.provider1 instanceof DirectiveProvider && isPresent(p.keyId1) && i.obj1 === UNDEFINED) - i.obj1 = i.instantiateProvider(p.provider1, p.visibility1); - if (p.provider2 instanceof DirectiveProvider && isPresent(p.keyId2) && i.obj2 === UNDEFINED) - i.obj2 = i.instantiateProvider(p.provider2, p.visibility2); - if (p.provider3 instanceof DirectiveProvider && isPresent(p.keyId3) && i.obj3 === UNDEFINED) - i.obj3 = i.instantiateProvider(p.provider3, p.visibility3); - if (p.provider4 instanceof DirectiveProvider && isPresent(p.keyId4) && i.obj4 === UNDEFINED) - i.obj4 = i.instantiateProvider(p.provider4, p.visibility4); - if (p.provider5 instanceof DirectiveProvider && isPresent(p.keyId5) && i.obj5 === UNDEFINED) - i.obj5 = i.instantiateProvider(p.provider5, p.visibility5); - if (p.provider6 instanceof DirectiveProvider && isPresent(p.keyId6) && i.obj6 === UNDEFINED) - i.obj6 = i.instantiateProvider(p.provider6, p.visibility6); - if (p.provider7 instanceof DirectiveProvider && isPresent(p.keyId7) && i.obj7 === UNDEFINED) - i.obj7 = i.instantiateProvider(p.provider7, p.visibility7); - if (p.provider8 instanceof DirectiveProvider && isPresent(p.keyId8) && i.obj8 === UNDEFINED) - i.obj8 = i.instantiateProvider(p.provider8, p.visibility8); - if (p.provider9 instanceof DirectiveProvider && isPresent(p.keyId9) && i.obj9 === UNDEFINED) - i.obj9 = i.instantiateProvider(p.provider9, p.visibility9); - } - - dehydrate() { - var i = this.injectorStrategy; - - i.obj0 = UNDEFINED; - i.obj1 = UNDEFINED; - i.obj2 = UNDEFINED; - i.obj3 = UNDEFINED; - i.obj4 = UNDEFINED; - i.obj5 = UNDEFINED; - i.obj6 = UNDEFINED; - i.obj7 = UNDEFINED; - i.obj8 = UNDEFINED; - i.obj9 = UNDEFINED; - } - - callOnDestroy(): void { - var i = this.injectorStrategy; - var p = i.protoStrategy; - - if (p.provider0 instanceof DirectiveProvider && - (p.provider0).callOnDestroy) { - i.obj0.ngOnDestroy(); - } - if (p.provider1 instanceof DirectiveProvider && - (p.provider1).callOnDestroy) { - i.obj1.ngOnDestroy(); - } - if (p.provider2 instanceof DirectiveProvider && - (p.provider2).callOnDestroy) { - i.obj2.ngOnDestroy(); - } - if (p.provider3 instanceof DirectiveProvider && - (p.provider3).callOnDestroy) { - i.obj3.ngOnDestroy(); - } - if (p.provider4 instanceof DirectiveProvider && - (p.provider4).callOnDestroy) { - i.obj4.ngOnDestroy(); - } - if (p.provider5 instanceof DirectiveProvider && - (p.provider5).callOnDestroy) { - i.obj5.ngOnDestroy(); - } - if (p.provider6 instanceof DirectiveProvider && - (p.provider6).callOnDestroy) { - i.obj6.ngOnDestroy(); - } - if (p.provider7 instanceof DirectiveProvider && - (p.provider7).callOnDestroy) { - i.obj7.ngOnDestroy(); - } - if (p.provider8 instanceof DirectiveProvider && - (p.provider8).callOnDestroy) { - i.obj8.ngOnDestroy(); - } - if (p.provider9 instanceof DirectiveProvider && - (p.provider9).callOnDestroy) { - i.obj9.ngOnDestroy(); - } - } - - getComponent(): any { return this.injectorStrategy.obj0; } - - isComponentKey(key: Key): boolean { - return this._ei._proto._firstProviderIsComponent && isPresent(key) && - key.id === this.injectorStrategy.protoStrategy.keyId0; - } - - addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void { - var i = this.injectorStrategy; - var p = i.protoStrategy; - - if (isPresent(p.provider0) && p.provider0.key.token === query.selector) { - if (i.obj0 === UNDEFINED) i.obj0 = i.instantiateProvider(p.provider0, p.visibility0); - list.push(i.obj0); - } - if (isPresent(p.provider1) && p.provider1.key.token === query.selector) { - if (i.obj1 === UNDEFINED) i.obj1 = i.instantiateProvider(p.provider1, p.visibility1); - list.push(i.obj1); - } - if (isPresent(p.provider2) && p.provider2.key.token === query.selector) { - if (i.obj2 === UNDEFINED) i.obj2 = i.instantiateProvider(p.provider2, p.visibility2); - list.push(i.obj2); - } - if (isPresent(p.provider3) && p.provider3.key.token === query.selector) { - if (i.obj3 === UNDEFINED) i.obj3 = i.instantiateProvider(p.provider3, p.visibility3); - list.push(i.obj3); - } - if (isPresent(p.provider4) && p.provider4.key.token === query.selector) { - if (i.obj4 === UNDEFINED) i.obj4 = i.instantiateProvider(p.provider4, p.visibility4); - list.push(i.obj4); - } - if (isPresent(p.provider5) && p.provider5.key.token === query.selector) { - if (i.obj5 === UNDEFINED) i.obj5 = i.instantiateProvider(p.provider5, p.visibility5); - list.push(i.obj5); - } - if (isPresent(p.provider6) && p.provider6.key.token === query.selector) { - if (i.obj6 === UNDEFINED) i.obj6 = i.instantiateProvider(p.provider6, p.visibility6); - list.push(i.obj6); - } - if (isPresent(p.provider7) && p.provider7.key.token === query.selector) { - if (i.obj7 === UNDEFINED) i.obj7 = i.instantiateProvider(p.provider7, p.visibility7); - list.push(i.obj7); - } - if (isPresent(p.provider8) && p.provider8.key.token === query.selector) { - if (i.obj8 === UNDEFINED) i.obj8 = i.instantiateProvider(p.provider8, p.visibility8); - list.push(i.obj8); - } - if (isPresent(p.provider9) && p.provider9.key.token === query.selector) { - if (i.obj9 === UNDEFINED) i.obj9 = i.instantiateProvider(p.provider9, p.visibility9); - list.push(i.obj9); - } - } -} - -/** - * Strategy used by the `ElementInjector` when the number of bindings is 11 or more. - * In such a case, there are too many fields to inline (see ElementInjectorInlineStrategy). - */ -class ElementInjectorDynamicStrategy implements _ElementInjectorStrategy { - constructor(public injectorStrategy: InjectorDynamicStrategy, public _ei: ElementInjector) {} - - hydrate(): void { - var inj = this.injectorStrategy; - var p = inj.protoStrategy; - inj.resetConstructionCounter(); - - for (var i = 0; i < p.keyIds.length; i++) { - if (p.providers[i] instanceof DirectiveProvider && isPresent(p.keyIds[i]) && - inj.objs[i] === UNDEFINED) { - inj.objs[i] = inj.instantiateProvider(p.providers[i], p.visibilities[i]); - } - } - } - - dehydrate(): void { - var inj = this.injectorStrategy; - ListWrapper.fill(inj.objs, UNDEFINED); - } - - callOnDestroy(): void { - var ist = this.injectorStrategy; - var p = ist.protoStrategy; - - for (var i = 0; i < p.providers.length; i++) { - if (p.providers[i] instanceof DirectiveProvider && - (p.providers[i]).callOnDestroy) { - ist.objs[i].ngOnDestroy(); - } - } - } - - getComponent(): any { return this.injectorStrategy.objs[0]; } - - isComponentKey(key: Key): boolean { - var p = this.injectorStrategy.protoStrategy; - return this._ei._proto._firstProviderIsComponent && isPresent(key) && key.id === p.keyIds[0]; - } - - addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void { - var ist = this.injectorStrategy; - var p = ist.protoStrategy; - - for (var i = 0; i < p.providers.length; i++) { - if (p.providers[i].key.token === query.selector) { - if (ist.objs[i] === UNDEFINED) { - ist.objs[i] = ist.instantiateProvider(p.providers[i], p.visibilities[i]); - } - list.push(ist.objs[i]); - } - } - } -} - -export class ProtoQueryRef { - constructor(public dirIndex: number, public setter: SetterFn, public query: QueryMetadata) {} - - get usesPropertySyntax(): boolean { return isPresent(this.setter); } -} - -export class QueryRef { - public list: QueryList; - public dirty: boolean; - - constructor(public protoQueryRef: ProtoQueryRef, private originator: ElementInjector) {} - - get isViewQuery(): boolean { return this.protoQueryRef.query.isViewQuery; } - - update(): void { - if (!this.dirty) return; - this._update(); - this.dirty = false; - - // TODO delete the check once only field queries are supported - if (this.protoQueryRef.usesPropertySyntax) { - var dir = this.originator.getDirectiveAtIndex(this.protoQueryRef.dirIndex); - if (this.protoQueryRef.query.first) { - this.protoQueryRef.setter(dir, this.list.length > 0 ? this.list.first : null); - } else { - this.protoQueryRef.setter(dir, this.list); - } - } - - this.list.notifyOnChanges(); - } - - private _update(): void { - var aggregator = []; - if (this.protoQueryRef.query.isViewQuery) { - var view = this.originator.getView(); - // intentionally skipping originator for view queries. - var nestedView = - view.getNestedView(view.elementOffset + this.originator.getBoundElementIndex()); - if (isPresent(nestedView)) this._visitView(nestedView, aggregator); - } else { - this._visit(this.originator, aggregator); - } - this.list.reset(aggregator); - }; - - private _visit(inj: ElementInjector, aggregator: any[]): void { - var view = inj.getView(); - var startIdx = view.elementOffset + inj._proto.index; - for (var i = startIdx; i < view.elementOffset + view.ownBindersCount; i++) { - var curInj = view.elementInjectors[i]; - if (isBlank(curInj)) continue; - // The first injector after inj, that is outside the subtree rooted at - // inj has to have a null parent or a parent that is an ancestor of inj. - if (i > startIdx && (isBlank(curInj) || isBlank(curInj.parent) || - view.elementOffset + curInj.parent._proto.index < startIdx)) { - break; - } - - if (!this.protoQueryRef.query.descendants && - !(curInj.parent == this.originator || curInj == this.originator)) - continue; - - // We visit the view container(VC) views right after the injector that contains - // the VC. Theoretically, that might not be the right order if there are - // child injectors of said injector. Not clear whether if such case can - // even be constructed with the current apis. - this._visitInjector(curInj, aggregator); - var vc = view.viewContainers[i]; - if (isPresent(vc)) this._visitViewContainer(vc, aggregator); - } - } - - private _visitInjector(inj: ElementInjector, aggregator: any[]) { - if (this.protoQueryRef.query.isVarBindingQuery) { - this._aggregateVariableBinding(inj, aggregator); - } else { - this._aggregateDirective(inj, aggregator); - } - } - - private _visitViewContainer(vc: AppViewContainer, aggregator: any[]) { - for (var j = 0; j < vc.views.length; j++) { - this._visitView(vc.views[j], aggregator); - } - } - - private _visitView(view: AppView, aggregator: any[]) { - for (var i = view.elementOffset; i < view.elementOffset + view.ownBindersCount; i++) { - var inj = view.elementInjectors[i]; - if (isBlank(inj)) continue; - - this._visitInjector(inj, aggregator); - - var vc = view.viewContainers[i]; - if (isPresent(vc)) this._visitViewContainer(vc, aggregator); - } - } - - private _aggregateVariableBinding(inj: ElementInjector, aggregator: any[]): void { - var vb = this.protoQueryRef.query.varBindings; - for (var i = 0; i < vb.length; ++i) { - if (inj.hasVariableBinding(vb[i])) { - aggregator.push(inj.getVariableBinding(vb[i])); - } - } - } - - private _aggregateDirective(inj: ElementInjector, aggregator: any[]): void { - inj.addDirectivesMatchingQuery(this.protoQueryRef.query, aggregator); - } - - dehydrate(): void { this.list = null; } - - hydrate(): void { - this.list = new QueryList(); - this.dirty = true; - } -} diff --git a/modules/angular2/src/core/linker/element_ref.ts b/modules/angular2/src/core/linker/element_ref.ts index 810fce84cb..a052fb8fa0 100644 --- a/modules/angular2/src/core/linker/element_ref.ts +++ b/modules/angular2/src/core/linker/element_ref.ts @@ -1,6 +1,5 @@ -import {BaseException, unimplemented} from 'angular2/src/facade/exceptions'; -import {ViewRef, ViewRef_} from './view_ref'; -import {RenderViewRef, RenderElementRef, Renderer} from 'angular2/src/core/render/api'; +import {unimplemented} from 'angular2/src/facade/exceptions'; +import {AppElement} from './element'; /** * Represents a location in a View that has an injection, change-detection and render context @@ -12,23 +11,7 @@ import {RenderViewRef, RenderElementRef, Renderer} from 'angular2/src/core/rende * An `ElementRef` is backed by a render-specific element. In the browser, this is usually a DOM * element. */ -export abstract class ElementRef implements RenderElementRef { - /** - * @internal - * - * Reference to the {@link ViewRef} that this `ElementRef` is part of. - */ - parentView: ViewRef; - - /** - * @internal - * - * Index of the element inside the {@link ViewRef}. - * - * This is used internally by the Angular framework to locate elements. - */ - boundElementIndex: number; - +export abstract class ElementRef { /** * The underlying native element or `null` if direct access to native elements is not supported * (e.g. when the application runs in a web worker). @@ -48,24 +31,13 @@ export abstract class ElementRef implements RenderElementRef { *

* */ - get nativeElement(): any { return unimplemented(); }; - - get renderView(): RenderViewRef { return unimplemented(); } + get nativeElement(): any { return unimplemented(); } } -export class ElementRef_ extends ElementRef { - constructor(public parentView: ViewRef, +export class ElementRef_ implements ElementRef { + constructor(private _appElement: AppElement) {} - /** - * Index of the element inside the {@link ViewRef}. - * - * This is used internally by the Angular framework to locate elements. - */ - public boundElementIndex: number, private _renderer: Renderer) { - super(); - } + get internalElement(): AppElement { return this._appElement; } - get renderView(): RenderViewRef { return (this.parentView).render; } - set renderView(value) { unimplemented(); } - get nativeElement(): any { return this._renderer.getNativeElementSync(this); } + get nativeElement() { return this._appElement.nativeElement; } } diff --git a/modules/angular2/src/core/linker/event_config.ts b/modules/angular2/src/core/linker/event_config.ts deleted file mode 100644 index fdbcf2baa0..0000000000 --- a/modules/angular2/src/core/linker/event_config.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const EVENT_TARGET_SEPARATOR = ':'; - -export class EventConfig { - constructor(public fieldName: string, public eventName: string, public isLongForm: boolean) {} - - static parse(eventConfig: string): EventConfig { - var fieldName = eventConfig, eventName = eventConfig, isLongForm = false; - var separatorIdx = eventConfig.indexOf(EVENT_TARGET_SEPARATOR); - if (separatorIdx > -1) { - // long format: 'fieldName: eventName' - fieldName = eventConfig.substring(0, separatorIdx).trim(); - eventName = eventConfig.substring(separatorIdx + 1).trim(); - isLongForm = true; - } - return new EventConfig(fieldName, eventName, isLongForm); - } - - getFullName(): string { - return this.isLongForm ? `${this.fieldName}${EVENT_TARGET_SEPARATOR}${this.eventName}` : - this.eventName; - } -} diff --git a/modules/angular2/src/core/linker/pipe_resolver.ts b/modules/angular2/src/core/linker/pipe_resolver.ts index 67683978c7..3de7a3f23c 100644 --- a/modules/angular2/src/core/linker/pipe_resolver.ts +++ b/modules/angular2/src/core/linker/pipe_resolver.ts @@ -31,3 +31,5 @@ export class PipeResolver { throw new BaseException(`No Pipe decorator found on ${stringify(type)}`); } } + +export var CODEGEN_PIPE_RESOLVER = new PipeResolver(); diff --git a/modules/angular2/src/core/linker/proto_view_factory.ts b/modules/angular2/src/core/linker/proto_view_factory.ts deleted file mode 100644 index 30216a44b4..0000000000 --- a/modules/angular2/src/core/linker/proto_view_factory.ts +++ /dev/null @@ -1,341 +0,0 @@ -import {isPresent, isBlank, Type, isArray, isNumber} from 'angular2/src/facade/lang'; - -import {RenderProtoViewRef, RenderComponentTemplate} from 'angular2/src/core/render/api'; - -import {Optional, Injectable, Provider, resolveForwardRef, Inject} from 'angular2/src/core/di'; - -import {PipeProvider} from '../pipes/pipe_provider'; -import {ProtoPipes} from '../pipes/pipes'; - -import {AppProtoView, AppProtoViewMergeInfo, ViewType} from './view'; -import {ElementBinder} from './element_binder'; -import {ProtoElementInjector, DirectiveProvider} from './element_injector'; -import {DirectiveResolver} from './directive_resolver'; -import {ViewResolver} from './view_resolver'; -import {PipeResolver} from './pipe_resolver'; -import {ViewMetadata, ViewEncapsulation} from '../metadata/view'; -import {PLATFORM_PIPES} from 'angular2/src/core/platform_directives_and_pipes'; - -import { - visitAllCommands, - CompiledComponentTemplate, - CompiledHostTemplate, - TemplateCmd, - CommandVisitor, - EmbeddedTemplateCmd, - BeginComponentCmd, - BeginElementCmd, - IBeginElementCmd, - TextCmd, - NgContentCmd -} from './template_commands'; - -import {Renderer} from 'angular2/src/core/render/api'; -import {APP_ID} from 'angular2/src/core/application_tokens'; - - -@Injectable() -export class ProtoViewFactory { - private _cache: Map = new Map(); - private _nextTemplateId: number = 0; - - constructor(private _renderer: Renderer, - @Optional() @Inject(PLATFORM_PIPES) private _platformPipes: Array, - private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver, - private _pipeResolver: PipeResolver, @Inject(APP_ID) private _appId: string) {} - - clearCache() { this._cache.clear(); } - - createHost(compiledHostTemplate: CompiledHostTemplate): AppProtoView { - var compiledTemplate = compiledHostTemplate.template; - var result = this._cache.get(compiledTemplate.id); - if (isBlank(result)) { - var emptyMap: {[key: string]: PipeProvider} = {}; - var shortId = `${this._appId}-${this._nextTemplateId++}`; - this._renderer.registerComponentTemplate(new RenderComponentTemplate( - compiledTemplate.id, shortId, ViewEncapsulation.None, compiledTemplate.commands, [])); - result = - new AppProtoView(compiledTemplate.id, compiledTemplate.commands, ViewType.HOST, true, - compiledTemplate.changeDetectorFactory, null, new ProtoPipes(emptyMap)); - this._cache.set(compiledTemplate.id, result); - } - return result; - } - - private _createComponent(cmd: BeginComponentCmd): AppProtoView { - var nestedProtoView = this._cache.get(cmd.templateId); - if (isBlank(nestedProtoView)) { - var component = cmd.directives[0]; - var view = this._viewResolver.resolve(component); - var compiledTemplate = cmd.templateGetter(); - var styles = _flattenStyleArr(compiledTemplate.styles, []); - var shortId = `${this._appId}-${this._nextTemplateId++}`; - this._renderer.registerComponentTemplate(new RenderComponentTemplate( - compiledTemplate.id, shortId, cmd.encapsulation, compiledTemplate.commands, styles)); - var boundPipes = this._flattenPipes(view).map(pipe => this._bindPipe(pipe)); - - nestedProtoView = new AppProtoView( - compiledTemplate.id, compiledTemplate.commands, ViewType.COMPONENT, true, - compiledTemplate.changeDetectorFactory, null, ProtoPipes.fromProviders(boundPipes)); - // Note: The cache is updated before recursing - // to be able to resolve cycles - this._cache.set(compiledTemplate.id, nestedProtoView); - this._initializeProtoView(nestedProtoView, null); - } - return nestedProtoView; - } - - private _createEmbeddedTemplate(cmd: EmbeddedTemplateCmd, parent: AppProtoView): AppProtoView { - var nestedProtoView = new AppProtoView( - parent.templateId, cmd.children, ViewType.EMBEDDED, cmd.isMerged, cmd.changeDetectorFactory, - arrayToMap(cmd.variableNameAndValues, true), new ProtoPipes(parent.pipes.config)); - if (cmd.isMerged) { - this.initializeProtoViewIfNeeded(nestedProtoView); - } - return nestedProtoView; - } - - initializeProtoViewIfNeeded(protoView: AppProtoView) { - if (!protoView.isInitialized()) { - var render = this._renderer.createProtoView(protoView.templateId, protoView.templateCmds); - this._initializeProtoView(protoView, render); - } - } - - private _initializeProtoView(protoView: AppProtoView, render: RenderProtoViewRef) { - var initializer = new _ProtoViewInitializer(protoView, this._directiveResolver, this); - visitAllCommands(initializer, protoView.templateCmds); - var mergeInfo = - new AppProtoViewMergeInfo(initializer.mergeEmbeddedViewCount, initializer.mergeElementCount, - initializer.mergeViewCount); - protoView.init(render, initializer.elementBinders, initializer.boundTextCount, mergeInfo, - initializer.variableLocations); - } - - private _bindPipe(typeOrProvider): PipeProvider { - let meta = this._pipeResolver.resolve(typeOrProvider); - return PipeProvider.createFromType(typeOrProvider, meta); - } - - private _flattenPipes(view: ViewMetadata): any[] { - let pipes = []; - if (isPresent(this._platformPipes)) { - _flattenArray(this._platformPipes, pipes); - } - if (isPresent(view.pipes)) { - _flattenArray(view.pipes, pipes); - } - return pipes; - } -} - - -function createComponent(protoViewFactory: ProtoViewFactory, cmd: BeginComponentCmd): AppProtoView { - return (protoViewFactory)._createComponent(cmd); -} - -function createEmbeddedTemplate(protoViewFactory: ProtoViewFactory, cmd: EmbeddedTemplateCmd, - parent: AppProtoView): AppProtoView { - return (protoViewFactory)._createEmbeddedTemplate(cmd, parent); -} - -class _ProtoViewInitializer implements CommandVisitor { - variableLocations: Map = new Map(); - boundTextCount: number = 0; - boundElementIndex: number = 0; - elementBinderStack: ElementBinder[] = []; - distanceToParentElementBinder: number = 0; - distanceToParentProtoElementInjector: number = 0; - elementBinders: ElementBinder[] = []; - mergeEmbeddedViewCount: number = 0; - mergeElementCount: number = 0; - mergeViewCount: number = 1; - - constructor(private _protoView: AppProtoView, private _directiveResolver: DirectiveResolver, - private _protoViewFactory: ProtoViewFactory) {} - - visitText(cmd: TextCmd, context: any): any { - if (cmd.isBound) { - this.boundTextCount++; - } - return null; - } - visitNgContent(cmd: NgContentCmd, context: any): any { return null; } - visitBeginElement(cmd: BeginElementCmd, context: any): any { - if (cmd.isBound) { - this._visitBeginBoundElement(cmd, null); - } else { - this._visitBeginElement(cmd, null, null); - } - return null; - } - visitEndElement(context: any): any { return this._visitEndElement(); } - visitBeginComponent(cmd: BeginComponentCmd, context: any): any { - var nestedProtoView = createComponent(this._protoViewFactory, cmd); - return this._visitBeginBoundElement(cmd, nestedProtoView); - } - visitEndComponent(context: any): any { return this._visitEndElement(); } - visitEmbeddedTemplate(cmd: EmbeddedTemplateCmd, context: any): any { - var nestedProtoView = createEmbeddedTemplate(this._protoViewFactory, cmd, this._protoView); - if (cmd.isMerged) { - this.mergeEmbeddedViewCount++; - } - this._visitBeginBoundElement(cmd, nestedProtoView); - return this._visitEndElement(); - } - - private _visitBeginBoundElement(cmd: IBeginElementCmd, nestedProtoView: AppProtoView): any { - if (isPresent(nestedProtoView) && nestedProtoView.isMergable) { - this.mergeElementCount += nestedProtoView.mergeInfo.elementCount; - this.mergeViewCount += nestedProtoView.mergeInfo.viewCount; - this.mergeEmbeddedViewCount += nestedProtoView.mergeInfo.embeddedViewCount; - } - var elementBinder = _createElementBinder( - this._directiveResolver, nestedProtoView, this.elementBinderStack, this.boundElementIndex, - this.distanceToParentElementBinder, this.distanceToParentProtoElementInjector, cmd); - this.elementBinders.push(elementBinder); - var protoElementInjector = elementBinder.protoElementInjector; - for (var i = 0; i < cmd.variableNameAndValues.length; i += 2) { - this.variableLocations.set(cmd.variableNameAndValues[i], this.boundElementIndex); - } - this.boundElementIndex++; - this.mergeElementCount++; - return this._visitBeginElement(cmd, elementBinder, protoElementInjector); - } - - private _visitBeginElement(cmd: IBeginElementCmd, elementBinder: ElementBinder, - protoElementInjector: ProtoElementInjector): any { - this.distanceToParentElementBinder = - isPresent(elementBinder) ? 1 : this.distanceToParentElementBinder + 1; - this.distanceToParentProtoElementInjector = - isPresent(protoElementInjector) ? 1 : this.distanceToParentProtoElementInjector + 1; - this.elementBinderStack.push(elementBinder); - return null; - } - - private _visitEndElement(): any { - var parentElementBinder = this.elementBinderStack.pop(); - var parentProtoElementInjector = - isPresent(parentElementBinder) ? parentElementBinder.protoElementInjector : null; - this.distanceToParentElementBinder = isPresent(parentElementBinder) ? - parentElementBinder.distanceToParent : - this.distanceToParentElementBinder - 1; - this.distanceToParentProtoElementInjector = isPresent(parentProtoElementInjector) ? - parentProtoElementInjector.distanceToParent : - this.distanceToParentProtoElementInjector - 1; - return null; - } -} - - -function _createElementBinder(directiveResolver: DirectiveResolver, nestedProtoView: AppProtoView, - elementBinderStack: ElementBinder[], boundElementIndex: number, - distanceToParentBinder: number, distanceToParentPei: number, - beginElementCmd: IBeginElementCmd): ElementBinder { - var parentElementBinder: ElementBinder = null; - var parentProtoElementInjector: ProtoElementInjector = null; - if (distanceToParentBinder > 0) { - parentElementBinder = elementBinderStack[elementBinderStack.length - distanceToParentBinder]; - } - if (isBlank(parentElementBinder)) { - distanceToParentBinder = -1; - } - if (distanceToParentPei > 0) { - var peiBinder = elementBinderStack[elementBinderStack.length - distanceToParentPei]; - if (isPresent(peiBinder)) { - parentProtoElementInjector = peiBinder.protoElementInjector; - } - } - if (isBlank(parentProtoElementInjector)) { - distanceToParentPei = -1; - } - var componentDirectiveProvider: DirectiveProvider = null; - var isEmbeddedTemplate = false; - var directiveProviders: DirectiveProvider[] = - beginElementCmd.directives.map(type => provideDirective(directiveResolver, type)); - if (beginElementCmd instanceof BeginComponentCmd) { - componentDirectiveProvider = directiveProviders[0]; - } else if (beginElementCmd instanceof EmbeddedTemplateCmd) { - isEmbeddedTemplate = true; - } - - var protoElementInjector = null; - // Create a protoElementInjector for any element that either has bindings *or* has one - // or more var- defined *or* for