diff --git a/modules/angular2/src/compiler/change_definition_factory.ts b/modules/angular2/src/compiler/change_definition_factory.ts index 67e26cfd17..aff8ea49db 100644 --- a/modules/angular2/src/compiler/change_definition_factory.ts +++ b/modules/angular2/src/compiler/change_definition_factory.ts @@ -14,7 +14,7 @@ import { ASTWithSource } from 'angular2/src/core/change_detection/change_detection'; -import {NormalizedDirectiveMetadata, TypeMetadata} from './directive_metadata'; +import {CompileDirectiveMetadata, CompileTypeMetadata} from './directive_metadata'; import { TemplateAst, ElementAst, @@ -32,9 +32,10 @@ import { AttrAst, TextAst } from './template_ast'; +import {LifecycleHooks} from 'angular2/src/core/compiler/interfaces'; export function createChangeDetectorDefinitions( - componentType: TypeMetadata, componentStrategy: ChangeDetectionStrategy, + componentType: CompileTypeMetadata, componentStrategy: ChangeDetectionStrategy, genConfig: ChangeDetectorGenConfig, parsedTemplate: TemplateAst[]): ChangeDetectorDefinition[] { var pvVisitors = []; var visitor = new ProtoViewVisitor(null, pvVisitors, componentStrategy); @@ -59,7 +60,9 @@ class ProtoViewVisitor implements TemplateAstVisitor { visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { this.boundElementCount++; - templateVisitAll(this, ast.directives); + for (var i = 0; i < ast.directives.length; i++) { + ast.directives[i].visit(this, i); + } var childVisitor = new ProtoViewVisitor(this, this.allVisitors, ChangeDetectionStrategy.Default); @@ -76,7 +79,7 @@ class ProtoViewVisitor implements TemplateAstVisitor { } templateVisitAll(this, ast.properties, null); templateVisitAll(this, ast.events); - templateVisitAll(this, ast.vars); + templateVisitAll(this, ast.exportAsVars); for (var i = 0; i < ast.directives.length; i++) { ast.directives[i].visit(this, i); } @@ -94,8 +97,8 @@ class ProtoViewVisitor implements TemplateAstVisitor { visitEvent(ast: BoundEventAst, directiveRecord: DirectiveRecord): any { var bindingRecord = isPresent(directiveRecord) ? - BindingRecord.createForHostEvent(ast.handler, ast.name, directiveRecord) : - BindingRecord.createForEvent(ast.handler, ast.name, this.boundElementCount - 1); + BindingRecord.createForHostEvent(ast.handler, ast.fullName, directiveRecord) : + BindingRecord.createForEvent(ast.handler, ast.fullName, this.boundElementCount - 1); this.eventRecords.push(bindingRecord); return null; } @@ -138,17 +141,20 @@ class ProtoViewVisitor implements TemplateAstVisitor { visitDirective(ast: DirectiveAst, directiveIndexAsNumber: number): any { var directiveIndex = new DirectiveIndex(this.boundElementCount - 1, directiveIndexAsNumber); var directiveMetadata = ast.directive; - var changeDetectionMeta = directiveMetadata.changeDetection; var directiveRecord = new DirectiveRecord({ directiveIndex: directiveIndex, - callAfterContentInit: changeDetectionMeta.callAfterContentInit, - callAfterContentChecked: changeDetectionMeta.callAfterContentChecked, - callAfterViewInit: changeDetectionMeta.callAfterViewInit, - callAfterViewChecked: changeDetectionMeta.callAfterViewChecked, - callOnChanges: changeDetectionMeta.callOnChanges, - callDoCheck: changeDetectionMeta.callDoCheck, - callOnInit: changeDetectionMeta.callOnInit, - changeDetection: changeDetectionMeta.changeDetection + callAfterContentInit: + directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.AfterContentInit) !== -1, + callAfterContentChecked: + directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.AfterContentChecked) !== -1, + callAfterViewInit: + directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.AfterViewInit) !== -1, + callAfterViewChecked: + directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.AfterViewChecked) !== -1, + callOnChanges: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1, + callDoCheck: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1, + callOnInit: directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1, + changeDetection: directiveMetadata.changeDetection }); this.directiveRecords.push(directiveRecord); @@ -165,6 +171,7 @@ class ProtoViewVisitor implements TemplateAstVisitor { } templateVisitAll(this, ast.hostProperties, directiveRecord); templateVisitAll(this, ast.hostEvents, directiveRecord); + templateVisitAll(this, ast.exportAsVars); return null; } visitDirectiveProperty(ast: BoundDirectivePropertyAst, directiveRecord: DirectiveRecord): any { @@ -178,7 +185,7 @@ class ProtoViewVisitor implements TemplateAstVisitor { } -function createChangeDefinitions(pvVisitors: ProtoViewVisitor[], componentType: TypeMetadata, +function createChangeDefinitions(pvVisitors: ProtoViewVisitor[], componentType: CompileTypeMetadata, genConfig: ChangeDetectorGenConfig): ChangeDetectorDefinition[] { var pvVariableNames = _collectNestedProtoViewsVariableNames(pvVisitors); return pvVisitors.map(pvVisitor => { @@ -202,6 +209,7 @@ function _collectNestedProtoViewsVariableNames(pvVisitors: ProtoViewVisitor[]): } -function _protoViewId(hostComponentType: TypeMetadata, pvIndex: number, viewType: string): string { +function _protoViewId(hostComponentType: CompileTypeMetadata, pvIndex: number, viewType: string): + string { return `${hostComponentType.name}_${viewType}_${pvIndex}`; } diff --git a/modules/angular2/src/compiler/change_detector_compiler.ts b/modules/angular2/src/compiler/change_detector_compiler.ts index 0123d666a9..c85d565078 100644 --- a/modules/angular2/src/compiler/change_detector_compiler.ts +++ b/modules/angular2/src/compiler/change_detector_compiler.ts @@ -1,5 +1,5 @@ -import {TypeMetadata} from './directive_metadata'; -import {SourceExpression, moduleRef} from './source_module'; +import {CompileTypeMetadata} from './directive_metadata'; +import {SourceExpressions, moduleRef} from './source_module'; import { ChangeDetectorJITGenerator } from 'angular2/src/core/change_detection/change_detection_jit_generator'; @@ -32,7 +32,7 @@ var PREGEN_PROTO_CHANGE_DETECTOR_MODULE = export class ChangeDetectionCompiler { constructor(private _genConfig: ChangeDetectorGenConfig) {} - compileComponentRuntime(componentType: TypeMetadata, strategy: ChangeDetectionStrategy, + compileComponentRuntime(componentType: CompileTypeMetadata, strategy: ChangeDetectionStrategy, parsedTemplate: TemplateAst[]): Function[] { var changeDetectorDefinitions = createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate); @@ -51,8 +51,8 @@ export class ChangeDetectionCompiler { } } - compileComponentCodeGen(componentType: TypeMetadata, strategy: ChangeDetectionStrategy, - parsedTemplate: TemplateAst[]): SourceExpression { + compileComponentCodeGen(componentType: CompileTypeMetadata, strategy: ChangeDetectionStrategy, + parsedTemplate: TemplateAst[]): SourceExpressions { var changeDetectorDefinitions = createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate); var factories = []; @@ -75,7 +75,6 @@ export class ChangeDetectionCompiler { return codegen.generateSource(); } }); - var expression = `[ ${factories.join(',')} ]`; - return new SourceExpression(sourceParts, expression); + return new SourceExpressions(sourceParts, factories); } } diff --git a/modules/angular2/src/compiler/command_compiler.ts b/modules/angular2/src/compiler/command_compiler.ts index 021c1bf7f7..400aa110a1 100644 --- a/modules/angular2/src/compiler/command_compiler.ts +++ b/modules/angular2/src/compiler/command_compiler.ts @@ -1,4 +1,5 @@ -import {isPresent, Type} from 'angular2/src/core/facade/lang'; +import {isPresent, isBlank, Type, isString} from 'angular2/src/core/facade/lang'; +import {SetWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/core/facade/collection'; import { TemplateCmd, text, @@ -25,8 +26,8 @@ import { BoundDirectivePropertyAst, templateVisitAll } from './template_ast'; -import {TypeMetadata, NormalizedDirectiveMetadata} from './directive_metadata'; -import {SourceExpression, moduleRef} from './source_module'; +import {CompileTypeMetadata, CompileDirectiveMetadata} from './directive_metadata'; +import {SourceExpressions, SourceExpression, moduleRef} from './source_module'; import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {shimHostAttribute, shimContentAttribute} from './style_compiler'; @@ -34,21 +35,25 @@ import {escapeSingleQuoteString} from './util'; import {Injectable} from 'angular2/src/core/di'; export var TEMPLATE_COMMANDS_MODULE_REF = moduleRef('angular2/src/core/compiler/template_commands'); +const IMPLICIT_VAR = '%implicit'; @Injectable() export class CommandCompiler { - compileComponentRuntime(component: NormalizedDirectiveMetadata, template: TemplateAst[], + compileComponentRuntime(component: CompileDirectiveMetadata, template: TemplateAst[], + changeDetectorFactories: Function[], componentTemplateFactory: Function): TemplateCmd[] { - var visitor = - new CommandBuilderVisitor(new RuntimeCommandFactory(componentTemplateFactory), component); + var visitor = new CommandBuilderVisitor( + new RuntimeCommandFactory(componentTemplateFactory, changeDetectorFactories), component, 0); templateVisitAll(visitor, template); return visitor.result; } - compileComponentCodeGen(component: NormalizedDirectiveMetadata, template: TemplateAst[], + compileComponentCodeGen(component: CompileDirectiveMetadata, template: TemplateAst[], + changeDetectorFactoryExpressions: string[], componentTemplateFactory: Function): SourceExpression { - var visitor = - new CommandBuilderVisitor(new CodegenCommandFactory(componentTemplateFactory), component); + var visitor = new CommandBuilderVisitor( + new CodegenCommandFactory(componentTemplateFactory, changeDetectorFactoryExpressions), + component, 0); templateVisitAll(visitor, template); var source = `[${visitor.result.join(',')}]`; return new SourceExpression([], source); @@ -58,22 +63,23 @@ export class CommandCompiler { interface CommandFactory { createText(value: string, isBound: boolean, ngContentIndex: number): R; createNgContent(ngContentIndex: number): R; - createBeginElement(name: string, attrNameAndValues: string[], eventNames: string[], - variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], + createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[], + variableNameAndValues: string[], directives: CompileDirectiveMetadata[], isBound: boolean, ngContentIndex: number): R; createEndElement(): R; - createBeginComponent(name: string, attrNameAndValues: string[], eventNames: string[], - variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], + createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[], + variableNameAndValues: string[], directives: CompileDirectiveMetadata[], nativeShadow: boolean, ngContentIndex: number): R; createEndComponent(): R; - createEmbeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[], - directives: NormalizedDirectiveMetadata[], isMerged: boolean, - ngContentIndex: number, children: R[]): R; + createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[], + variableNameAndValues: string[], directives: CompileDirectiveMetadata[], + isMerged: boolean, ngContentIndex: number, children: R[]): R; } class RuntimeCommandFactory implements CommandFactory { - constructor(public componentTemplateFactory: Function) {} - private _mapDirectives(directives: NormalizedDirectiveMetadata[]): Type[] { + constructor(public componentTemplateFactory: Function, + public changeDetectorFactories: Function[]) {} + private _mapDirectives(directives: CompileDirectiveMetadata[]): Type[] { return directives.map(directive => directive.type.runtime); } @@ -81,35 +87,46 @@ class RuntimeCommandFactory implements CommandFactory { return text(value, isBound, ngContentIndex); } createNgContent(ngContentIndex: number): TemplateCmd { return ngContent(ngContentIndex); } - createBeginElement(name: string, attrNameAndValues: string[], eventNames: string[], - variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], + createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[], + variableNameAndValues: string[], directives: CompileDirectiveMetadata[], isBound: boolean, ngContentIndex: number): TemplateCmd { - return beginElement(name, attrNameAndValues, eventNames, variableNameAndValues, + return beginElement(name, attrNameAndValues, eventTargetAndNames, variableNameAndValues, this._mapDirectives(directives), isBound, ngContentIndex); } createEndElement(): TemplateCmd { return endElement(); } - createBeginComponent(name: string, attrNameAndValues: string[], eventNames: string[], - variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], + createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[], + variableNameAndValues: string[], directives: CompileDirectiveMetadata[], nativeShadow: boolean, ngContentIndex: number): TemplateCmd { - return beginComponent(name, attrNameAndValues, eventNames, variableNameAndValues, + return beginComponent(name, attrNameAndValues, eventTargetAndNames, variableNameAndValues, this._mapDirectives(directives), nativeShadow, ngContentIndex, this.componentTemplateFactory(directives[0])); } createEndComponent(): TemplateCmd { return endComponent(); } - createEmbeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[], - directives: NormalizedDirectiveMetadata[], isMerged: boolean, - ngContentIndex: number, children: TemplateCmd[]): TemplateCmd { + createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[], + variableNameAndValues: string[], directives: CompileDirectiveMetadata[], + isMerged: boolean, ngContentIndex: number, + children: TemplateCmd[]): TemplateCmd { return embeddedTemplate(attrNameAndValues, variableNameAndValues, - this._mapDirectives(directives), isMerged, ngContentIndex, children); + this._mapDirectives(directives), isMerged, ngContentIndex, + this.changeDetectorFactories[embeddedTemplateIndex], children); } } -function escapeStringArray(data: string[]): string { - return `[${data.map( value => escapeSingleQuoteString(value)).join(',')}]`; +function escapePrimitiveArray(data: any[]): string { + return `[${data.map( (value) => { + if (isString(value)) { + return escapeSingleQuoteString(value); + } else if (isBlank(value)) { + return 'null'; + } else { + return value; + } + }).join(',')}]`; } class CodegenCommandFactory implements CommandFactory { - constructor(public componentTemplateFactory: Function) {} + constructor(public componentTemplateFactory: Function, + public changeDetectorFactoryExpressions: string[]) {} createText(value: string, isBound: boolean, ngContentIndex: number): string { return `${TEMPLATE_COMMANDS_MODULE_REF}text(${escapeSingleQuoteString(value)}, ${isBound}, ${ngContentIndex})`; @@ -117,26 +134,27 @@ class CodegenCommandFactory implements CommandFactory { createNgContent(ngContentIndex: number): string { return `${TEMPLATE_COMMANDS_MODULE_REF}ngContent(${ngContentIndex})`; } - createBeginElement(name: string, attrNameAndValues: string[], eventNames: string[], - variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], + createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[], + variableNameAndValues: string[], directives: CompileDirectiveMetadata[], isBound: boolean, ngContentIndex: number): string { - return `${TEMPLATE_COMMANDS_MODULE_REF}beginElement(${escapeSingleQuoteString(name)}, ${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(eventNames)}, ${escapeStringArray(variableNameAndValues)}, [${_escapeDirectives(directives).join(',')}], ${isBound}, ${ngContentIndex})`; + return `${TEMPLATE_COMMANDS_MODULE_REF}beginElement(${escapeSingleQuoteString(name)}, ${escapePrimitiveArray(attrNameAndValues)}, ${escapePrimitiveArray(eventTargetAndNames)}, ${escapePrimitiveArray(variableNameAndValues)}, [${_escapeDirectives(directives).join(',')}], ${isBound}, ${ngContentIndex})`; } createEndElement(): string { return `${TEMPLATE_COMMANDS_MODULE_REF}endElement()`; } - createBeginComponent(name: string, attrNameAndValues: string[], eventNames: string[], - variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], + createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[], + variableNameAndValues: string[], directives: CompileDirectiveMetadata[], nativeShadow: boolean, ngContentIndex: number): string { - return `${TEMPLATE_COMMANDS_MODULE_REF}beginComponent(${escapeSingleQuoteString(name)}, ${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(eventNames)}, ${escapeStringArray(variableNameAndValues)}, [${_escapeDirectives(directives).join(',')}], ${nativeShadow}, ${ngContentIndex}, ${this.componentTemplateFactory(directives[0])})`; + return `${TEMPLATE_COMMANDS_MODULE_REF}beginComponent(${escapeSingleQuoteString(name)}, ${escapePrimitiveArray(attrNameAndValues)}, ${escapePrimitiveArray(eventTargetAndNames)}, ${escapePrimitiveArray(variableNameAndValues)}, [${_escapeDirectives(directives).join(',')}], ${nativeShadow}, ${ngContentIndex}, ${this.componentTemplateFactory(directives[0])})`; } createEndComponent(): string { return `${TEMPLATE_COMMANDS_MODULE_REF}endComponent()`; } - createEmbeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[], - directives: NormalizedDirectiveMetadata[], isMerged: boolean, - ngContentIndex: number, children: string[]): string { - return `${TEMPLATE_COMMANDS_MODULE_REF}embeddedTemplate(${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(variableNameAndValues)}, [${_escapeDirectives(directives).join(',')}], ${isMerged}, ${ngContentIndex}, [${children.join(',')}])`; + createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[], + variableNameAndValues: string[], directives: CompileDirectiveMetadata[], + isMerged: boolean, ngContentIndex: number, children: string[]): string { + return `${TEMPLATE_COMMANDS_MODULE_REF}embeddedTemplate(${escapePrimitiveArray(attrNameAndValues)}, ${escapePrimitiveArray(variableNameAndValues)}, ` + + `[${_escapeDirectives(directives).join(',')}], ${isMerged}, ${ngContentIndex}, ${this.changeDetectorFactoryExpressions[embeddedTemplateIndex]}, [${children.join(',')}])`; } } -function _escapeDirectives(directives: NormalizedDirectiveMetadata[]): string[] { +function _escapeDirectives(directives: CompileDirectiveMetadata[]): string[] { return directives.map(directiveType => `${moduleRef(directiveType.type.moduleId)}${directiveType.type.name}`); } @@ -150,10 +168,11 @@ function visitAndReturnContext(visitor: TemplateAstVisitor, asts: TemplateAst[], class CommandBuilderVisitor implements TemplateAstVisitor { result: R[] = []; transitiveNgContentCount: number = 0; - constructor(public commandFactory: CommandFactory, - public component: NormalizedDirectiveMetadata) {} + constructor(public commandFactory: CommandFactory, public component: CompileDirectiveMetadata, + public embeddedTemplateIndex: number) {} - private _readAttrNameAndValues(localComponent: NormalizedDirectiveMetadata, + private _readAttrNameAndValues(localComponent: CompileDirectiveMetadata, + directives: CompileDirectiveMetadata[], attrAsts: TemplateAst[]): string[] { var attrNameAndValues: string[] = visitAndReturnContext(this, attrAsts, []); if (isPresent(localComponent) && @@ -165,7 +184,13 @@ class CommandBuilderVisitor implements TemplateAstVisitor { attrNameAndValues.push(shimContentAttribute(this.component.type.id)); attrNameAndValues.push(''); } - return attrNameAndValues; + directives.forEach(directiveMeta => { + StringMapWrapper.forEach(directiveMeta.hostAttributes, (value, name) => { + attrNameAndValues.push(name); + attrNameAndValues.push(value); + }); + }); + return removeKeyValueArrayDuplicates(attrNameAndValues); } visitNgContent(ast: NgContentAst, context: any): any { @@ -174,43 +199,61 @@ class CommandBuilderVisitor implements TemplateAstVisitor { return null; } visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { - var childVisitor = new CommandBuilderVisitor(this.commandFactory, this.component); + this.embeddedTemplateIndex++; + var childVisitor = + new CommandBuilderVisitor(this.commandFactory, this.component, this.embeddedTemplateIndex); templateVisitAll(childVisitor, ast.children); var isMerged = childVisitor.transitiveNgContentCount > 0; - this.transitiveNgContentCount += childVisitor.transitiveNgContentCount; - var directivesAndEventNames = visitAndReturnContext(this, ast.directives, [[], []]); + var variableNameAndValues = []; + ast.vars.forEach((varAst) => { + variableNameAndValues.push(varAst.name); + variableNameAndValues.push(varAst.value); + }); + var directives = []; + ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => { + directiveAst.visit(this, new DirectiveContext(index, [], [], directives)); + }); this.result.push(this.commandFactory.createEmbeddedTemplate( - this._readAttrNameAndValues(null, ast.attrs), visitAndReturnContext(this, ast.vars, []), - directivesAndEventNames[0], isMerged, ast.ngContentIndex, childVisitor.result)); + this.embeddedTemplateIndex, this._readAttrNameAndValues(null, 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 eventNames = visitAndReturnContext(this, ast.events, []); + var eventTargetAndNames = visitAndReturnContext(this, ast.events, []); + var variableNameAndValues = []; + if (isBlank(component)) { + ast.exportAsVars.forEach((varAst) => { + variableNameAndValues.push(varAst.name); + variableNameAndValues.push(IMPLICIT_VAR); + }); + } var directives = []; - visitAndReturnContext(this, ast.directives, [directives, eventNames]); - var attrNameAndValues = this._readAttrNameAndValues(component, ast.attrs); - var vars = visitAndReturnContext(this, ast.vars, []); + ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => { + directiveAst.visit(this, new DirectiveContext(index, eventTargetAndNames, + variableNameAndValues, directives)); + }); + eventTargetAndNames = removeKeyValueArrayDuplicates(eventTargetAndNames); + + var attrNameAndValues = this._readAttrNameAndValues(component, directives, ast.attrs); if (isPresent(component)) { this.result.push(this.commandFactory.createBeginComponent( - ast.name, attrNameAndValues, eventNames, vars, directives, + ast.name, attrNameAndValues, eventTargetAndNames, variableNameAndValues, directives, component.template.encapsulation === ViewEncapsulation.Native, ast.ngContentIndex)); templateVisitAll(this, ast.children); this.result.push(this.commandFactory.createEndComponent()); } else { - this.result.push(this.commandFactory.createBeginElement(ast.name, attrNameAndValues, - eventNames, vars, directives, - ast.isBound(), ast.ngContentIndex)); + 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, variableNameAndValues: string[]): any { - variableNameAndValues.push(ast.name); - variableNameAndValues.push(ast.value); - return null; - } + visitVariable(ast: VariableAst, ctx: any): any { return null; } visitAttr(ast: AttrAst, attrNameAndValues: string[]): any { attrNameAndValues.push(ast.name); attrNameAndValues.push(ast.value); @@ -224,15 +267,42 @@ class CommandBuilderVisitor implements TemplateAstVisitor { this.result.push(this.commandFactory.createText(ast.value, false, ast.ngContentIndex)); return null; } - visitDirective(ast: DirectiveAst, directivesAndEventNames: any[][]): any { - directivesAndEventNames[0].push(ast.directive); - templateVisitAll(this, ast.hostEvents, directivesAndEventNames[1]); + 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, eventNames: string[]): any { - eventNames.push(ast.getFullName()); + 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; +} + +class DirectiveContext { + constructor(public index: number, public eventTargetAndNames: string[], + public targetVariableNameAndValues: any[], + public targetDirectives: CompileDirectiveMetadata[]) {} +} \ No newline at end of file diff --git a/modules/angular2/src/compiler/compiler.ts b/modules/angular2/src/compiler/compiler.ts index 6f345c24a0..6d93feb685 100644 --- a/modules/angular2/src/compiler/compiler.ts +++ b/modules/angular2/src/compiler/compiler.ts @@ -1,9 +1,7 @@ export {TemplateCompiler} from './template_compiler'; export { - DirectiveMetadata, - TypeMetadata, - TemplateMetadata, - ChangeDetectionMetadata, - INormalizedDirectiveMetadata + CompileDirectiveMetadata, + CompileTypeMetadata, + CompileTemplateMetadata } from './directive_metadata'; export {SourceModule, SourceWithImports} from './source_module'; diff --git a/modules/angular2/src/compiler/directive_metadata.ts b/modules/angular2/src/compiler/directive_metadata.ts index 6883825203..82d37b3185 100644 --- a/modules/angular2/src/compiler/directive_metadata.ts +++ b/modules/angular2/src/compiler/directive_metadata.ts @@ -1,12 +1,27 @@ -import {isPresent, normalizeBool, serializeEnum, Type} from 'angular2/src/core/facade/lang'; +import { + isPresent, + isBlank, + normalizeBool, + serializeEnum, + Type, + RegExpWrapper, + StringWrapper +} from 'angular2/src/core/facade/lang'; +import {StringMapWrapper} from 'angular2/src/core/facade/collection'; import { ChangeDetectionStrategy, - changeDetectionStrategyFromJson + CHANGE_DECTION_STRATEGY_VALUES } from 'angular2/src/core/change_detection/change_detection'; -import {ViewEncapsulation, viewEncapsulationFromJson} from 'angular2/src/core/render/api'; +import {ViewEncapsulation, VIEW_ENCAPSULATION_VALUES} from 'angular2/src/core/render/api'; import {CssSelector} from 'angular2/src/core/render/dom/compiler/selector'; +import {splitAtColon} from './util'; +import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/compiler/interfaces'; -export class TypeMetadata { +// group 1: "property" from "[property]" +// group 2: "event" from "(event)" +var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g; + +export class CompileTypeMetadata { id: number; runtime: Type; name: string; @@ -19,8 +34,9 @@ export class TypeMetadata { this.moduleId = moduleId; } - static fromJson(data: StringMap): TypeMetadata { - return new TypeMetadata({id: data['id'], name: data['name'], moduleId: data['moduleId']}); + static fromJson(data: StringMap): CompileTypeMetadata { + return new CompileTypeMetadata( + {id: data['id'], name: data['name'], moduleId: data['moduleId']}); } toJson(): StringMap { @@ -33,169 +49,39 @@ export class TypeMetadata { } } -export class ChangeDetectionMetadata { - changeDetection: ChangeDetectionStrategy; - properties: string[]; - events: string[]; - hostListeners: StringMap; - hostProperties: StringMap; - callAfterContentInit: boolean; - callAfterContentChecked: boolean; - callAfterViewInit: boolean; - callAfterViewChecked: boolean; - callOnChanges: boolean; - callDoCheck: boolean; - callOnInit: boolean; - constructor({changeDetection, properties, events, hostListeners, hostProperties, - callAfterContentInit, callAfterContentChecked, callAfterViewInit, - callAfterViewChecked, callOnChanges, callDoCheck, callOnInit}: { - changeDetection?: ChangeDetectionStrategy, - properties?: string[], - events?: string[], - hostListeners?: StringMap, - hostProperties?: StringMap, - callAfterContentInit?: boolean, - callAfterContentChecked?: boolean, - callAfterViewInit?: boolean, - callAfterViewChecked?: boolean, - callOnChanges?: boolean, - callDoCheck?: boolean, - callOnInit?: boolean - } = {}) { - this.changeDetection = changeDetection; - this.properties = isPresent(properties) ? properties : []; - this.events = isPresent(events) ? events : []; - this.hostListeners = isPresent(hostListeners) ? hostListeners : {}; - this.hostProperties = isPresent(hostProperties) ? hostProperties : {}; - this.callAfterContentInit = normalizeBool(callAfterContentInit); - this.callAfterContentChecked = normalizeBool(callAfterContentChecked); - this.callAfterViewInit = normalizeBool(callAfterViewInit); - this.callAfterViewChecked = normalizeBool(callAfterViewChecked); - this.callOnChanges = normalizeBool(callOnChanges); - this.callDoCheck = normalizeBool(callDoCheck); - this.callOnInit = normalizeBool(callOnInit); - } - - static fromJson(data: StringMap): ChangeDetectionMetadata { - return new ChangeDetectionMetadata({ - changeDetection: isPresent(data['changeDetection']) ? - changeDetectionStrategyFromJson(data['changeDetection']) : - data['changeDetection'], - properties: data['properties'], - events: data['events'], - hostListeners: data['hostListeners'], - hostProperties: data['hostProperties'], - callAfterContentInit: data['callAfterContentInit'], - callAfterContentChecked: data['callAfterContentChecked'], - callAfterViewInit: data['callAfterViewInit'], - callAfterViewChecked: data['callAfterViewChecked'], - callOnChanges: data['callOnChanges'], - callDoCheck: data['callDoCheck'], - callOnInit: data['callOnInit'] - }); - } - - toJson(): StringMap { - return { - 'changeDetection': isPresent(this.changeDetection) ? serializeEnum(this.changeDetection) : - this.changeDetection, - 'properties': this.properties, - 'events': this.events, - 'hostListeners': this.hostListeners, - 'hostProperties': this.hostProperties, - 'callAfterContentInit': this.callAfterContentInit, - 'callAfterContentChecked': this.callAfterContentChecked, - 'callAfterViewInit': this.callAfterViewInit, - 'callAfterViewChecked': this.callAfterViewChecked, - 'callOnChanges': this.callOnChanges, - 'callDoCheck': this.callDoCheck, - 'callOnInit': this.callOnInit - }; - } -} - -export class TemplateMetadata { +export class CompileTemplateMetadata { encapsulation: ViewEncapsulation; template: string; templateUrl: string; styles: string[]; styleUrls: string[]; - hostAttributes: StringMap; - constructor({encapsulation, template, templateUrl, styles, styleUrls, hostAttributes}: { + ngContentSelectors: string[]; + constructor({encapsulation, template, templateUrl, styles, styleUrls, ngContentSelectors}: { encapsulation?: ViewEncapsulation, template?: string, templateUrl?: string, styles?: string[], styleUrls?: string[], - hostAttributes?: StringMap + ngContentSelectors?: string[] } = {}) { - this.encapsulation = isPresent(encapsulation) ? encapsulation : ViewEncapsulation.None; + this.encapsulation = encapsulation; this.template = template; this.templateUrl = templateUrl; this.styles = isPresent(styles) ? styles : []; this.styleUrls = isPresent(styleUrls) ? styleUrls : []; - this.hostAttributes = isPresent(hostAttributes) ? hostAttributes : {}; - } -} - - -export class DirectiveMetadata { - type: TypeMetadata; - isComponent: boolean; - dynamicLoadable: boolean; - selector: string; - changeDetection: ChangeDetectionMetadata; - template: TemplateMetadata; - constructor({type, isComponent, dynamicLoadable, selector, changeDetection, template}: { - type?: TypeMetadata, - isComponent?: boolean, - dynamicLoadable?: boolean, - selector?: string, - changeDetection?: ChangeDetectionMetadata, - template?: TemplateMetadata - } = {}) { - this.type = type; - this.isComponent = normalizeBool(isComponent); - this.dynamicLoadable = normalizeBool(dynamicLoadable); - this.selector = selector; - this.changeDetection = changeDetection; - this.template = template; - } -} - -export class NormalizedTemplateMetadata { - encapsulation: ViewEncapsulation; - template: string; - styles: string[]; - styleAbsUrls: string[]; - ngContentSelectors: string[]; - hostAttributes: StringMap; - constructor({encapsulation, template, styles, styleAbsUrls, ngContentSelectors, hostAttributes}: { - encapsulation?: ViewEncapsulation, - template?: string, - styles?: string[], - styleAbsUrls?: string[], - ngContentSelectors?: string[], - hostAttributes?: StringMap - } = {}) { - this.encapsulation = encapsulation; - this.template = template; - this.styles = styles; - this.styleAbsUrls = styleAbsUrls; - this.ngContentSelectors = ngContentSelectors; - this.hostAttributes = hostAttributes; + this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : []; } - static fromJson(data: StringMap): NormalizedTemplateMetadata { - return new NormalizedTemplateMetadata({ + static fromJson(data: StringMap): CompileTemplateMetadata { + return new CompileTemplateMetadata({ encapsulation: isPresent(data['encapsulation']) ? - viewEncapsulationFromJson(data['encapsulation']) : + VIEW_ENCAPSULATION_VALUES[data['encapsulation']] : data['encapsulation'], template: data['template'], + templateUrl: data['templateUrl'], styles: data['styles'], - styleAbsUrls: data['styleAbsUrls'], - ngContentSelectors: data['ngContentSelectors'], - hostAttributes: data['hostAttributes'] + styleUrls: data['styleUrls'], + ngContentSelectors: data['ngContentSelectors'] }); } @@ -204,52 +90,142 @@ export class NormalizedTemplateMetadata { 'encapsulation': isPresent(this.encapsulation) ? serializeEnum(this.encapsulation) : this.encapsulation, 'template': this.template, + 'templateUrl': this.templateUrl, 'styles': this.styles, - 'styleAbsUrls': this.styleAbsUrls, - 'ngContentSelectors': this.ngContentSelectors, - 'hostAttributes': this.hostAttributes + 'styleUrls': this.styleUrls, + 'ngContentSelectors': this.ngContentSelectors }; } } -export interface INormalizedDirectiveMetadata {} - -export class NormalizedDirectiveMetadata implements INormalizedDirectiveMetadata { - type: TypeMetadata; - isComponent: boolean; - dynamicLoadable: boolean; - selector: string; - changeDetection: ChangeDetectionMetadata; - template: NormalizedTemplateMetadata; - constructor({type, isComponent, dynamicLoadable, selector, changeDetection, template}: { - id?: number, - type?: TypeMetadata, +export class CompileDirectiveMetadata { + static create({type, isComponent, dynamicLoadable, selector, exportAs, changeDetection, + properties, events, host, lifecycleHooks, template}: { + type?: CompileTypeMetadata, isComponent?: boolean, dynamicLoadable?: boolean, selector?: string, - changeDetection?: ChangeDetectionMetadata, - template?: NormalizedTemplateMetadata + exportAs?: string, + changeDetection?: ChangeDetectionStrategy, + properties?: string[], + events?: string[], + host?: StringMap, + lifecycleHooks?: LifecycleHooks[], + template?: CompileTemplateMetadata + } = {}): CompileDirectiveMetadata { + var hostListeners = {}; + var hostProperties = {}; + var hostAttributes = {}; + if (isPresent(host)) { + StringMapWrapper.forEach(host, (value: string, key: string) => { + var matches = RegExpWrapper.firstMatch(HOST_REG_EXP, key); + if (isBlank(matches)) { + hostAttributes[key] = value; + } else if (isPresent(matches[1])) { + hostProperties[matches[1]] = value; + } else if (isPresent(matches[2])) { + hostListeners[matches[2]] = value; + } + }); + } + var propsMap = {}; + if (isPresent(properties)) { + properties.forEach((bindConfig: string) => { + // canonical syntax: `dirProp: elProp` + // if there is no `:`, use dirProp = elProp + var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]); + propsMap[parts[0]] = parts[1]; + }); + } + var eventsMap = {}; + if (isPresent(events)) { + events.forEach((bindConfig: string) => { + // canonical syntax: `dirProp: elProp` + // if there is no `:`, use dirProp = elProp + var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]); + eventsMap[parts[0]] = parts[1]; + }); + } + + return new CompileDirectiveMetadata({ + type: type, + isComponent: normalizeBool(isComponent), + dynamicLoadable: normalizeBool(dynamicLoadable), + selector: selector, + exportAs: exportAs, + changeDetection: changeDetection, + properties: propsMap, + events: eventsMap, + hostListeners: hostListeners, + hostProperties: hostProperties, + hostAttributes: hostAttributes, + lifecycleHooks: isPresent(lifecycleHooks) ? lifecycleHooks : [], template: template + }); + } + + type: CompileTypeMetadata; + isComponent: boolean; + dynamicLoadable: boolean; + selector: string; + exportAs: string; + changeDetection: ChangeDetectionStrategy; + properties: StringMap; + events: StringMap; + hostListeners: StringMap; + hostProperties: StringMap; + hostAttributes: StringMap; + lifecycleHooks: LifecycleHooks[]; + template: CompileTemplateMetadata; + constructor({type, isComponent, dynamicLoadable, selector, exportAs, changeDetection, properties, + events, hostListeners, hostProperties, hostAttributes, lifecycleHooks, template}: { + type?: CompileTypeMetadata, + isComponent?: boolean, + dynamicLoadable?: boolean, + selector?: string, + exportAs?: string, + changeDetection?: ChangeDetectionStrategy, + properties?: StringMap, + events?: StringMap, + hostListeners?: StringMap, + hostProperties?: StringMap, + hostAttributes?: StringMap, + lifecycleHooks?: LifecycleHooks[], + template?: CompileTemplateMetadata } = {}) { this.type = type; - this.isComponent = normalizeBool(isComponent); - this.dynamicLoadable = normalizeBool(dynamicLoadable); + this.isComponent = isComponent; + this.dynamicLoadable = dynamicLoadable; this.selector = selector; + this.exportAs = exportAs; this.changeDetection = changeDetection; + this.properties = properties; + this.events = events; + this.hostListeners = hostListeners; + this.hostProperties = hostProperties; + this.hostAttributes = hostAttributes; + this.lifecycleHooks = lifecycleHooks; this.template = template; } - static fromJson(data: StringMap): NormalizedDirectiveMetadata { - return new NormalizedDirectiveMetadata({ + static fromJson(data: StringMap): CompileDirectiveMetadata { + return new CompileDirectiveMetadata({ isComponent: data['isComponent'], dynamicLoadable: data['dynamicLoadable'], selector: data['selector'], - type: isPresent(data['type']) ? TypeMetadata.fromJson(data['type']) : data['type'], + exportAs: data['exportAs'], + type: isPresent(data['type']) ? CompileTypeMetadata.fromJson(data['type']) : data['type'], changeDetection: isPresent(data['changeDetection']) ? - ChangeDetectionMetadata.fromJson(data['changeDetection']) : + CHANGE_DECTION_STRATEGY_VALUES[data['changeDetection']] : data['changeDetection'], - template: - isPresent(data['template']) ? NormalizedTemplateMetadata.fromJson(data['template']) : - data['template'] + properties: data['properties'], + events: data['events'], + hostListeners: data['hostListeners'], + hostProperties: data['hostProperties'], + hostAttributes: data['hostAttributes'], + lifecycleHooks: + (data['lifecycleHooks']).map(hookValue => LIFECYCLE_HOOKS_VALUES[hookValue]), + template: isPresent(data['template']) ? CompileTemplateMetadata.fromJson(data['template']) : + data['template'] }); } @@ -258,45 +234,38 @@ export class NormalizedDirectiveMetadata implements INormalizedDirectiveMetadata 'isComponent': this.isComponent, 'dynamicLoadable': this.dynamicLoadable, 'selector': this.selector, + 'exportAs': this.exportAs, 'type': isPresent(this.type) ? this.type.toJson() : this.type, - 'changeDetection': - isPresent(this.changeDetection) ? this.changeDetection.toJson() : this.changeDetection, + 'changeDetection': isPresent(this.changeDetection) ? serializeEnum(this.changeDetection) : + this.changeDetection, + 'properties': this.properties, + 'events': this.events, + 'hostListeners': this.hostListeners, + 'hostProperties': this.hostProperties, + 'hostAttributes': this.hostAttributes, + 'lifecycleHooks': this.lifecycleHooks.map(hook => serializeEnum(hook)), 'template': isPresent(this.template) ? this.template.toJson() : this.template }; } } -export function createHostComponentMeta(componentType: TypeMetadata, componentSelector: string): - NormalizedDirectiveMetadata { +export function createHostComponentMeta(componentType: CompileTypeMetadata, + componentSelector: string): CompileDirectiveMetadata { var template = CssSelector.parse(componentSelector)[0].getMatchingElementTemplate(); - return new NormalizedDirectiveMetadata({ - type: new TypeMetadata({ + return CompileDirectiveMetadata.create({ + type: new CompileTypeMetadata({ runtime: Object, id: (componentType.id * -1) - 1, name: `Host${componentType.name}`, moduleId: componentType.moduleId }), - template: new NormalizedTemplateMetadata({ - template: template, - styles: [], - styleAbsUrls: [], - hostAttributes: {}, - ngContentSelectors: [] - }), - changeDetection: new ChangeDetectionMetadata({ - changeDetection: ChangeDetectionStrategy.Default, - properties: [], - events: [], - hostListeners: {}, - hostProperties: {}, - callAfterContentInit: false, - callAfterContentChecked: false, - callAfterViewInit: false, - callAfterViewChecked: false, - callOnChanges: false, - callDoCheck: false, - callOnInit: false - }), + template: new CompileTemplateMetadata( + {template: template, templateUrl: '', styles: [], styleUrls: [], ngContentSelectors: []}), + changeDetection: ChangeDetectionStrategy.Default, + properties: [], + events: [], + host: {}, + lifecycleHooks: [], isComponent: true, dynamicLoadable: false, selector: '*' diff --git a/modules/angular2/src/compiler/runtime_metadata.ts b/modules/angular2/src/compiler/runtime_metadata.ts index ca17cbedc6..4c81a230c5 100644 --- a/modules/angular2/src/compiler/runtime_metadata.ts +++ b/modules/angular2/src/compiler/runtime_metadata.ts @@ -8,14 +8,14 @@ import { RegExpWrapper } from 'angular2/src/core/facade/lang'; import {BaseException} from 'angular2/src/core/facade/exceptions'; -import {MapWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection'; +import {MapWrapper, StringMapWrapper, ListWrapper} from 'angular2/src/core/facade/collection'; import * as cpl from './directive_metadata'; import * as dirAnn from 'angular2/src/core/metadata/directives'; import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; import {ViewResolver} from 'angular2/src/core/compiler/view_resolver'; import {ViewMetadata} from 'angular2/src/core/metadata/view'; import {hasLifecycleHook} from 'angular2/src/core/compiler/directive_lifecycle_reflector'; -import {LifecycleHooks} from 'angular2/src/core/compiler/interfaces'; +import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/compiler/interfaces'; import {reflector} from 'angular2/src/core/reflection/reflection'; import {Injectable} from 'angular2/src/core/di'; @@ -26,81 +26,55 @@ var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g; @Injectable() export class RuntimeMetadataResolver { private _directiveCounter = 0; - private _cache: Map = new Map(); + private _cache: Map = new Map(); constructor(private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver) {} - getMetadata(directiveType: Type): cpl.DirectiveMetadata { + getMetadata(directiveType: Type): cpl.CompileDirectiveMetadata { var meta = this._cache.get(directiveType); if (isBlank(meta)) { var directiveAnnotation = this._directiveResolver.resolve(directiveType); var moduleId = calcModuleId(directiveType, directiveAnnotation); var templateMeta = null; - var hostListeners = {}; - var hostProperties = {}; - var hostAttributes = {}; var changeDetectionStrategy = null; - var dynamicLoadable: boolean = false; - if (isPresent(directiveAnnotation.host)) { - StringMapWrapper.forEach(directiveAnnotation.host, (value: string, key: string) => { - var matches = RegExpWrapper.firstMatch(HOST_REG_EXP, key); - if (isBlank(matches)) { - hostAttributes[key] = value; - } else if (isPresent(matches[1])) { - hostProperties[matches[1]] = value; - } else if (isPresent(matches[2])) { - hostListeners[matches[2]] = value; - } - }); - } if (directiveAnnotation instanceof dirAnn.ComponentMetadata) { var compAnnotation = directiveAnnotation; var viewAnnotation = this._viewResolver.resolve(directiveType); - templateMeta = new cpl.TemplateMetadata({ + templateMeta = new cpl.CompileTemplateMetadata({ encapsulation: viewAnnotation.encapsulation, template: viewAnnotation.template, templateUrl: viewAnnotation.templateUrl, styles: viewAnnotation.styles, - styleUrls: viewAnnotation.styleUrls, - hostAttributes: hostAttributes + styleUrls: viewAnnotation.styleUrls }); changeDetectionStrategy = compAnnotation.changeDetection; - dynamicLoadable = compAnnotation.dynamicLoadable; } - meta = new cpl.DirectiveMetadata({ + meta = cpl.CompileDirectiveMetadata.create({ selector: directiveAnnotation.selector, + exportAs: directiveAnnotation.exportAs, isComponent: isPresent(templateMeta), - dynamicLoadable: dynamicLoadable, - type: new cpl.TypeMetadata({ + dynamicLoadable: true, + type: new cpl.CompileTypeMetadata({ id: this._directiveCounter++, name: stringify(directiveType), moduleId: moduleId, runtime: directiveType }), template: templateMeta, - changeDetection: new cpl.ChangeDetectionMetadata({ - changeDetection: changeDetectionStrategy, - properties: directiveAnnotation.properties, - events: directiveAnnotation.events, - hostListeners: hostListeners, - hostProperties: hostProperties, - callAfterContentInit: hasLifecycleHook(LifecycleHooks.AfterContentInit, directiveType), - callAfterContentChecked: - hasLifecycleHook(LifecycleHooks.AfterContentChecked, directiveType), - callAfterViewInit: hasLifecycleHook(LifecycleHooks.AfterViewInit, directiveType), - callAfterViewChecked: hasLifecycleHook(LifecycleHooks.AfterViewChecked, directiveType), - callOnChanges: hasLifecycleHook(LifecycleHooks.OnChanges, directiveType), - callDoCheck: hasLifecycleHook(LifecycleHooks.DoCheck, directiveType), - callOnInit: hasLifecycleHook(LifecycleHooks.OnInit, directiveType), - }) + changeDetection: changeDetectionStrategy, + properties: directiveAnnotation.properties, + events: directiveAnnotation.events, + host: directiveAnnotation.host, + lifecycleHooks: ListWrapper.filter(LIFECYCLE_HOOKS_VALUES, + hook => hasLifecycleHook(hook, directiveType)) }); this._cache.set(directiveType, meta); } return meta; } - getViewDirectivesMetadata(component: Type): cpl.DirectiveMetadata[] { + getViewDirectivesMetadata(component: Type): cpl.CompileDirectiveMetadata[] { var view = this._viewResolver.resolve(component); var directives = flattenDirectives(view); for (var i = 0; i < directives.length; i++) { @@ -113,8 +87,9 @@ export class RuntimeMetadataResolver { } } -function removeDuplicatedDirectives(directives: cpl.DirectiveMetadata[]): cpl.DirectiveMetadata[] { - var directivesMap: Map = new Map(); +function removeDuplicatedDirectives(directives: cpl.CompileDirectiveMetadata[]): + cpl.CompileDirectiveMetadata[] { + var directivesMap: Map = new Map(); directives.forEach((dirMeta) => { directivesMap.set(dirMeta.type.id, dirMeta); }); return MapWrapper.values(directivesMap); } diff --git a/modules/angular2/src/compiler/source_module.ts b/modules/angular2/src/compiler/source_module.ts index dab7f3a95f..c68d80cfe1 100644 --- a/modules/angular2/src/compiler/source_module.ts +++ b/modules/angular2/src/compiler/source_module.ts @@ -35,6 +35,10 @@ export class SourceExpression { constructor(public declarations: string[], public expression: string) {} } +export class SourceExpressions { + constructor(public declarations: string[], public expressions: string[]) {} +} + export class SourceWithImports { constructor(public source: string, public imports: string[][]) {} } diff --git a/modules/angular2/src/compiler/style_compiler.ts b/modules/angular2/src/compiler/style_compiler.ts index 9f17f12a3b..5580eb4a3e 100644 --- a/modules/angular2/src/compiler/style_compiler.ts +++ b/modules/angular2/src/compiler/style_compiler.ts @@ -1,4 +1,4 @@ -import {TypeMetadata, NormalizedDirectiveMetadata} from './directive_metadata'; +import {CompileTypeMetadata, CompileDirectiveMetadata} from './directive_metadata'; import {SourceModule, SourceExpression, moduleRef} from './source_module'; import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {XHR} from 'angular2/src/core/render/xhr'; @@ -29,16 +29,16 @@ export class StyleCompiler { constructor(private _xhr: XHR, private _urlResolver: UrlResolver) {} - compileComponentRuntime(component: NormalizedDirectiveMetadata): Promise { + compileComponentRuntime(component: CompileDirectiveMetadata): Promise { var styles = component.template.styles; - var styleAbsUrls = component.template.styleAbsUrls; + var styleAbsUrls = component.template.styleUrls; return this._loadStyles(styles, styleAbsUrls, component.template.encapsulation === ViewEncapsulation.Emulated) .then(styles => styles.map(style => StringWrapper.replaceAll(style, COMPONENT_REGEX, `${component.type.id}`))); } - compileComponentCodeGen(component: NormalizedDirectiveMetadata): SourceExpression { + compileComponentCodeGen(component: CompileDirectiveMetadata): SourceExpression { var shim = component.template.encapsulation === ViewEncapsulation.Emulated; var suffix; if (shim) { @@ -48,7 +48,7 @@ export class StyleCompiler { } else { suffix = ''; } - return this._styleCodeGen(component.template.styles, component.template.styleAbsUrls, shim, + return this._styleCodeGen(component.template.styles, component.template.styleUrls, shim, suffix); } @@ -62,6 +62,8 @@ export class StyleCompiler { ]; } + clearCache() { this._styleCache.clear(); } + private _loadStyles(plainStyles: string[], absUrls: string[], encapsulate: boolean): Promise { var promises = absUrls.map((absUrl) => { diff --git a/modules/angular2/src/compiler/template_ast.ts b/modules/angular2/src/compiler/template_ast.ts index f8ad04ed06..d752383a41 100644 --- a/modules/angular2/src/compiler/template_ast.ts +++ b/modules/angular2/src/compiler/template_ast.ts @@ -1,6 +1,6 @@ import {AST} from 'angular2/src/core/change_detection/change_detection'; import {isPresent} from 'angular2/src/core/facade/lang'; -import {NormalizedDirectiveMetadata} from './directive_metadata'; +import {CompileDirectiveMetadata} from './directive_metadata'; export interface TemplateAst { sourceInfo: string; @@ -38,7 +38,7 @@ export class BoundEventAst implements TemplateAst { visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitEvent(this, context); } - getFullName(): string { + get fullName() { if (isPresent(this.target)) { return `${this.target}:${this.name}`; } else { @@ -57,7 +57,7 @@ export class VariableAst implements TemplateAst { export class ElementAst implements TemplateAst { constructor(public name: string, public attrs: AttrAst[], public properties: BoundElementPropertyAst[], public events: BoundEventAst[], - public vars: VariableAst[], public directives: DirectiveAst[], + public exportAsVars: VariableAst[], public directives: DirectiveAst[], public children: TemplateAst[], public ngContentIndex: number, public sourceInfo: string) {} visit(visitor: TemplateAstVisitor, context: any): any { @@ -65,11 +65,11 @@ export class ElementAst implements TemplateAst { } isBound(): boolean { - return (this.properties.length > 0 || this.events.length > 0 || this.vars.length > 0 || + return (this.properties.length > 0 || this.events.length > 0 || this.exportAsVars.length > 0 || this.directives.length > 0); } - getComponent(): NormalizedDirectiveMetadata { + getComponent(): CompileDirectiveMetadata { return this.directives.length > 0 && this.directives[0].directive.isComponent ? this.directives[0].directive : null; @@ -94,10 +94,10 @@ export class BoundDirectivePropertyAst implements TemplateAst { } export class DirectiveAst implements TemplateAst { - constructor(public directive: NormalizedDirectiveMetadata, + constructor(public directive: CompileDirectiveMetadata, public properties: BoundDirectivePropertyAst[], public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[], - public sourceInfo: string) {} + public exportAsVars: VariableAst[], public sourceInfo: string) {} visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitDirective(this, context); } diff --git a/modules/angular2/src/compiler/template_compiler.ts b/modules/angular2/src/compiler/template_compiler.ts index 0fdf7bdfde..bc7829d46f 100644 --- a/modules/angular2/src/compiler/template_compiler.ts +++ b/modules/angular2/src/compiler/template_compiler.ts @@ -1,16 +1,12 @@ import {Type, Json, isBlank, stringify} from 'angular2/src/core/facade/lang'; -import {BaseException} from 'angular2/src/core/facade/exceptions'; import {ListWrapper, SetWrapper} from 'angular2/src/core/facade/collection'; import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async'; import {CompiledTemplate, TemplateCmd} from 'angular2/src/core/compiler/template_commands'; import { createHostComponentMeta, - DirectiveMetadata, - INormalizedDirectiveMetadata, - NormalizedDirectiveMetadata, - TypeMetadata, - ChangeDetectionMetadata, - NormalizedTemplateMetadata + CompileDirectiveMetadata, + CompileTypeMetadata, + CompileTemplateMetadata } from './directive_metadata'; import {TemplateAst} from './template_ast'; import {Injectable} from 'angular2/src/core/di'; @@ -36,7 +32,8 @@ export class TemplateCompiler { private _commandCompiler: CommandCompiler, private _cdCompiler: ChangeDetectionCompiler) {} - normalizeDirectiveMetadata(directive: DirectiveMetadata): Promise { + normalizeDirectiveMetadata(directive: + CompileDirectiveMetadata): Promise { var normalizedTemplatePromise; if (directive.isComponent) { normalizedTemplatePromise = @@ -45,49 +42,58 @@ export class TemplateCompiler { normalizedTemplatePromise = PromiseWrapper.resolve(null); } return normalizedTemplatePromise.then( - (normalizedTemplate) => new NormalizedDirectiveMetadata({ - selector: directive.selector, - dynamicLoadable: directive.dynamicLoadable, - isComponent: directive.isComponent, + (normalizedTemplate) => new CompileDirectiveMetadata({ type: directive.type, - changeDetection: directive.changeDetection, template: normalizedTemplate + isComponent: directive.isComponent, + dynamicLoadable: directive.dynamicLoadable, + selector: directive.selector, + exportAs: directive.exportAs, + changeDetection: directive.changeDetection, + properties: directive.properties, + events: directive.events, + hostListeners: directive.hostListeners, + hostProperties: directive.hostProperties, + hostAttributes: directive.hostAttributes, + lifecycleHooks: directive.lifecycleHooks, template: normalizedTemplate })); } - serializeDirectiveMetadata(metadata: INormalizedDirectiveMetadata): string { - return Json.stringify((metadata).toJson()); + serializeDirectiveMetadata(metadata: CompileDirectiveMetadata): string { + return Json.stringify(metadata.toJson()); } - deserializeDirectiveMetadata(data: string): INormalizedDirectiveMetadata { - return NormalizedDirectiveMetadata.fromJson(Json.parse(data)); + deserializeDirectiveMetadata(data: string): CompileDirectiveMetadata { + return CompileDirectiveMetadata.fromJson(Json.parse(data)); } compileHostComponentRuntime(type: Type): Promise { - var compMeta: DirectiveMetadata = this._runtimeMetadataResolver.getMetadata(type); - if (isBlank(compMeta) || !compMeta.isComponent || !compMeta.dynamicLoadable) { - throw new BaseException( - `Could not compile '${stringify(type)}' because it is not dynamically loadable.`); - } - var hostMeta: NormalizedDirectiveMetadata = + var compMeta: CompileDirectiveMetadata = this._runtimeMetadataResolver.getMetadata(type); + var hostMeta: CompileDirectiveMetadata = createHostComponentMeta(compMeta.type, compMeta.selector); this._compileComponentRuntime(hostMeta, [compMeta], new Set()); return this._compiledTemplateDone.get(hostMeta.type.id); } - private _compileComponentRuntime(compMeta: NormalizedDirectiveMetadata, - viewDirectives: DirectiveMetadata[], + clearCache() { + this._styleCompiler.clearCache(); + this._compiledTemplateCache.clear(); + this._compiledTemplateDone.clear(); + } + + private _compileComponentRuntime(compMeta: CompileDirectiveMetadata, + viewDirectives: CompileDirectiveMetadata[], compilingComponentIds: Set): CompiledTemplate { var compiledTemplate = this._compiledTemplateCache.get(compMeta.type.id); var done = this._compiledTemplateDone.get(compMeta.type.id); if (isBlank(compiledTemplate)) { var styles; - var changeDetectorFactories; + var changeDetectorFactory; var commands; compiledTemplate = - new CompiledTemplate(compMeta.type.id, () => [changeDetectorFactories, commands, styles]); + new CompiledTemplate(compMeta.type.id, () => [changeDetectorFactory, commands, styles]); this._compiledTemplateCache.set(compMeta.type.id, compiledTemplate); compilingComponentIds.add(compMeta.type.id); - done = PromiseWrapper.all([this._styleCompiler.compileComponentRuntime(compMeta)].concat( + done = PromiseWrapper.all([this._styleCompiler.compileComponentRuntime(compMeta)].concat( viewDirectives.map( dirMeta => this.normalizeDirectiveMetadata(dirMeta)))) .then((stylesAndNormalizedViewDirMetas: any[]) => { @@ -96,10 +102,12 @@ export class TemplateCompiler { var parsedTemplate = this._templateParser.parse( compMeta.template.template, normalizedViewDirMetas, compMeta.type.name); - changeDetectorFactories = this._cdCompiler.compileComponentRuntime( - compMeta.type, compMeta.changeDetection.changeDetection, parsedTemplate); + var changeDetectorFactories = this._cdCompiler.compileComponentRuntime( + compMeta.type, compMeta.changeDetection, parsedTemplate); + changeDetectorFactory = changeDetectorFactories[0]; styles = stylesAndNormalizedViewDirMetas[0]; commands = this._compileCommandsRuntime(compMeta, parsedTemplate, + changeDetectorFactories, compilingComponentIds, childPromises); return PromiseWrapper.all(childPromises); }) @@ -112,12 +120,14 @@ export class TemplateCompiler { return compiledTemplate; } - private _compileCommandsRuntime(compMeta: NormalizedDirectiveMetadata, - parsedTemplate: TemplateAst[], compilingComponentIds: Set, + private _compileCommandsRuntime(compMeta: CompileDirectiveMetadata, parsedTemplate: TemplateAst[], + changeDetectorFactories: Function[], + compilingComponentIds: Set, childPromises: Promise[]): TemplateCmd[] { return this._commandCompiler.compileComponentRuntime( - compMeta, parsedTemplate, (childComponentDir: NormalizedDirectiveMetadata) => { - var childViewDirectives: DirectiveMetadata[] = + compMeta, parsedTemplate, changeDetectorFactories, + (childComponentDir: CompileDirectiveMetadata) => { + var childViewDirectives: CompileDirectiveMetadata[] = this._runtimeMetadataResolver.getViewDirectivesMetadata( childComponentDir.type.runtime); var childIsRecursive = SetWrapper.has(compilingComponentIds, childComponentDir.type.id); @@ -135,12 +145,12 @@ export class TemplateCompiler { components: NormalizedComponentWithViewDirectives[]): SourceModule { var declarations = []; var templateArguments = []; - var componentMetas: NormalizedDirectiveMetadata[] = []; + var componentMetas: CompileDirectiveMetadata[] = []; components.forEach(componentWithDirs => { - var compMeta = componentWithDirs.component; + var compMeta = componentWithDirs.component; componentMetas.push(compMeta); this._processTemplateCodeGen(compMeta, - componentWithDirs.directives, + componentWithDirs.directives, declarations, templateArguments); if (compMeta.dynamicLoadable) { var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector); @@ -148,7 +158,7 @@ export class TemplateCompiler { this._processTemplateCodeGen(hostMeta, [compMeta], declarations, templateArguments); } }); - ListWrapper.forEachWithIndex(componentMetas, (compMeta: NormalizedDirectiveMetadata, + ListWrapper.forEachWithIndex(componentMetas, (compMeta: CompileDirectiveMetadata, index: number) => { var templateDataFn = codeGenValueFn([], `[${templateArguments[index].join(',')}]`); declarations.push( @@ -161,32 +171,33 @@ export class TemplateCompiler { return this._styleCompiler.compileStylesheetCodeGen(moduleId, cssText); } - private _processTemplateCodeGen(compMeta: NormalizedDirectiveMetadata, - directives: NormalizedDirectiveMetadata[], + private _processTemplateCodeGen(compMeta: CompileDirectiveMetadata, + directives: CompileDirectiveMetadata[], targetDeclarations: string[], targetTemplateArguments: any[][]) { var styleExpr = this._styleCompiler.compileComponentCodeGen(compMeta); var parsedTemplate = this._templateParser.parse(compMeta.template.template, directives, compMeta.type.name); - var changeDetectorsExpr = this._cdCompiler.compileComponentCodeGen( - compMeta.type, compMeta.changeDetection.changeDetection, parsedTemplate); + var changeDetectorsExprs = this._cdCompiler.compileComponentCodeGen( + compMeta.type, compMeta.changeDetection, parsedTemplate); var commandsExpr = this._commandCompiler.compileComponentCodeGen( - compMeta, parsedTemplate, codeGenComponentTemplateFactory); + compMeta, parsedTemplate, changeDetectorsExprs.expressions, + codeGenComponentTemplateFactory); addAll(styleExpr.declarations, targetDeclarations); - addAll(changeDetectorsExpr.declarations, targetDeclarations); + addAll(changeDetectorsExprs.declarations, targetDeclarations); addAll(commandsExpr.declarations, targetDeclarations); targetTemplateArguments.push( - [changeDetectorsExpr.expression, commandsExpr.expression, styleExpr.expression]); + [changeDetectorsExprs.expressions[0], commandsExpr.expression, styleExpr.expression]); } } export class NormalizedComponentWithViewDirectives { - constructor(public component: INormalizedDirectiveMetadata, - public directives: INormalizedDirectiveMetadata[]) {} + constructor(public component: CompileDirectiveMetadata, + public directives: CompileDirectiveMetadata[]) {} } -function templateVariableName(type: TypeMetadata): string { +function templateVariableName(type: CompileTypeMetadata): string { return `${type.name}Template`; } @@ -200,6 +211,6 @@ function addAll(source: any[], target: any[]) { } } -function codeGenComponentTemplateFactory(nestedCompType: NormalizedDirectiveMetadata): string { +function codeGenComponentTemplateFactory(nestedCompType: CompileDirectiveMetadata): string { return `${moduleRef(templateModuleName(nestedCompType.type.moduleId))}${templateVariableName(nestedCompType.type)}`; } diff --git a/modules/angular2/src/compiler/template_normalizer.ts b/modules/angular2/src/compiler/template_normalizer.ts index 02a41827d0..f6fa21d8f0 100644 --- a/modules/angular2/src/compiler/template_normalizer.ts +++ b/modules/angular2/src/compiler/template_normalizer.ts @@ -1,8 +1,7 @@ import { - TypeMetadata, - TemplateMetadata, - NormalizedDirectiveMetadata, - NormalizedTemplateMetadata + CompileTypeMetadata, + CompileDirectiveMetadata, + CompileTemplateMetadata } from './directive_metadata'; import {isPresent, isBlank} from 'angular2/src/core/facade/lang'; import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async'; @@ -11,6 +10,7 @@ import {XHR} from 'angular2/src/core/render/xhr'; import {UrlResolver} from 'angular2/src/core/services/url_resolver'; import {resolveStyleUrls} from './style_url_resolver'; import {Injectable} from 'angular2/src/core/di'; +import {ViewEncapsulation} from 'angular2/src/core/render/api'; import { HtmlAstVisitor, @@ -22,21 +22,15 @@ import { } from './html_ast'; import {HtmlParser} from './html_parser'; -const NG_CONTENT_SELECT_ATTR = 'select'; -const NG_CONTENT_ELEMENT = 'ng-content'; -const LINK_ELEMENT = 'link'; -const LINK_STYLE_REL_ATTR = 'rel'; -const LINK_STYLE_HREF_ATTR = 'href'; -const LINK_STYLE_REL_VALUE = 'stylesheet'; -const STYLE_ELEMENT = 'style'; +import {preparseElement, PreparsedElement, PreparsedElementType} from './template_preparser'; @Injectable() export class TemplateNormalizer { constructor(private _xhr: XHR, private _urlResolver: UrlResolver, private _domParser: HtmlParser) {} - normalizeTemplate(directiveType: TypeMetadata, - template: TemplateMetadata): Promise { + normalizeTemplate(directiveType: CompileTypeMetadata, + template: CompileTemplateMetadata): Promise { if (isPresent(template.template)) { return PromiseWrapper.resolve(this.normalizeLoadedTemplate( directiveType, template, template.template, directiveType.moduleId)); @@ -48,11 +42,11 @@ export class TemplateNormalizer { } } - normalizeLoadedTemplate(directiveType: TypeMetadata, templateMeta: TemplateMetadata, - template: string, templateAbsUrl: string): NormalizedTemplateMetadata { + normalizeLoadedTemplate(directiveType: CompileTypeMetadata, templateMeta: CompileTemplateMetadata, + template: string, templateAbsUrl: string): CompileTemplateMetadata { var domNodes = this._domParser.parse(template, directiveType.name); var visitor = new TemplatePreparseVisitor(); - var remainingNodes = htmlVisitAll(visitor, domNodes); + htmlVisitAll(visitor, domNodes); var allStyles = templateMeta.styles.concat(visitor.styles); var allStyleAbsUrls = @@ -65,11 +59,17 @@ export class TemplateNormalizer { styleWithImports.styleUrls.forEach(styleUrl => allStyleAbsUrls.push(styleUrl)); return styleWithImports.style; }); - return new NormalizedTemplateMetadata({ - encapsulation: templateMeta.encapsulation, - template: this._domParser.unparse(remainingNodes), + var encapsulation = templateMeta.encapsulation; + if (encapsulation === ViewEncapsulation.Emulated && allResolvedStyles.length === 0 && + allStyleAbsUrls.length === 0) { + encapsulation = ViewEncapsulation.None; + } + return new CompileTemplateMetadata({ + encapsulation: encapsulation, + template: template, + templateUrl: templateAbsUrl, styles: allResolvedStyles, - styleAbsUrls: allStyleAbsUrls, + styleUrls: allStyleAbsUrls, ngContentSelectors: visitor.ngContentSelectors }); } @@ -80,50 +80,30 @@ class TemplatePreparseVisitor implements HtmlAstVisitor { styles: string[] = []; styleUrls: string[] = []; - visitElement(ast: HtmlElementAst, context: any): HtmlElementAst { - var selectAttr = null; - var hrefAttr = null; - var relAttr = null; - ast.attrs.forEach(attr => { - if (attr.name == NG_CONTENT_SELECT_ATTR) { - selectAttr = attr.value; - } else if (attr.name == LINK_STYLE_HREF_ATTR) { - hrefAttr = attr.value; - } else if (attr.name == LINK_STYLE_REL_ATTR) { - relAttr = attr.value; - } - }); - var nodeName = ast.name; - var keepElement = true; - if (nodeName == NG_CONTENT_ELEMENT) { - this.ngContentSelectors.push(normalizeNgContentSelect(selectAttr)); - } else if (nodeName == STYLE_ELEMENT) { - keepElement = false; - var textContent = ''; - ast.children.forEach(child => { - if (child instanceof HtmlTextAst) { - textContent += (child).value; - } - }); - this.styles.push(textContent); - } else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) { - keepElement = false; - this.styleUrls.push(hrefAttr); + visitElement(ast: HtmlElementAst, context: any): any { + var preparsedElement = preparseElement(ast); + switch (preparsedElement.type) { + case PreparsedElementType.NG_CONTENT: + this.ngContentSelectors.push(preparsedElement.selectAttr); + break; + case PreparsedElementType.STYLE: + var textContent = ''; + ast.children.forEach(child => { + if (child instanceof HtmlTextAst) { + textContent += (child).value; + } + }); + this.styles.push(textContent); + break; + case PreparsedElementType.STYLESHEET: + this.styleUrls.push(preparsedElement.hrefAttr); + break; } - if (keepElement) { - return new HtmlElementAst(ast.name, ast.attrs, htmlVisitAll(this, ast.children), - ast.sourceInfo); - } else { - return null; + if (preparsedElement.type !== PreparsedElementType.NON_BINDABLE) { + htmlVisitAll(this, ast.children); } + return null; } - visitAttr(ast: HtmlAttrAst, context: any): HtmlAttrAst { return ast; } - visitText(ast: HtmlTextAst, context: any): HtmlTextAst { return ast; } + visitAttr(ast: HtmlAttrAst, context: any): any { return null; } + visitText(ast: HtmlTextAst, context: any): any { return null; } } - -function normalizeNgContentSelect(selectAttr: string): string { - if (isBlank(selectAttr) || selectAttr.length === 0) { - return '*'; - } - return selectAttr; -} \ No newline at end of file diff --git a/modules/angular2/src/compiler/template_parser.ts b/modules/angular2/src/compiler/template_parser.ts index 1695169068..66ca60fa47 100644 --- a/modules/angular2/src/compiler/template_parser.ts +++ b/modules/angular2/src/compiler/template_parser.ts @@ -1,4 +1,9 @@ -import {MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection'; +import { + MapWrapper, + ListWrapper, + StringMapWrapper, + SetWrapper +} from 'angular2/src/core/facade/collection'; import { RegExpWrapper, isPresent, @@ -12,7 +17,7 @@ import {Injectable} from 'angular2/src/core/di'; import {BaseException} from 'angular2/src/core/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 {NormalizedDirectiveMetadata} from './directive_metadata'; +import {CompileDirectiveMetadata} from './directive_metadata'; import {HtmlParser} from './html_parser'; import { @@ -33,6 +38,7 @@ import { import {CssSelector, SelectorMatcher} from 'angular2/src/core/render/dom/compiler/selector'; import {ElementSchemaRegistry} from 'angular2/src/core/render/dom/schema/element_schema_registry'; +import {preparseElement, PreparsedElement, PreparsedElementType} from './template_preparser'; import { HtmlAstVisitor, @@ -43,7 +49,7 @@ import { htmlVisitAll } from './html_ast'; -import {dashCaseToCamelCase, camelCaseToDashCase} from './util'; +import {dashCaseToCamelCase, camelCaseToDashCase, splitAtColon} from './util'; // Group 1 = "bind-" // Group 2 = "var-" or "#" @@ -56,12 +62,10 @@ import {dashCaseToCamelCase, camelCaseToDashCase} from './util'; var BIND_NAME_REGEXP = /^(?:(?:(?:(bind-)|(var-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g; -const NG_CONTENT_ELEMENT = 'ng-content'; const TEMPLATE_ELEMENT = 'template'; const TEMPLATE_ATTR = 'template'; const TEMPLATE_ATTR_PREFIX = '*'; const CLASS_ATTR = 'class'; -const IMPLICIT_VAR_NAME = '$implicit'; var PROPERTY_PARTS_SEPARATOR = new RegExp('\\.'); const ATTRIBUTE_PREFIX = 'attr'; @@ -75,7 +79,7 @@ export class TemplateParser { constructor(private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry, private _htmlParser: HtmlParser) {} - parse(template: string, directives: NormalizedDirectiveMetadata[], + parse(template: string, directives: CompileDirectiveMetadata[], sourceInfo: string): TemplateAst[] { var parseVisitor = new TemplateParseVisitor(directives, this._exprParser, this._schemaRegistry); var result = @@ -91,13 +95,16 @@ export class TemplateParser { class TemplateParseVisitor implements HtmlAstVisitor { selectorMatcher: SelectorMatcher; errors: string[] = []; - constructor(directives: NormalizedDirectiveMetadata[], private _exprParser: Parser, + directivesIndexByTypeId: Map = new Map(); + constructor(directives: CompileDirectiveMetadata[], private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry) { this.selectorMatcher = new SelectorMatcher(); - directives.forEach(directive => { - var selector = CssSelector.parse(directive.selector); - this.selectorMatcher.addSelectables(selector, directive); - }); + ListWrapper.forEachWithIndex(directives, + (directive: CompileDirectiveMetadata, index: number) => { + var selector = CssSelector.parse(directive.selector); + this.selectorMatcher.addSelectables(selector, directive); + this.directivesIndexByTypeId.set(directive.type.id, index); + }); } private _reportError(message: string) { this.errors.push(message); } @@ -154,6 +161,17 @@ class TemplateParseVisitor implements HtmlAstVisitor { visitElement(element: HtmlElementAst, component: Component): any { var nodeName = element.name; + var preparsedElement = preparseElement(element); + if (preparsedElement.type === PreparsedElementType.SCRIPT || + preparsedElement.type === PreparsedElementType.STYLE || + preparsedElement.type === PreparsedElementType.STYLESHEET || + preparsedElement.type === PreparsedElementType.NON_BINDABLE) { + // Skipping a', []))) + .toEqual([[TextAst, 'a', 'TestComp > #text(a):nth-child(1)']]); + + }); + + it('should ignore a', []))) + .toEqual([[TextAst, 'a', 'TestComp > #text(a):nth-child(1)']]); + + }); + + it('should ignore elements but include them for source info', () => { + expect(humanizeTemplateAsts(parse('a', []))) + .toEqual([[TextAst, 'a', 'TestComp > #text(a):nth-child(1)']]); + + }); + + it('should ignore elements with ng-non-bindable, including their children, but include them for source info', + () => { + expect(humanizeTemplateAsts(parse('
b
a', []))) + .toEqual([[TextAst, 'a', 'TestComp > #text(a):nth-child(1)']]); + + }); + + }); }); } @@ -805,7 +874,7 @@ class TemplateHumanizer implements TemplateAstVisitor { templateVisitAll(this, ast.attrs); templateVisitAll(this, ast.properties); templateVisitAll(this, ast.events); - templateVisitAll(this, ast.vars); + templateVisitAll(this, ast.exportAsVars); templateVisitAll(this, ast.directives); templateVisitAll(this, ast.children); return null; @@ -852,6 +921,7 @@ class TemplateHumanizer implements TemplateAstVisitor { templateVisitAll(this, ast.properties); templateVisitAll(this, ast.hostProperties); templateVisitAll(this, ast.hostEvents); + templateVisitAll(this, ast.exportAsVars); return null; } visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { diff --git a/modules/angular2/test/core/compiler/integration_spec.ts b/modules/angular2/test/core/compiler/integration_spec.ts index 720178366e..aefccc0594 100644 --- a/modules/angular2/test/core/compiler/integration_spec.ts +++ b/modules/angular2/test/core/compiler/integration_spec.ts @@ -397,23 +397,6 @@ export function main() { }); })); - it('should use the last directive binding per directive', - inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { - tcb.overrideView(MyComp, new ViewMetadata({ - template: '

', - directives: [ - bind(DuplicateDir) - .toClass(DuplicateDir), - bind(DuplicateDir).toClass(OtherDuplicateDir) - ] - })) - .createAsync(MyComp) - .then((rootTC) => { - expect(rootTC.debugElement.nativeElement).toHaveText('othernoduplicate'); - async.done(); - }); - })); - it('should support directives where a selector matches property binding', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { tcb.overrideView(MyComp, new ViewMetadata( @@ -436,27 +419,11 @@ export function main() { }); })); - it('should allow specifying directives as bindings', - inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { - tcb.overrideView(MyComp, new ViewMetadata({ - template: '', - directives: [bind(ChildComp).toClass(ChildComp)] - })) - - .createAsync(MyComp) - .then((rootTC) => { - rootTC.detectChanges(); - - expect(rootTC.debugElement.nativeElement).toHaveText('hello'); - async.done(); - }); - })); - it('should read directives metadata from their binding token', inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { tcb.overrideView(MyComp, new ViewMetadata({ template: '
', - directives: [bind(PublicApi).toClass(PrivateImpl), NeedsPublicApi] + directives: [PrivateImpl, NeedsPublicApi] })) .createAsync(MyComp) @@ -2043,12 +2010,14 @@ class NeedsAttribute { } } -@Directive({selector: '[public-api]'}) @Injectable() class PublicApi { } -@Directive({selector: '[private-impl]'}) +@Directive({ + selector: '[public-api]', + bindings: [new Binding(PublicApi, {toAlias: PrivateImpl, deps: []})] +}) @Injectable() class PrivateImpl extends PublicApi { } diff --git a/modules/angular2/test/core/forward_ref_integration_spec.ts b/modules/angular2/test/core/forward_ref_integration_spec.ts index bb1bb2f00b..0ffa123a2d 100644 --- a/modules/angular2/test/core/forward_ref_integration_spec.ts +++ b/modules/angular2/test/core/forward_ref_integration_spec.ts @@ -42,11 +42,7 @@ export function main() { @Component({selector: 'app', viewBindings: [forwardRef(() => Frame)]}) @View({ template: ``, - directives: [ - bind(forwardRef(() => Door)) - .toClass(forwardRef(() => Door)), - bind(forwardRef(() => Lock)).toClass(forwardRef(() => Lock)) - ] + directives: [forwardRef(() => Door), forwardRef(() => Lock)] }) class App { }