refactor(compiler): cleanup and preparation for integration

- Rename `DirectiveMetadata` into `CompileDirectiveMetadata`, merge
  with `NormalizedDirectiveMetadata` and remove `ChangeDetectionMetadata`
- Store change detector factories not as array but
  directly at the `CompiledTemplate` or the embedded template
  to make instantiation easier later on
- Already analyze variable values and map them
  to `Directive.exportAs`
- Keep the directive sort order as specified in the
  `@View()` annotation
- Allow to clear the runtime cache in `StyleCompiler`
  and `TemplateCompiler`
- Ignore `script` elements to match the semantics of the
  current compiler
- Make all components dynamically loadable and remove
  the previously introduced property `@Component#dynamicLoadable`
  for now until we find a better option to configure this
- Don’t allow to specify bindings in `@View#directives` and `@View#pipes` as this was never supported by the transformer (see below for the breaking change)

BREAKING CHANGE:
- don't support DI bindings in `@View#directives` and `@View@pipes` any more in preparation of integrating the new compiler. Use `@Directive#bindings` to reexport directives under a different token instead.

Part of #3605
Closes #4314
This commit is contained in:
Tobias Bosch 2015-09-18 10:33:23 -07:00
parent eb7839e0ec
commit cc0c30484f
37 changed files with 1480 additions and 1167 deletions

View File

@ -14,7 +14,7 @@ import {
ASTWithSource ASTWithSource
} from 'angular2/src/core/change_detection/change_detection'; } from 'angular2/src/core/change_detection/change_detection';
import {NormalizedDirectiveMetadata, TypeMetadata} from './directive_metadata'; import {CompileDirectiveMetadata, CompileTypeMetadata} from './directive_metadata';
import { import {
TemplateAst, TemplateAst,
ElementAst, ElementAst,
@ -32,9 +32,10 @@ import {
AttrAst, AttrAst,
TextAst TextAst
} from './template_ast'; } from './template_ast';
import {LifecycleHooks} from 'angular2/src/core/compiler/interfaces';
export function createChangeDetectorDefinitions( export function createChangeDetectorDefinitions(
componentType: TypeMetadata, componentStrategy: ChangeDetectionStrategy, componentType: CompileTypeMetadata, componentStrategy: ChangeDetectionStrategy,
genConfig: ChangeDetectorGenConfig, parsedTemplate: TemplateAst[]): ChangeDetectorDefinition[] { genConfig: ChangeDetectorGenConfig, parsedTemplate: TemplateAst[]): ChangeDetectorDefinition[] {
var pvVisitors = []; var pvVisitors = [];
var visitor = new ProtoViewVisitor(null, pvVisitors, componentStrategy); var visitor = new ProtoViewVisitor(null, pvVisitors, componentStrategy);
@ -59,7 +60,9 @@ class ProtoViewVisitor implements TemplateAstVisitor {
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any {
this.boundElementCount++; this.boundElementCount++;
templateVisitAll(this, ast.directives); for (var i = 0; i < ast.directives.length; i++) {
ast.directives[i].visit(this, i);
}
var childVisitor = var childVisitor =
new ProtoViewVisitor(this, this.allVisitors, ChangeDetectionStrategy.Default); new ProtoViewVisitor(this, this.allVisitors, ChangeDetectionStrategy.Default);
@ -76,7 +79,7 @@ class ProtoViewVisitor implements TemplateAstVisitor {
} }
templateVisitAll(this, ast.properties, null); templateVisitAll(this, ast.properties, null);
templateVisitAll(this, ast.events); templateVisitAll(this, ast.events);
templateVisitAll(this, ast.vars); templateVisitAll(this, ast.exportAsVars);
for (var i = 0; i < ast.directives.length; i++) { for (var i = 0; i < ast.directives.length; i++) {
ast.directives[i].visit(this, i); ast.directives[i].visit(this, i);
} }
@ -94,8 +97,8 @@ class ProtoViewVisitor implements TemplateAstVisitor {
visitEvent(ast: BoundEventAst, directiveRecord: DirectiveRecord): any { visitEvent(ast: BoundEventAst, directiveRecord: DirectiveRecord): any {
var bindingRecord = var bindingRecord =
isPresent(directiveRecord) ? isPresent(directiveRecord) ?
BindingRecord.createForHostEvent(ast.handler, ast.name, directiveRecord) : BindingRecord.createForHostEvent(ast.handler, ast.fullName, directiveRecord) :
BindingRecord.createForEvent(ast.handler, ast.name, this.boundElementCount - 1); BindingRecord.createForEvent(ast.handler, ast.fullName, this.boundElementCount - 1);
this.eventRecords.push(bindingRecord); this.eventRecords.push(bindingRecord);
return null; return null;
} }
@ -138,17 +141,20 @@ class ProtoViewVisitor implements TemplateAstVisitor {
visitDirective(ast: DirectiveAst, directiveIndexAsNumber: number): any { visitDirective(ast: DirectiveAst, directiveIndexAsNumber: number): any {
var directiveIndex = new DirectiveIndex(this.boundElementCount - 1, directiveIndexAsNumber); var directiveIndex = new DirectiveIndex(this.boundElementCount - 1, directiveIndexAsNumber);
var directiveMetadata = ast.directive; var directiveMetadata = ast.directive;
var changeDetectionMeta = directiveMetadata.changeDetection;
var directiveRecord = new DirectiveRecord({ var directiveRecord = new DirectiveRecord({
directiveIndex: directiveIndex, directiveIndex: directiveIndex,
callAfterContentInit: changeDetectionMeta.callAfterContentInit, callAfterContentInit:
callAfterContentChecked: changeDetectionMeta.callAfterContentChecked, directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.AfterContentInit) !== -1,
callAfterViewInit: changeDetectionMeta.callAfterViewInit, callAfterContentChecked:
callAfterViewChecked: changeDetectionMeta.callAfterViewChecked, directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.AfterContentChecked) !== -1,
callOnChanges: changeDetectionMeta.callOnChanges, callAfterViewInit:
callDoCheck: changeDetectionMeta.callDoCheck, directiveMetadata.lifecycleHooks.indexOf(LifecycleHooks.AfterViewInit) !== -1,
callOnInit: changeDetectionMeta.callOnInit, callAfterViewChecked:
changeDetection: changeDetectionMeta.changeDetection 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); this.directiveRecords.push(directiveRecord);
@ -165,6 +171,7 @@ class ProtoViewVisitor implements TemplateAstVisitor {
} }
templateVisitAll(this, ast.hostProperties, directiveRecord); templateVisitAll(this, ast.hostProperties, directiveRecord);
templateVisitAll(this, ast.hostEvents, directiveRecord); templateVisitAll(this, ast.hostEvents, directiveRecord);
templateVisitAll(this, ast.exportAsVars);
return null; return null;
} }
visitDirectiveProperty(ast: BoundDirectivePropertyAst, directiveRecord: DirectiveRecord): any { 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[] { genConfig: ChangeDetectorGenConfig): ChangeDetectorDefinition[] {
var pvVariableNames = _collectNestedProtoViewsVariableNames(pvVisitors); var pvVariableNames = _collectNestedProtoViewsVariableNames(pvVisitors);
return pvVisitors.map(pvVisitor => { 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}`; return `${hostComponentType.name}_${viewType}_${pvIndex}`;
} }

View File

@ -1,5 +1,5 @@
import {TypeMetadata} from './directive_metadata'; import {CompileTypeMetadata} from './directive_metadata';
import {SourceExpression, moduleRef} from './source_module'; import {SourceExpressions, moduleRef} from './source_module';
import { import {
ChangeDetectorJITGenerator ChangeDetectorJITGenerator
} from 'angular2/src/core/change_detection/change_detection_jit_generator'; } from 'angular2/src/core/change_detection/change_detection_jit_generator';
@ -32,7 +32,7 @@ var PREGEN_PROTO_CHANGE_DETECTOR_MODULE =
export class ChangeDetectionCompiler { export class ChangeDetectionCompiler {
constructor(private _genConfig: ChangeDetectorGenConfig) {} constructor(private _genConfig: ChangeDetectorGenConfig) {}
compileComponentRuntime(componentType: TypeMetadata, strategy: ChangeDetectionStrategy, compileComponentRuntime(componentType: CompileTypeMetadata, strategy: ChangeDetectionStrategy,
parsedTemplate: TemplateAst[]): Function[] { parsedTemplate: TemplateAst[]): Function[] {
var changeDetectorDefinitions = var changeDetectorDefinitions =
createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate); createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate);
@ -51,8 +51,8 @@ export class ChangeDetectionCompiler {
} }
} }
compileComponentCodeGen(componentType: TypeMetadata, strategy: ChangeDetectionStrategy, compileComponentCodeGen(componentType: CompileTypeMetadata, strategy: ChangeDetectionStrategy,
parsedTemplate: TemplateAst[]): SourceExpression { parsedTemplate: TemplateAst[]): SourceExpressions {
var changeDetectorDefinitions = var changeDetectorDefinitions =
createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate); createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate);
var factories = []; var factories = [];
@ -75,7 +75,6 @@ export class ChangeDetectionCompiler {
return codegen.generateSource(); return codegen.generateSource();
} }
}); });
var expression = `[ ${factories.join(',')} ]`; return new SourceExpressions(sourceParts, factories);
return new SourceExpression(sourceParts, expression);
} }
} }

View File

@ -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 { import {
TemplateCmd, TemplateCmd,
text, text,
@ -25,8 +26,8 @@ import {
BoundDirectivePropertyAst, BoundDirectivePropertyAst,
templateVisitAll templateVisitAll
} from './template_ast'; } from './template_ast';
import {TypeMetadata, NormalizedDirectiveMetadata} from './directive_metadata'; import {CompileTypeMetadata, CompileDirectiveMetadata} from './directive_metadata';
import {SourceExpression, moduleRef} from './source_module'; import {SourceExpressions, SourceExpression, moduleRef} from './source_module';
import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {ViewEncapsulation} from 'angular2/src/core/render/api';
import {shimHostAttribute, shimContentAttribute} from './style_compiler'; import {shimHostAttribute, shimContentAttribute} from './style_compiler';
@ -34,21 +35,25 @@ import {escapeSingleQuoteString} from './util';
import {Injectable} from 'angular2/src/core/di'; import {Injectable} from 'angular2/src/core/di';
export var TEMPLATE_COMMANDS_MODULE_REF = moduleRef('angular2/src/core/compiler/template_commands'); export var TEMPLATE_COMMANDS_MODULE_REF = moduleRef('angular2/src/core/compiler/template_commands');
const IMPLICIT_VAR = '%implicit';
@Injectable() @Injectable()
export class CommandCompiler { export class CommandCompiler {
compileComponentRuntime(component: NormalizedDirectiveMetadata, template: TemplateAst[], compileComponentRuntime(component: CompileDirectiveMetadata, template: TemplateAst[],
changeDetectorFactories: Function[],
componentTemplateFactory: Function): TemplateCmd[] { componentTemplateFactory: Function): TemplateCmd[] {
var visitor = var visitor = new CommandBuilderVisitor(
new CommandBuilderVisitor(new RuntimeCommandFactory(componentTemplateFactory), component); new RuntimeCommandFactory(componentTemplateFactory, changeDetectorFactories), component, 0);
templateVisitAll(visitor, template); templateVisitAll(visitor, template);
return visitor.result; return visitor.result;
} }
compileComponentCodeGen(component: NormalizedDirectiveMetadata, template: TemplateAst[], compileComponentCodeGen(component: CompileDirectiveMetadata, template: TemplateAst[],
changeDetectorFactoryExpressions: string[],
componentTemplateFactory: Function): SourceExpression { componentTemplateFactory: Function): SourceExpression {
var visitor = var visitor = new CommandBuilderVisitor(
new CommandBuilderVisitor(new CodegenCommandFactory(componentTemplateFactory), component); new CodegenCommandFactory(componentTemplateFactory, changeDetectorFactoryExpressions),
component, 0);
templateVisitAll(visitor, template); templateVisitAll(visitor, template);
var source = `[${visitor.result.join(',')}]`; var source = `[${visitor.result.join(',')}]`;
return new SourceExpression([], source); return new SourceExpression([], source);
@ -58,22 +63,23 @@ export class CommandCompiler {
interface CommandFactory<R> { interface CommandFactory<R> {
createText(value: string, isBound: boolean, ngContentIndex: number): R; createText(value: string, isBound: boolean, ngContentIndex: number): R;
createNgContent(ngContentIndex: number): R; createNgContent(ngContentIndex: number): R;
createBeginElement(name: string, attrNameAndValues: string[], eventNames: string[], createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isBound: boolean, ngContentIndex: number): R; isBound: boolean, ngContentIndex: number): R;
createEndElement(): R; createEndElement(): R;
createBeginComponent(name: string, attrNameAndValues: string[], eventNames: string[], createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
nativeShadow: boolean, ngContentIndex: number): R; nativeShadow: boolean, ngContentIndex: number): R;
createEndComponent(): R; createEndComponent(): R;
createEmbeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[], createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[],
directives: NormalizedDirectiveMetadata[], isMerged: boolean, variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
ngContentIndex: number, children: R[]): R; isMerged: boolean, ngContentIndex: number, children: R[]): R;
} }
class RuntimeCommandFactory implements CommandFactory<TemplateCmd> { class RuntimeCommandFactory implements CommandFactory<TemplateCmd> {
constructor(public componentTemplateFactory: Function) {} constructor(public componentTemplateFactory: Function,
private _mapDirectives(directives: NormalizedDirectiveMetadata[]): Type[] { public changeDetectorFactories: Function[]) {}
private _mapDirectives(directives: CompileDirectiveMetadata[]): Type[] {
return directives.map(directive => directive.type.runtime); return directives.map(directive => directive.type.runtime);
} }
@ -81,35 +87,46 @@ class RuntimeCommandFactory implements CommandFactory<TemplateCmd> {
return text(value, isBound, ngContentIndex); return text(value, isBound, ngContentIndex);
} }
createNgContent(ngContentIndex: number): TemplateCmd { return ngContent(ngContentIndex); } createNgContent(ngContentIndex: number): TemplateCmd { return ngContent(ngContentIndex); }
createBeginElement(name: string, attrNameAndValues: string[], eventNames: string[], createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isBound: boolean, ngContentIndex: number): TemplateCmd { isBound: boolean, ngContentIndex: number): TemplateCmd {
return beginElement(name, attrNameAndValues, eventNames, variableNameAndValues, return beginElement(name, attrNameAndValues, eventTargetAndNames, variableNameAndValues,
this._mapDirectives(directives), isBound, ngContentIndex); this._mapDirectives(directives), isBound, ngContentIndex);
} }
createEndElement(): TemplateCmd { return endElement(); } createEndElement(): TemplateCmd { return endElement(); }
createBeginComponent(name: string, attrNameAndValues: string[], eventNames: string[], createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
nativeShadow: boolean, ngContentIndex: number): TemplateCmd { nativeShadow: boolean, ngContentIndex: number): TemplateCmd {
return beginComponent(name, attrNameAndValues, eventNames, variableNameAndValues, return beginComponent(name, attrNameAndValues, eventTargetAndNames, variableNameAndValues,
this._mapDirectives(directives), nativeShadow, ngContentIndex, this._mapDirectives(directives), nativeShadow, ngContentIndex,
this.componentTemplateFactory(directives[0])); this.componentTemplateFactory(directives[0]));
} }
createEndComponent(): TemplateCmd { return endComponent(); } createEndComponent(): TemplateCmd { return endComponent(); }
createEmbeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[], createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[],
directives: NormalizedDirectiveMetadata[], isMerged: boolean, variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
ngContentIndex: number, children: TemplateCmd[]): TemplateCmd { isMerged: boolean, ngContentIndex: number,
children: TemplateCmd[]): TemplateCmd {
return embeddedTemplate(attrNameAndValues, variableNameAndValues, return embeddedTemplate(attrNameAndValues, variableNameAndValues,
this._mapDirectives(directives), isMerged, ngContentIndex, children); this._mapDirectives(directives), isMerged, ngContentIndex,
this.changeDetectorFactories[embeddedTemplateIndex], children);
} }
} }
function escapeStringArray(data: string[]): string { function escapePrimitiveArray(data: any[]): string {
return `[${data.map( value => escapeSingleQuoteString(value)).join(',')}]`; return `[${data.map( (value) => {
if (isString(value)) {
return escapeSingleQuoteString(value);
} else if (isBlank(value)) {
return 'null';
} else {
return value;
}
}).join(',')}]`;
} }
class CodegenCommandFactory implements CommandFactory<string> { class CodegenCommandFactory implements CommandFactory<string> {
constructor(public componentTemplateFactory: Function) {} constructor(public componentTemplateFactory: Function,
public changeDetectorFactoryExpressions: string[]) {}
createText(value: string, isBound: boolean, ngContentIndex: number): string { createText(value: string, isBound: boolean, ngContentIndex: number): string {
return `${TEMPLATE_COMMANDS_MODULE_REF}text(${escapeSingleQuoteString(value)}, ${isBound}, ${ngContentIndex})`; return `${TEMPLATE_COMMANDS_MODULE_REF}text(${escapeSingleQuoteString(value)}, ${isBound}, ${ngContentIndex})`;
@ -117,26 +134,27 @@ class CodegenCommandFactory implements CommandFactory<string> {
createNgContent(ngContentIndex: number): string { createNgContent(ngContentIndex: number): string {
return `${TEMPLATE_COMMANDS_MODULE_REF}ngContent(${ngContentIndex})`; return `${TEMPLATE_COMMANDS_MODULE_REF}ngContent(${ngContentIndex})`;
} }
createBeginElement(name: string, attrNameAndValues: string[], eventNames: string[], createBeginElement(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
isBound: boolean, ngContentIndex: number): string { 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()`; } createEndElement(): string { return `${TEMPLATE_COMMANDS_MODULE_REF}endElement()`; }
createBeginComponent(name: string, attrNameAndValues: string[], eventNames: string[], createBeginComponent(name: string, attrNameAndValues: string[], eventTargetAndNames: string[],
variableNameAndValues: string[], directives: NormalizedDirectiveMetadata[], variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
nativeShadow: boolean, ngContentIndex: number): string { 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()`; } createEndComponent(): string { return `${TEMPLATE_COMMANDS_MODULE_REF}endComponent()`; }
createEmbeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[], createEmbeddedTemplate(embeddedTemplateIndex: number, attrNameAndValues: string[],
directives: NormalizedDirectiveMetadata[], isMerged: boolean, variableNameAndValues: string[], directives: CompileDirectiveMetadata[],
ngContentIndex: number, children: string[]): string { isMerged: boolean, ngContentIndex: number, children: string[]): string {
return `${TEMPLATE_COMMANDS_MODULE_REF}embeddedTemplate(${escapeStringArray(attrNameAndValues)}, ${escapeStringArray(variableNameAndValues)}, [${_escapeDirectives(directives).join(',')}], ${isMerged}, ${ngContentIndex}, [${children.join(',')}])`; 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 => return directives.map(directiveType =>
`${moduleRef(directiveType.type.moduleId)}${directiveType.type.name}`); `${moduleRef(directiveType.type.moduleId)}${directiveType.type.name}`);
} }
@ -150,10 +168,11 @@ function visitAndReturnContext(visitor: TemplateAstVisitor, asts: TemplateAst[],
class CommandBuilderVisitor<R> implements TemplateAstVisitor { class CommandBuilderVisitor<R> implements TemplateAstVisitor {
result: R[] = []; result: R[] = [];
transitiveNgContentCount: number = 0; transitiveNgContentCount: number = 0;
constructor(public commandFactory: CommandFactory<R>, constructor(public commandFactory: CommandFactory<R>, public component: CompileDirectiveMetadata,
public component: NormalizedDirectiveMetadata) {} public embeddedTemplateIndex: number) {}
private _readAttrNameAndValues(localComponent: NormalizedDirectiveMetadata, private _readAttrNameAndValues(localComponent: CompileDirectiveMetadata,
directives: CompileDirectiveMetadata[],
attrAsts: TemplateAst[]): string[] { attrAsts: TemplateAst[]): string[] {
var attrNameAndValues: string[] = visitAndReturnContext(this, attrAsts, []); var attrNameAndValues: string[] = visitAndReturnContext(this, attrAsts, []);
if (isPresent(localComponent) && if (isPresent(localComponent) &&
@ -165,7 +184,13 @@ class CommandBuilderVisitor<R> implements TemplateAstVisitor {
attrNameAndValues.push(shimContentAttribute(this.component.type.id)); attrNameAndValues.push(shimContentAttribute(this.component.type.id));
attrNameAndValues.push(''); 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 { visitNgContent(ast: NgContentAst, context: any): any {
@ -174,43 +199,61 @@ class CommandBuilderVisitor<R> implements TemplateAstVisitor {
return null; return null;
} }
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { 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); templateVisitAll(childVisitor, ast.children);
var isMerged = childVisitor.transitiveNgContentCount > 0; var isMerged = childVisitor.transitiveNgContentCount > 0;
this.transitiveNgContentCount += childVisitor.transitiveNgContentCount; var variableNameAndValues = [];
var directivesAndEventNames = visitAndReturnContext(this, ast.directives, [[], []]); 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.result.push(this.commandFactory.createEmbeddedTemplate(
this._readAttrNameAndValues(null, ast.attrs), visitAndReturnContext(this, ast.vars, []), this.embeddedTemplateIndex, this._readAttrNameAndValues(null, directives, ast.attrs),
directivesAndEventNames[0], isMerged, ast.ngContentIndex, childVisitor.result)); variableNameAndValues, directives, isMerged, ast.ngContentIndex, childVisitor.result));
this.transitiveNgContentCount += childVisitor.transitiveNgContentCount;
this.embeddedTemplateIndex = childVisitor.embeddedTemplateIndex;
return null; return null;
} }
visitElement(ast: ElementAst, context: any): any { visitElement(ast: ElementAst, context: any): any {
var component = ast.getComponent(); 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 = []; var directives = [];
visitAndReturnContext(this, ast.directives, [directives, eventNames]); ListWrapper.forEachWithIndex(ast.directives, (directiveAst: DirectiveAst, index: number) => {
var attrNameAndValues = this._readAttrNameAndValues(component, ast.attrs); directiveAst.visit(this, new DirectiveContext(index, eventTargetAndNames,
var vars = visitAndReturnContext(this, ast.vars, []); variableNameAndValues, directives));
});
eventTargetAndNames = removeKeyValueArrayDuplicates(eventTargetAndNames);
var attrNameAndValues = this._readAttrNameAndValues(component, directives, ast.attrs);
if (isPresent(component)) { if (isPresent(component)) {
this.result.push(this.commandFactory.createBeginComponent( 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)); component.template.encapsulation === ViewEncapsulation.Native, ast.ngContentIndex));
templateVisitAll(this, ast.children); templateVisitAll(this, ast.children);
this.result.push(this.commandFactory.createEndComponent()); this.result.push(this.commandFactory.createEndComponent());
} else { } else {
this.result.push(this.commandFactory.createBeginElement(ast.name, attrNameAndValues, this.result.push(this.commandFactory.createBeginElement(
eventNames, vars, directives, ast.name, attrNameAndValues, eventTargetAndNames, variableNameAndValues, directives,
ast.isBound(), ast.ngContentIndex)); ast.isBound(), ast.ngContentIndex));
templateVisitAll(this, ast.children); templateVisitAll(this, ast.children);
this.result.push(this.commandFactory.createEndElement()); this.result.push(this.commandFactory.createEndElement());
} }
return null; return null;
} }
visitVariable(ast: VariableAst, variableNameAndValues: string[]): any { visitVariable(ast: VariableAst, ctx: any): any { return null; }
variableNameAndValues.push(ast.name);
variableNameAndValues.push(ast.value);
return null;
}
visitAttr(ast: AttrAst, attrNameAndValues: string[]): any { visitAttr(ast: AttrAst, attrNameAndValues: string[]): any {
attrNameAndValues.push(ast.name); attrNameAndValues.push(ast.name);
attrNameAndValues.push(ast.value); attrNameAndValues.push(ast.value);
@ -224,15 +267,42 @@ class CommandBuilderVisitor<R> implements TemplateAstVisitor {
this.result.push(this.commandFactory.createText(ast.value, false, ast.ngContentIndex)); this.result.push(this.commandFactory.createText(ast.value, false, ast.ngContentIndex));
return null; return null;
} }
visitDirective(ast: DirectiveAst, directivesAndEventNames: any[][]): any { visitDirective(ast: DirectiveAst, ctx: DirectiveContext): any {
directivesAndEventNames[0].push(ast.directive); ctx.targetDirectives.push(ast.directive);
templateVisitAll(this, ast.hostEvents, directivesAndEventNames[1]); templateVisitAll(this, ast.hostEvents, ctx.eventTargetAndNames);
ast.exportAsVars.forEach(varAst => {
ctx.targetVariableNameAndValues.push(varAst.name);
ctx.targetVariableNameAndValues.push(ctx.index);
});
return null; return null;
} }
visitEvent(ast: BoundEventAst, eventNames: string[]): any { visitEvent(ast: BoundEventAst, eventTargetAndNames: string[]): any {
eventNames.push(ast.getFullName()); eventTargetAndNames.push(ast.target);
eventTargetAndNames.push(ast.name);
return null; return null;
} }
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; } visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { return null; }
visitElementProperty(ast: BoundElementPropertyAst, 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[]) {}
}

View File

@ -1,9 +1,7 @@
export {TemplateCompiler} from './template_compiler'; export {TemplateCompiler} from './template_compiler';
export { export {
DirectiveMetadata, CompileDirectiveMetadata,
TypeMetadata, CompileTypeMetadata,
TemplateMetadata, CompileTemplateMetadata
ChangeDetectionMetadata,
INormalizedDirectiveMetadata
} from './directive_metadata'; } from './directive_metadata';
export {SourceModule, SourceWithImports} from './source_module'; export {SourceModule, SourceWithImports} from './source_module';

View File

@ -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 { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
changeDetectionStrategyFromJson CHANGE_DECTION_STRATEGY_VALUES
} from 'angular2/src/core/change_detection/change_detection'; } 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 {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; id: number;
runtime: Type; runtime: Type;
name: string; name: string;
@ -19,8 +34,9 @@ export class TypeMetadata {
this.moduleId = moduleId; this.moduleId = moduleId;
} }
static fromJson(data: StringMap<string, any>): TypeMetadata { static fromJson(data: StringMap<string, any>): CompileTypeMetadata {
return new TypeMetadata({id: data['id'], name: data['name'], moduleId: data['moduleId']}); return new CompileTypeMetadata(
{id: data['id'], name: data['name'], moduleId: data['moduleId']});
} }
toJson(): StringMap<string, any> { toJson(): StringMap<string, any> {
@ -33,169 +49,39 @@ export class TypeMetadata {
} }
} }
export class ChangeDetectionMetadata { export class CompileTemplateMetadata {
changeDetection: ChangeDetectionStrategy;
properties: string[];
events: string[];
hostListeners: StringMap<string, string>;
hostProperties: StringMap<string, string>;
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<string, string>,
hostProperties?: StringMap<string, string>,
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<string, any>): 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<string, any> {
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 {
encapsulation: ViewEncapsulation; encapsulation: ViewEncapsulation;
template: string; template: string;
templateUrl: string; templateUrl: string;
styles: string[]; styles: string[];
styleUrls: string[]; styleUrls: string[];
hostAttributes: StringMap<string, string>; ngContentSelectors: string[];
constructor({encapsulation, template, templateUrl, styles, styleUrls, hostAttributes}: { constructor({encapsulation, template, templateUrl, styles, styleUrls, ngContentSelectors}: {
encapsulation?: ViewEncapsulation, encapsulation?: ViewEncapsulation,
template?: string, template?: string,
templateUrl?: string, templateUrl?: string,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],
hostAttributes?: StringMap<string, string> ngContentSelectors?: string[]
} = {}) { } = {}) {
this.encapsulation = isPresent(encapsulation) ? encapsulation : ViewEncapsulation.None; this.encapsulation = encapsulation;
this.template = template; this.template = template;
this.templateUrl = templateUrl; this.templateUrl = templateUrl;
this.styles = isPresent(styles) ? styles : []; this.styles = isPresent(styles) ? styles : [];
this.styleUrls = isPresent(styleUrls) ? styleUrls : []; this.styleUrls = isPresent(styleUrls) ? styleUrls : [];
this.hostAttributes = isPresent(hostAttributes) ? hostAttributes : {}; this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : [];
}
}
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<string, string>;
constructor({encapsulation, template, styles, styleAbsUrls, ngContentSelectors, hostAttributes}: {
encapsulation?: ViewEncapsulation,
template?: string,
styles?: string[],
styleAbsUrls?: string[],
ngContentSelectors?: string[],
hostAttributes?: StringMap<string, string>
} = {}) {
this.encapsulation = encapsulation;
this.template = template;
this.styles = styles;
this.styleAbsUrls = styleAbsUrls;
this.ngContentSelectors = ngContentSelectors;
this.hostAttributes = hostAttributes;
} }
static fromJson(data: StringMap<string, any>): NormalizedTemplateMetadata { static fromJson(data: StringMap<string, any>): CompileTemplateMetadata {
return new NormalizedTemplateMetadata({ return new CompileTemplateMetadata({
encapsulation: isPresent(data['encapsulation']) ? encapsulation: isPresent(data['encapsulation']) ?
viewEncapsulationFromJson(data['encapsulation']) : VIEW_ENCAPSULATION_VALUES[data['encapsulation']] :
data['encapsulation'], data['encapsulation'],
template: data['template'], template: data['template'],
templateUrl: data['templateUrl'],
styles: data['styles'], styles: data['styles'],
styleAbsUrls: data['styleAbsUrls'], styleUrls: data['styleUrls'],
ngContentSelectors: data['ngContentSelectors'], ngContentSelectors: data['ngContentSelectors']
hostAttributes: data['hostAttributes']
}); });
} }
@ -204,51 +90,141 @@ export class NormalizedTemplateMetadata {
'encapsulation': 'encapsulation':
isPresent(this.encapsulation) ? serializeEnum(this.encapsulation) : this.encapsulation, isPresent(this.encapsulation) ? serializeEnum(this.encapsulation) : this.encapsulation,
'template': this.template, 'template': this.template,
'templateUrl': this.templateUrl,
'styles': this.styles, 'styles': this.styles,
'styleAbsUrls': this.styleAbsUrls, 'styleUrls': this.styleUrls,
'ngContentSelectors': this.ngContentSelectors, 'ngContentSelectors': this.ngContentSelectors
'hostAttributes': this.hostAttributes
}; };
} }
} }
export interface INormalizedDirectiveMetadata {} export class CompileDirectiveMetadata {
static create({type, isComponent, dynamicLoadable, selector, exportAs, changeDetection,
export class NormalizedDirectiveMetadata implements INormalizedDirectiveMetadata { properties, events, host, lifecycleHooks, template}: {
type: TypeMetadata; type?: CompileTypeMetadata,
isComponent: boolean;
dynamicLoadable: boolean;
selector: string;
changeDetection: ChangeDetectionMetadata;
template: NormalizedTemplateMetadata;
constructor({type, isComponent, dynamicLoadable, selector, changeDetection, template}: {
id?: number,
type?: TypeMetadata,
isComponent?: boolean, isComponent?: boolean,
dynamicLoadable?: boolean, dynamicLoadable?: boolean,
selector?: string, selector?: string,
changeDetection?: ChangeDetectionMetadata, exportAs?: string,
template?: NormalizedTemplateMetadata changeDetection?: ChangeDetectionStrategy,
properties?: string[],
events?: string[],
host?: StringMap<string, string>,
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<string, string>;
events: StringMap<string, string>;
hostListeners: StringMap<string, string>;
hostProperties: StringMap<string, string>;
hostAttributes: StringMap<string, string>;
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<string, string>,
events?: StringMap<string, string>,
hostListeners?: StringMap<string, string>,
hostProperties?: StringMap<string, string>,
hostAttributes?: StringMap<string, string>,
lifecycleHooks?: LifecycleHooks[],
template?: CompileTemplateMetadata
} = {}) { } = {}) {
this.type = type; this.type = type;
this.isComponent = normalizeBool(isComponent); this.isComponent = isComponent;
this.dynamicLoadable = normalizeBool(dynamicLoadable); this.dynamicLoadable = dynamicLoadable;
this.selector = selector; this.selector = selector;
this.exportAs = exportAs;
this.changeDetection = changeDetection; this.changeDetection = changeDetection;
this.properties = properties;
this.events = events;
this.hostListeners = hostListeners;
this.hostProperties = hostProperties;
this.hostAttributes = hostAttributes;
this.lifecycleHooks = lifecycleHooks;
this.template = template; this.template = template;
} }
static fromJson(data: StringMap<string, any>): NormalizedDirectiveMetadata { static fromJson(data: StringMap<string, any>): CompileDirectiveMetadata {
return new NormalizedDirectiveMetadata({ return new CompileDirectiveMetadata({
isComponent: data['isComponent'], isComponent: data['isComponent'],
dynamicLoadable: data['dynamicLoadable'], dynamicLoadable: data['dynamicLoadable'],
selector: data['selector'], 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']) ? changeDetection: isPresent(data['changeDetection']) ?
ChangeDetectionMetadata.fromJson(data['changeDetection']) : CHANGE_DECTION_STRATEGY_VALUES[data['changeDetection']] :
data['changeDetection'], data['changeDetection'],
template: properties: data['properties'],
isPresent(data['template']) ? NormalizedTemplateMetadata.fromJson(data['template']) : events: data['events'],
hostListeners: data['hostListeners'],
hostProperties: data['hostProperties'],
hostAttributes: data['hostAttributes'],
lifecycleHooks:
(<any[]>data['lifecycleHooks']).map(hookValue => LIFECYCLE_HOOKS_VALUES[hookValue]),
template: isPresent(data['template']) ? CompileTemplateMetadata.fromJson(data['template']) :
data['template'] data['template']
}); });
} }
@ -258,45 +234,38 @@ export class NormalizedDirectiveMetadata implements INormalizedDirectiveMetadata
'isComponent': this.isComponent, 'isComponent': this.isComponent,
'dynamicLoadable': this.dynamicLoadable, 'dynamicLoadable': this.dynamicLoadable,
'selector': this.selector, 'selector': this.selector,
'exportAs': this.exportAs,
'type': isPresent(this.type) ? this.type.toJson() : this.type, 'type': isPresent(this.type) ? this.type.toJson() : this.type,
'changeDetection': 'changeDetection': isPresent(this.changeDetection) ? serializeEnum(this.changeDetection) :
isPresent(this.changeDetection) ? this.changeDetection.toJson() : 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 'template': isPresent(this.template) ? this.template.toJson() : this.template
}; };
} }
} }
export function createHostComponentMeta(componentType: TypeMetadata, componentSelector: string): export function createHostComponentMeta(componentType: CompileTypeMetadata,
NormalizedDirectiveMetadata { componentSelector: string): CompileDirectiveMetadata {
var template = CssSelector.parse(componentSelector)[0].getMatchingElementTemplate(); var template = CssSelector.parse(componentSelector)[0].getMatchingElementTemplate();
return new NormalizedDirectiveMetadata({ return CompileDirectiveMetadata.create({
type: new TypeMetadata({ type: new CompileTypeMetadata({
runtime: Object, runtime: Object,
id: (componentType.id * -1) - 1, id: (componentType.id * -1) - 1,
name: `Host${componentType.name}`, name: `Host${componentType.name}`,
moduleId: componentType.moduleId moduleId: componentType.moduleId
}), }),
template: new NormalizedTemplateMetadata({ template: new CompileTemplateMetadata(
template: template, {template: template, templateUrl: '', styles: [], styleUrls: [], ngContentSelectors: []}),
styles: [],
styleAbsUrls: [],
hostAttributes: {},
ngContentSelectors: []
}),
changeDetection: new ChangeDetectionMetadata({
changeDetection: ChangeDetectionStrategy.Default, changeDetection: ChangeDetectionStrategy.Default,
properties: [], properties: [],
events: [], events: [],
hostListeners: {}, host: {},
hostProperties: {}, lifecycleHooks: [],
callAfterContentInit: false,
callAfterContentChecked: false,
callAfterViewInit: false,
callAfterViewChecked: false,
callOnChanges: false,
callDoCheck: false,
callOnInit: false
}),
isComponent: true, isComponent: true,
dynamicLoadable: false, dynamicLoadable: false,
selector: '*' selector: '*'

View File

@ -8,14 +8,14 @@ import {
RegExpWrapper RegExpWrapper
} from 'angular2/src/core/facade/lang'; } from 'angular2/src/core/facade/lang';
import {BaseException} from 'angular2/src/core/facade/exceptions'; 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 cpl from './directive_metadata';
import * as dirAnn from 'angular2/src/core/metadata/directives'; import * as dirAnn from 'angular2/src/core/metadata/directives';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {ViewResolver} from 'angular2/src/core/compiler/view_resolver'; import {ViewResolver} from 'angular2/src/core/compiler/view_resolver';
import {ViewMetadata} from 'angular2/src/core/metadata/view'; import {ViewMetadata} from 'angular2/src/core/metadata/view';
import {hasLifecycleHook} from 'angular2/src/core/compiler/directive_lifecycle_reflector'; 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 {reflector} from 'angular2/src/core/reflection/reflection';
import {Injectable} from 'angular2/src/core/di'; import {Injectable} from 'angular2/src/core/di';
@ -26,81 +26,55 @@ var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g;
@Injectable() @Injectable()
export class RuntimeMetadataResolver { export class RuntimeMetadataResolver {
private _directiveCounter = 0; private _directiveCounter = 0;
private _cache: Map<Type, cpl.DirectiveMetadata> = new Map(); private _cache: Map<Type, cpl.CompileDirectiveMetadata> = new Map();
constructor(private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver) {} constructor(private _directiveResolver: DirectiveResolver, private _viewResolver: ViewResolver) {}
getMetadata(directiveType: Type): cpl.DirectiveMetadata { getMetadata(directiveType: Type): cpl.CompileDirectiveMetadata {
var meta = this._cache.get(directiveType); var meta = this._cache.get(directiveType);
if (isBlank(meta)) { if (isBlank(meta)) {
var directiveAnnotation = this._directiveResolver.resolve(directiveType); var directiveAnnotation = this._directiveResolver.resolve(directiveType);
var moduleId = calcModuleId(directiveType, directiveAnnotation); var moduleId = calcModuleId(directiveType, directiveAnnotation);
var templateMeta = null; var templateMeta = null;
var hostListeners = {};
var hostProperties = {};
var hostAttributes = {};
var changeDetectionStrategy = null; 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) { if (directiveAnnotation instanceof dirAnn.ComponentMetadata) {
var compAnnotation = <dirAnn.ComponentMetadata>directiveAnnotation; var compAnnotation = <dirAnn.ComponentMetadata>directiveAnnotation;
var viewAnnotation = this._viewResolver.resolve(directiveType); var viewAnnotation = this._viewResolver.resolve(directiveType);
templateMeta = new cpl.TemplateMetadata({ templateMeta = new cpl.CompileTemplateMetadata({
encapsulation: viewAnnotation.encapsulation, encapsulation: viewAnnotation.encapsulation,
template: viewAnnotation.template, template: viewAnnotation.template,
templateUrl: viewAnnotation.templateUrl, templateUrl: viewAnnotation.templateUrl,
styles: viewAnnotation.styles, styles: viewAnnotation.styles,
styleUrls: viewAnnotation.styleUrls, styleUrls: viewAnnotation.styleUrls
hostAttributes: hostAttributes
}); });
changeDetectionStrategy = compAnnotation.changeDetection; changeDetectionStrategy = compAnnotation.changeDetection;
dynamicLoadable = compAnnotation.dynamicLoadable;
} }
meta = new cpl.DirectiveMetadata({ meta = cpl.CompileDirectiveMetadata.create({
selector: directiveAnnotation.selector, selector: directiveAnnotation.selector,
exportAs: directiveAnnotation.exportAs,
isComponent: isPresent(templateMeta), isComponent: isPresent(templateMeta),
dynamicLoadable: dynamicLoadable, dynamicLoadable: true,
type: new cpl.TypeMetadata({ type: new cpl.CompileTypeMetadata({
id: this._directiveCounter++, id: this._directiveCounter++,
name: stringify(directiveType), name: stringify(directiveType),
moduleId: moduleId, moduleId: moduleId,
runtime: directiveType runtime: directiveType
}), }),
template: templateMeta, template: templateMeta,
changeDetection: new cpl.ChangeDetectionMetadata({
changeDetection: changeDetectionStrategy, changeDetection: changeDetectionStrategy,
properties: directiveAnnotation.properties, properties: directiveAnnotation.properties,
events: directiveAnnotation.events, events: directiveAnnotation.events,
hostListeners: hostListeners, host: directiveAnnotation.host,
hostProperties: hostProperties, lifecycleHooks: ListWrapper.filter(LIFECYCLE_HOOKS_VALUES,
callAfterContentInit: hasLifecycleHook(LifecycleHooks.AfterContentInit, directiveType), hook => hasLifecycleHook(hook, 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),
})
}); });
this._cache.set(directiveType, meta); this._cache.set(directiveType, meta);
} }
return meta; return meta;
} }
getViewDirectivesMetadata(component: Type): cpl.DirectiveMetadata[] { getViewDirectivesMetadata(component: Type): cpl.CompileDirectiveMetadata[] {
var view = this._viewResolver.resolve(component); var view = this._viewResolver.resolve(component);
var directives = flattenDirectives(view); var directives = flattenDirectives(view);
for (var i = 0; i < directives.length; i++) { for (var i = 0; i < directives.length; i++) {
@ -113,8 +87,9 @@ export class RuntimeMetadataResolver {
} }
} }
function removeDuplicatedDirectives(directives: cpl.DirectiveMetadata[]): cpl.DirectiveMetadata[] { function removeDuplicatedDirectives(directives: cpl.CompileDirectiveMetadata[]):
var directivesMap: Map<number, cpl.DirectiveMetadata> = new Map(); cpl.CompileDirectiveMetadata[] {
var directivesMap: Map<number, cpl.CompileDirectiveMetadata> = new Map();
directives.forEach((dirMeta) => { directivesMap.set(dirMeta.type.id, dirMeta); }); directives.forEach((dirMeta) => { directivesMap.set(dirMeta.type.id, dirMeta); });
return MapWrapper.values(directivesMap); return MapWrapper.values(directivesMap);
} }

View File

@ -35,6 +35,10 @@ export class SourceExpression {
constructor(public declarations: string[], public expression: string) {} constructor(public declarations: string[], public expression: string) {}
} }
export class SourceExpressions {
constructor(public declarations: string[], public expressions: string[]) {}
}
export class SourceWithImports { export class SourceWithImports {
constructor(public source: string, public imports: string[][]) {} constructor(public source: string, public imports: string[][]) {}
} }

View File

@ -1,4 +1,4 @@
import {TypeMetadata, NormalizedDirectiveMetadata} from './directive_metadata'; import {CompileTypeMetadata, CompileDirectiveMetadata} from './directive_metadata';
import {SourceModule, SourceExpression, moduleRef} from './source_module'; import {SourceModule, SourceExpression, moduleRef} from './source_module';
import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {ViewEncapsulation} from 'angular2/src/core/render/api';
import {XHR} from 'angular2/src/core/render/xhr'; import {XHR} from 'angular2/src/core/render/xhr';
@ -29,16 +29,16 @@ export class StyleCompiler {
constructor(private _xhr: XHR, private _urlResolver: UrlResolver) {} constructor(private _xhr: XHR, private _urlResolver: UrlResolver) {}
compileComponentRuntime(component: NormalizedDirectiveMetadata): Promise<string[]> { compileComponentRuntime(component: CompileDirectiveMetadata): Promise<string[]> {
var styles = component.template.styles; var styles = component.template.styles;
var styleAbsUrls = component.template.styleAbsUrls; var styleAbsUrls = component.template.styleUrls;
return this._loadStyles(styles, styleAbsUrls, return this._loadStyles(styles, styleAbsUrls,
component.template.encapsulation === ViewEncapsulation.Emulated) component.template.encapsulation === ViewEncapsulation.Emulated)
.then(styles => styles.map(style => StringWrapper.replaceAll(style, COMPONENT_REGEX, .then(styles => styles.map(style => StringWrapper.replaceAll(style, COMPONENT_REGEX,
`${component.type.id}`))); `${component.type.id}`)));
} }
compileComponentCodeGen(component: NormalizedDirectiveMetadata): SourceExpression { compileComponentCodeGen(component: CompileDirectiveMetadata): SourceExpression {
var shim = component.template.encapsulation === ViewEncapsulation.Emulated; var shim = component.template.encapsulation === ViewEncapsulation.Emulated;
var suffix; var suffix;
if (shim) { if (shim) {
@ -48,7 +48,7 @@ export class StyleCompiler {
} else { } else {
suffix = ''; suffix = '';
} }
return this._styleCodeGen(component.template.styles, component.template.styleAbsUrls, shim, return this._styleCodeGen(component.template.styles, component.template.styleUrls, shim,
suffix); suffix);
} }
@ -62,6 +62,8 @@ export class StyleCompiler {
]; ];
} }
clearCache() { this._styleCache.clear(); }
private _loadStyles(plainStyles: string[], absUrls: string[], private _loadStyles(plainStyles: string[], absUrls: string[],
encapsulate: boolean): Promise<string[]> { encapsulate: boolean): Promise<string[]> {
var promises = absUrls.map((absUrl) => { var promises = absUrls.map((absUrl) => {

View File

@ -1,6 +1,6 @@
import {AST} from 'angular2/src/core/change_detection/change_detection'; import {AST} from 'angular2/src/core/change_detection/change_detection';
import {isPresent} from 'angular2/src/core/facade/lang'; import {isPresent} from 'angular2/src/core/facade/lang';
import {NormalizedDirectiveMetadata} from './directive_metadata'; import {CompileDirectiveMetadata} from './directive_metadata';
export interface TemplateAst { export interface TemplateAst {
sourceInfo: string; sourceInfo: string;
@ -38,7 +38,7 @@ export class BoundEventAst implements TemplateAst {
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitEvent(this, context); return visitor.visitEvent(this, context);
} }
getFullName(): string { get fullName() {
if (isPresent(this.target)) { if (isPresent(this.target)) {
return `${this.target}:${this.name}`; return `${this.target}:${this.name}`;
} else { } else {
@ -57,7 +57,7 @@ export class VariableAst implements TemplateAst {
export class ElementAst implements TemplateAst { export class ElementAst implements TemplateAst {
constructor(public name: string, public attrs: AttrAst[], constructor(public name: string, public attrs: AttrAst[],
public properties: BoundElementPropertyAst[], public events: BoundEventAst[], 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 children: TemplateAst[], public ngContentIndex: number,
public sourceInfo: string) {} public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {
@ -65,11 +65,11 @@ export class ElementAst implements TemplateAst {
} }
isBound(): boolean { 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); this.directives.length > 0);
} }
getComponent(): NormalizedDirectiveMetadata { getComponent(): CompileDirectiveMetadata {
return this.directives.length > 0 && this.directives[0].directive.isComponent ? return this.directives.length > 0 && this.directives[0].directive.isComponent ?
this.directives[0].directive : this.directives[0].directive :
null; null;
@ -94,10 +94,10 @@ export class BoundDirectivePropertyAst implements TemplateAst {
} }
export class DirectiveAst implements TemplateAst { export class DirectiveAst implements TemplateAst {
constructor(public directive: NormalizedDirectiveMetadata, constructor(public directive: CompileDirectiveMetadata,
public properties: BoundDirectivePropertyAst[], public properties: BoundDirectivePropertyAst[],
public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[], public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[],
public sourceInfo: string) {} public exportAsVars: VariableAst[], public sourceInfo: string) {}
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitDirective(this, context); return visitor.visitDirective(this, context);
} }

View File

@ -1,16 +1,12 @@
import {Type, Json, isBlank, stringify} from 'angular2/src/core/facade/lang'; 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 {ListWrapper, SetWrapper} from 'angular2/src/core/facade/collection';
import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async'; import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async';
import {CompiledTemplate, TemplateCmd} from 'angular2/src/core/compiler/template_commands'; import {CompiledTemplate, TemplateCmd} from 'angular2/src/core/compiler/template_commands';
import { import {
createHostComponentMeta, createHostComponentMeta,
DirectiveMetadata, CompileDirectiveMetadata,
INormalizedDirectiveMetadata, CompileTypeMetadata,
NormalizedDirectiveMetadata, CompileTemplateMetadata
TypeMetadata,
ChangeDetectionMetadata,
NormalizedTemplateMetadata
} from './directive_metadata'; } from './directive_metadata';
import {TemplateAst} from './template_ast'; import {TemplateAst} from './template_ast';
import {Injectable} from 'angular2/src/core/di'; import {Injectable} from 'angular2/src/core/di';
@ -36,7 +32,8 @@ export class TemplateCompiler {
private _commandCompiler: CommandCompiler, private _commandCompiler: CommandCompiler,
private _cdCompiler: ChangeDetectionCompiler) {} private _cdCompiler: ChangeDetectionCompiler) {}
normalizeDirectiveMetadata(directive: DirectiveMetadata): Promise<INormalizedDirectiveMetadata> { normalizeDirectiveMetadata(directive:
CompileDirectiveMetadata): Promise<CompileDirectiveMetadata> {
var normalizedTemplatePromise; var normalizedTemplatePromise;
if (directive.isComponent) { if (directive.isComponent) {
normalizedTemplatePromise = normalizedTemplatePromise =
@ -45,49 +42,58 @@ export class TemplateCompiler {
normalizedTemplatePromise = PromiseWrapper.resolve(null); normalizedTemplatePromise = PromiseWrapper.resolve(null);
} }
return normalizedTemplatePromise.then( return normalizedTemplatePromise.then(
(normalizedTemplate) => new NormalizedDirectiveMetadata({ (normalizedTemplate) => new CompileDirectiveMetadata({
selector: directive.selector,
dynamicLoadable: directive.dynamicLoadable,
isComponent: directive.isComponent,
type: directive.type, 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 { serializeDirectiveMetadata(metadata: CompileDirectiveMetadata): string {
return Json.stringify((<NormalizedDirectiveMetadata>metadata).toJson()); return Json.stringify(metadata.toJson());
} }
deserializeDirectiveMetadata(data: string): INormalizedDirectiveMetadata { deserializeDirectiveMetadata(data: string): CompileDirectiveMetadata {
return NormalizedDirectiveMetadata.fromJson(Json.parse(data)); return CompileDirectiveMetadata.fromJson(Json.parse(data));
} }
compileHostComponentRuntime(type: Type): Promise<CompiledTemplate> { compileHostComponentRuntime(type: Type): Promise<CompiledTemplate> {
var compMeta: DirectiveMetadata = this._runtimeMetadataResolver.getMetadata(type); var compMeta: CompileDirectiveMetadata = this._runtimeMetadataResolver.getMetadata(type);
if (isBlank(compMeta) || !compMeta.isComponent || !compMeta.dynamicLoadable) { var hostMeta: CompileDirectiveMetadata =
throw new BaseException(
`Could not compile '${stringify(type)}' because it is not dynamically loadable.`);
}
var hostMeta: NormalizedDirectiveMetadata =
createHostComponentMeta(compMeta.type, compMeta.selector); createHostComponentMeta(compMeta.type, compMeta.selector);
this._compileComponentRuntime(hostMeta, [compMeta], new Set()); this._compileComponentRuntime(hostMeta, [compMeta], new Set());
return this._compiledTemplateDone.get(hostMeta.type.id); return this._compiledTemplateDone.get(hostMeta.type.id);
} }
private _compileComponentRuntime(compMeta: NormalizedDirectiveMetadata, clearCache() {
viewDirectives: DirectiveMetadata[], this._styleCompiler.clearCache();
this._compiledTemplateCache.clear();
this._compiledTemplateDone.clear();
}
private _compileComponentRuntime(compMeta: CompileDirectiveMetadata,
viewDirectives: CompileDirectiveMetadata[],
compilingComponentIds: Set<number>): CompiledTemplate { compilingComponentIds: Set<number>): CompiledTemplate {
var compiledTemplate = this._compiledTemplateCache.get(compMeta.type.id); var compiledTemplate = this._compiledTemplateCache.get(compMeta.type.id);
var done = this._compiledTemplateDone.get(compMeta.type.id); var done = this._compiledTemplateDone.get(compMeta.type.id);
if (isBlank(compiledTemplate)) { if (isBlank(compiledTemplate)) {
var styles; var styles;
var changeDetectorFactories; var changeDetectorFactory;
var commands; var commands;
compiledTemplate = compiledTemplate =
new CompiledTemplate(compMeta.type.id, () => [changeDetectorFactories, commands, styles]); new CompiledTemplate(compMeta.type.id, () => [changeDetectorFactory, commands, styles]);
this._compiledTemplateCache.set(compMeta.type.id, compiledTemplate); this._compiledTemplateCache.set(compMeta.type.id, compiledTemplate);
compilingComponentIds.add(compMeta.type.id); compilingComponentIds.add(compMeta.type.id);
done = PromiseWrapper.all([this._styleCompiler.compileComponentRuntime(compMeta)].concat( done = PromiseWrapper.all([<any>this._styleCompiler.compileComponentRuntime(compMeta)].concat(
viewDirectives.map( viewDirectives.map(
dirMeta => this.normalizeDirectiveMetadata(dirMeta)))) dirMeta => this.normalizeDirectiveMetadata(dirMeta))))
.then((stylesAndNormalizedViewDirMetas: any[]) => { .then((stylesAndNormalizedViewDirMetas: any[]) => {
@ -96,10 +102,12 @@ export class TemplateCompiler {
var parsedTemplate = this._templateParser.parse( var parsedTemplate = this._templateParser.parse(
compMeta.template.template, normalizedViewDirMetas, compMeta.type.name); compMeta.template.template, normalizedViewDirMetas, compMeta.type.name);
changeDetectorFactories = this._cdCompiler.compileComponentRuntime( var changeDetectorFactories = this._cdCompiler.compileComponentRuntime(
compMeta.type, compMeta.changeDetection.changeDetection, parsedTemplate); compMeta.type, compMeta.changeDetection, parsedTemplate);
changeDetectorFactory = changeDetectorFactories[0];
styles = stylesAndNormalizedViewDirMetas[0]; styles = stylesAndNormalizedViewDirMetas[0];
commands = this._compileCommandsRuntime(compMeta, parsedTemplate, commands = this._compileCommandsRuntime(compMeta, parsedTemplate,
changeDetectorFactories,
compilingComponentIds, childPromises); compilingComponentIds, childPromises);
return PromiseWrapper.all(childPromises); return PromiseWrapper.all(childPromises);
}) })
@ -112,12 +120,14 @@ export class TemplateCompiler {
return compiledTemplate; return compiledTemplate;
} }
private _compileCommandsRuntime(compMeta: NormalizedDirectiveMetadata, private _compileCommandsRuntime(compMeta: CompileDirectiveMetadata, parsedTemplate: TemplateAst[],
parsedTemplate: TemplateAst[], compilingComponentIds: Set<number>, changeDetectorFactories: Function[],
compilingComponentIds: Set<number>,
childPromises: Promise<any>[]): TemplateCmd[] { childPromises: Promise<any>[]): TemplateCmd[] {
return this._commandCompiler.compileComponentRuntime( return this._commandCompiler.compileComponentRuntime(
compMeta, parsedTemplate, (childComponentDir: NormalizedDirectiveMetadata) => { compMeta, parsedTemplate, changeDetectorFactories,
var childViewDirectives: DirectiveMetadata[] = (childComponentDir: CompileDirectiveMetadata) => {
var childViewDirectives: CompileDirectiveMetadata[] =
this._runtimeMetadataResolver.getViewDirectivesMetadata( this._runtimeMetadataResolver.getViewDirectivesMetadata(
childComponentDir.type.runtime); childComponentDir.type.runtime);
var childIsRecursive = SetWrapper.has(compilingComponentIds, childComponentDir.type.id); var childIsRecursive = SetWrapper.has(compilingComponentIds, childComponentDir.type.id);
@ -135,12 +145,12 @@ export class TemplateCompiler {
components: NormalizedComponentWithViewDirectives[]): SourceModule { components: NormalizedComponentWithViewDirectives[]): SourceModule {
var declarations = []; var declarations = [];
var templateArguments = []; var templateArguments = [];
var componentMetas: NormalizedDirectiveMetadata[] = []; var componentMetas: CompileDirectiveMetadata[] = [];
components.forEach(componentWithDirs => { components.forEach(componentWithDirs => {
var compMeta = <NormalizedDirectiveMetadata>componentWithDirs.component; var compMeta = <CompileDirectiveMetadata>componentWithDirs.component;
componentMetas.push(compMeta); componentMetas.push(compMeta);
this._processTemplateCodeGen(compMeta, this._processTemplateCodeGen(compMeta,
<NormalizedDirectiveMetadata[]>componentWithDirs.directives, <CompileDirectiveMetadata[]>componentWithDirs.directives,
declarations, templateArguments); declarations, templateArguments);
if (compMeta.dynamicLoadable) { if (compMeta.dynamicLoadable) {
var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector); var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector);
@ -148,7 +158,7 @@ export class TemplateCompiler {
this._processTemplateCodeGen(hostMeta, [compMeta], declarations, templateArguments); this._processTemplateCodeGen(hostMeta, [compMeta], declarations, templateArguments);
} }
}); });
ListWrapper.forEachWithIndex(componentMetas, (compMeta: NormalizedDirectiveMetadata, ListWrapper.forEachWithIndex(componentMetas, (compMeta: CompileDirectiveMetadata,
index: number) => { index: number) => {
var templateDataFn = codeGenValueFn([], `[${templateArguments[index].join(',')}]`); var templateDataFn = codeGenValueFn([], `[${templateArguments[index].join(',')}]`);
declarations.push( declarations.push(
@ -161,32 +171,33 @@ export class TemplateCompiler {
return this._styleCompiler.compileStylesheetCodeGen(moduleId, cssText); return this._styleCompiler.compileStylesheetCodeGen(moduleId, cssText);
} }
private _processTemplateCodeGen(compMeta: NormalizedDirectiveMetadata, private _processTemplateCodeGen(compMeta: CompileDirectiveMetadata,
directives: NormalizedDirectiveMetadata[], directives: CompileDirectiveMetadata[],
targetDeclarations: string[], targetTemplateArguments: any[][]) { targetDeclarations: string[], targetTemplateArguments: any[][]) {
var styleExpr = this._styleCompiler.compileComponentCodeGen(compMeta); var styleExpr = this._styleCompiler.compileComponentCodeGen(compMeta);
var parsedTemplate = var parsedTemplate =
this._templateParser.parse(compMeta.template.template, directives, compMeta.type.name); this._templateParser.parse(compMeta.template.template, directives, compMeta.type.name);
var changeDetectorsExpr = this._cdCompiler.compileComponentCodeGen( var changeDetectorsExprs = this._cdCompiler.compileComponentCodeGen(
compMeta.type, compMeta.changeDetection.changeDetection, parsedTemplate); compMeta.type, compMeta.changeDetection, parsedTemplate);
var commandsExpr = this._commandCompiler.compileComponentCodeGen( var commandsExpr = this._commandCompiler.compileComponentCodeGen(
compMeta, parsedTemplate, codeGenComponentTemplateFactory); compMeta, parsedTemplate, changeDetectorsExprs.expressions,
codeGenComponentTemplateFactory);
addAll(styleExpr.declarations, targetDeclarations); addAll(styleExpr.declarations, targetDeclarations);
addAll(changeDetectorsExpr.declarations, targetDeclarations); addAll(changeDetectorsExprs.declarations, targetDeclarations);
addAll(commandsExpr.declarations, targetDeclarations); addAll(commandsExpr.declarations, targetDeclarations);
targetTemplateArguments.push( targetTemplateArguments.push(
[changeDetectorsExpr.expression, commandsExpr.expression, styleExpr.expression]); [changeDetectorsExprs.expressions[0], commandsExpr.expression, styleExpr.expression]);
} }
} }
export class NormalizedComponentWithViewDirectives { export class NormalizedComponentWithViewDirectives {
constructor(public component: INormalizedDirectiveMetadata, constructor(public component: CompileDirectiveMetadata,
public directives: INormalizedDirectiveMetadata[]) {} public directives: CompileDirectiveMetadata[]) {}
} }
function templateVariableName(type: TypeMetadata): string { function templateVariableName(type: CompileTypeMetadata): string {
return `${type.name}Template`; 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)}`; return `${moduleRef(templateModuleName(nestedCompType.type.moduleId))}${templateVariableName(nestedCompType.type)}`;
} }

View File

@ -1,8 +1,7 @@
import { import {
TypeMetadata, CompileTypeMetadata,
TemplateMetadata, CompileDirectiveMetadata,
NormalizedDirectiveMetadata, CompileTemplateMetadata
NormalizedTemplateMetadata
} from './directive_metadata'; } from './directive_metadata';
import {isPresent, isBlank} from 'angular2/src/core/facade/lang'; import {isPresent, isBlank} from 'angular2/src/core/facade/lang';
import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async'; 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 {UrlResolver} from 'angular2/src/core/services/url_resolver';
import {resolveStyleUrls} from './style_url_resolver'; import {resolveStyleUrls} from './style_url_resolver';
import {Injectable} from 'angular2/src/core/di'; import {Injectable} from 'angular2/src/core/di';
import {ViewEncapsulation} from 'angular2/src/core/render/api';
import { import {
HtmlAstVisitor, HtmlAstVisitor,
@ -22,21 +22,15 @@ import {
} from './html_ast'; } from './html_ast';
import {HtmlParser} from './html_parser'; import {HtmlParser} from './html_parser';
const NG_CONTENT_SELECT_ATTR = 'select'; import {preparseElement, PreparsedElement, PreparsedElementType} from './template_preparser';
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';
@Injectable() @Injectable()
export class TemplateNormalizer { export class TemplateNormalizer {
constructor(private _xhr: XHR, private _urlResolver: UrlResolver, constructor(private _xhr: XHR, private _urlResolver: UrlResolver,
private _domParser: HtmlParser) {} private _domParser: HtmlParser) {}
normalizeTemplate(directiveType: TypeMetadata, normalizeTemplate(directiveType: CompileTypeMetadata,
template: TemplateMetadata): Promise<NormalizedTemplateMetadata> { template: CompileTemplateMetadata): Promise<CompileTemplateMetadata> {
if (isPresent(template.template)) { if (isPresent(template.template)) {
return PromiseWrapper.resolve(this.normalizeLoadedTemplate( return PromiseWrapper.resolve(this.normalizeLoadedTemplate(
directiveType, template, template.template, directiveType.moduleId)); directiveType, template, template.template, directiveType.moduleId));
@ -48,11 +42,11 @@ export class TemplateNormalizer {
} }
} }
normalizeLoadedTemplate(directiveType: TypeMetadata, templateMeta: TemplateMetadata, normalizeLoadedTemplate(directiveType: CompileTypeMetadata, templateMeta: CompileTemplateMetadata,
template: string, templateAbsUrl: string): NormalizedTemplateMetadata { template: string, templateAbsUrl: string): CompileTemplateMetadata {
var domNodes = this._domParser.parse(template, directiveType.name); var domNodes = this._domParser.parse(template, directiveType.name);
var visitor = new TemplatePreparseVisitor(); var visitor = new TemplatePreparseVisitor();
var remainingNodes = htmlVisitAll(visitor, domNodes); htmlVisitAll(visitor, domNodes);
var allStyles = templateMeta.styles.concat(visitor.styles); var allStyles = templateMeta.styles.concat(visitor.styles);
var allStyleAbsUrls = var allStyleAbsUrls =
@ -65,11 +59,17 @@ export class TemplateNormalizer {
styleWithImports.styleUrls.forEach(styleUrl => allStyleAbsUrls.push(styleUrl)); styleWithImports.styleUrls.forEach(styleUrl => allStyleAbsUrls.push(styleUrl));
return styleWithImports.style; return styleWithImports.style;
}); });
return new NormalizedTemplateMetadata({ var encapsulation = templateMeta.encapsulation;
encapsulation: templateMeta.encapsulation, if (encapsulation === ViewEncapsulation.Emulated && allResolvedStyles.length === 0 &&
template: this._domParser.unparse(remainingNodes), allStyleAbsUrls.length === 0) {
encapsulation = ViewEncapsulation.None;
}
return new CompileTemplateMetadata({
encapsulation: encapsulation,
template: template,
templateUrl: templateAbsUrl,
styles: allResolvedStyles, styles: allResolvedStyles,
styleAbsUrls: allStyleAbsUrls, styleUrls: allStyleAbsUrls,
ngContentSelectors: visitor.ngContentSelectors ngContentSelectors: visitor.ngContentSelectors
}); });
} }
@ -80,25 +80,13 @@ class TemplatePreparseVisitor implements HtmlAstVisitor {
styles: string[] = []; styles: string[] = [];
styleUrls: string[] = []; styleUrls: string[] = [];
visitElement(ast: HtmlElementAst, context: any): HtmlElementAst { visitElement(ast: HtmlElementAst, context: any): any {
var selectAttr = null; var preparsedElement = preparseElement(ast);
var hrefAttr = null; switch (preparsedElement.type) {
var relAttr = null; case PreparsedElementType.NG_CONTENT:
ast.attrs.forEach(attr => { this.ngContentSelectors.push(preparsedElement.selectAttr);
if (attr.name == NG_CONTENT_SELECT_ATTR) { break;
selectAttr = attr.value; case PreparsedElementType.STYLE:
} 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 = ''; var textContent = '';
ast.children.forEach(child => { ast.children.forEach(child => {
if (child instanceof HtmlTextAst) { if (child instanceof HtmlTextAst) {
@ -106,24 +94,16 @@ class TemplatePreparseVisitor implements HtmlAstVisitor {
} }
}); });
this.styles.push(textContent); this.styles.push(textContent);
} else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) { break;
keepElement = false; case PreparsedElementType.STYLESHEET:
this.styleUrls.push(hrefAttr); this.styleUrls.push(preparsedElement.hrefAttr);
break;
}
if (preparsedElement.type !== PreparsedElementType.NON_BINDABLE) {
htmlVisitAll(this, ast.children);
} }
if (keepElement) {
return new HtmlElementAst(ast.name, ast.attrs, htmlVisitAll(this, ast.children),
ast.sourceInfo);
} else {
return null; return null;
} }
} visitAttr(ast: HtmlAttrAst, context: any): any { return null; }
visitAttr(ast: HtmlAttrAst, context: any): HtmlAttrAst { return ast; } visitText(ast: HtmlTextAst, context: any): any { return null; }
visitText(ast: HtmlTextAst, context: any): HtmlTextAst { return ast; }
}
function normalizeNgContentSelect(selectAttr: string): string {
if (isBlank(selectAttr) || selectAttr.length === 0) {
return '*';
}
return selectAttr;
} }

View File

@ -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 { import {
RegExpWrapper, RegExpWrapper,
isPresent, isPresent,
@ -12,7 +17,7 @@ import {Injectable} from 'angular2/src/core/di';
import {BaseException} from 'angular2/src/core/facade/exceptions'; import {BaseException} from 'angular2/src/core/facade/exceptions';
import {Parser, AST, ASTWithSource} from 'angular2/src/core/change_detection/change_detection'; import {Parser, AST, ASTWithSource} from 'angular2/src/core/change_detection/change_detection';
import {TemplateBinding} from 'angular2/src/core/change_detection/parser/ast'; 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 {HtmlParser} from './html_parser';
import { import {
@ -33,6 +38,7 @@ import {
import {CssSelector, SelectorMatcher} from 'angular2/src/core/render/dom/compiler/selector'; import {CssSelector, SelectorMatcher} from 'angular2/src/core/render/dom/compiler/selector';
import {ElementSchemaRegistry} from 'angular2/src/core/render/dom/schema/element_schema_registry'; import {ElementSchemaRegistry} from 'angular2/src/core/render/dom/schema/element_schema_registry';
import {preparseElement, PreparsedElement, PreparsedElementType} from './template_preparser';
import { import {
HtmlAstVisitor, HtmlAstVisitor,
@ -43,7 +49,7 @@ import {
htmlVisitAll htmlVisitAll
} from './html_ast'; } from './html_ast';
import {dashCaseToCamelCase, camelCaseToDashCase} from './util'; import {dashCaseToCamelCase, camelCaseToDashCase, splitAtColon} from './util';
// Group 1 = "bind-" // Group 1 = "bind-"
// Group 2 = "var-" or "#" // Group 2 = "var-" or "#"
@ -56,12 +62,10 @@ import {dashCaseToCamelCase, camelCaseToDashCase} from './util';
var BIND_NAME_REGEXP = var BIND_NAME_REGEXP =
/^(?:(?:(?:(bind-)|(var-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g; /^(?:(?:(?:(bind-)|(var-|#)|(on-)|(bindon-))(.+))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/g;
const NG_CONTENT_ELEMENT = 'ng-content';
const TEMPLATE_ELEMENT = 'template'; const TEMPLATE_ELEMENT = 'template';
const TEMPLATE_ATTR = 'template'; const TEMPLATE_ATTR = 'template';
const TEMPLATE_ATTR_PREFIX = '*'; const TEMPLATE_ATTR_PREFIX = '*';
const CLASS_ATTR = 'class'; const CLASS_ATTR = 'class';
const IMPLICIT_VAR_NAME = '$implicit';
var PROPERTY_PARTS_SEPARATOR = new RegExp('\\.'); var PROPERTY_PARTS_SEPARATOR = new RegExp('\\.');
const ATTRIBUTE_PREFIX = 'attr'; const ATTRIBUTE_PREFIX = 'attr';
@ -75,7 +79,7 @@ export class TemplateParser {
constructor(private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry, constructor(private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry,
private _htmlParser: HtmlParser) {} private _htmlParser: HtmlParser) {}
parse(template: string, directives: NormalizedDirectiveMetadata[], parse(template: string, directives: CompileDirectiveMetadata[],
sourceInfo: string): TemplateAst[] { sourceInfo: string): TemplateAst[] {
var parseVisitor = new TemplateParseVisitor(directives, this._exprParser, this._schemaRegistry); var parseVisitor = new TemplateParseVisitor(directives, this._exprParser, this._schemaRegistry);
var result = var result =
@ -91,12 +95,15 @@ export class TemplateParser {
class TemplateParseVisitor implements HtmlAstVisitor { class TemplateParseVisitor implements HtmlAstVisitor {
selectorMatcher: SelectorMatcher; selectorMatcher: SelectorMatcher;
errors: string[] = []; errors: string[] = [];
constructor(directives: NormalizedDirectiveMetadata[], private _exprParser: Parser, directivesIndexByTypeId: Map<number, number> = new Map();
constructor(directives: CompileDirectiveMetadata[], private _exprParser: Parser,
private _schemaRegistry: ElementSchemaRegistry) { private _schemaRegistry: ElementSchemaRegistry) {
this.selectorMatcher = new SelectorMatcher(); this.selectorMatcher = new SelectorMatcher();
directives.forEach(directive => { ListWrapper.forEachWithIndex(directives,
(directive: CompileDirectiveMetadata, index: number) => {
var selector = CssSelector.parse(directive.selector); var selector = CssSelector.parse(directive.selector);
this.selectorMatcher.addSelectables(selector, directive); this.selectorMatcher.addSelectables(selector, directive);
this.directivesIndexByTypeId.set(directive.type.id, index);
}); });
} }
@ -154,6 +161,17 @@ class TemplateParseVisitor implements HtmlAstVisitor {
visitElement(element: HtmlElementAst, component: Component): any { visitElement(element: HtmlElementAst, component: Component): any {
var nodeName = element.name; 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 <script> for security reasons
// Skipping <style> and stylesheets as we already processed them
// in the StyleCompiler
return null;
}
var matchableAttrs: string[][] = []; var matchableAttrs: string[][] = [];
var elementOrDirectiveProps: BoundElementOrDirectiveProperty[] = []; var elementOrDirectiveProps: BoundElementOrDirectiveProperty[] = [];
var vars: VariableAst[] = []; var vars: VariableAst[] = [];
@ -177,26 +195,29 @@ class TemplateParseVisitor implements HtmlAstVisitor {
hasInlineTemplates = true; hasInlineTemplates = true;
} }
}); });
var isTemplateElement = nodeName == TEMPLATE_ELEMENT;
var elementCssSelector = this._createElementCssSelector(nodeName, matchableAttrs); var elementCssSelector = this._createElementCssSelector(nodeName, matchableAttrs);
var directives = this._createDirectiveAsts( var directives = this._createDirectiveAsts(
element.name, this._parseDirectives(this.selectorMatcher, elementCssSelector), element.name, this._parseDirectives(this.selectorMatcher, elementCssSelector),
elementOrDirectiveProps, element.sourceInfo); elementOrDirectiveProps, isTemplateElement ? [] : vars, element.sourceInfo);
var elementProps: BoundElementPropertyAst[] = var elementProps: BoundElementPropertyAst[] =
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directives); this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directives);
var children = htmlVisitAll(this, element.children, Component.create(directives)); var children = htmlVisitAll(this, element.children, Component.create(directives));
var elementNgContentIndex = var elementNgContentIndex =
hasInlineTemplates ? null : component.findNgContentIndex(elementCssSelector); hasInlineTemplates ? null : component.findNgContentIndex(elementCssSelector);
var parsedElement; var parsedElement;
if (nodeName == NG_CONTENT_ELEMENT) { if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
parsedElement = new NgContentAst(elementNgContentIndex, element.sourceInfo); parsedElement = new NgContentAst(elementNgContentIndex, element.sourceInfo);
} else if (nodeName == TEMPLATE_ELEMENT) { } else if (isTemplateElement) {
this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps, events, this._assertNoComponentsNorElementBindingsOnTemplate(directives, elementProps, events,
element.sourceInfo); element.sourceInfo);
parsedElement = new EmbeddedTemplateAst(attrs, vars, directives, children, parsedElement = new EmbeddedTemplateAst(attrs, vars, directives, children,
elementNgContentIndex, element.sourceInfo); elementNgContentIndex, element.sourceInfo);
} else { } else {
this._assertOnlyOneComponent(directives, element.sourceInfo); this._assertOnlyOneComponent(directives, element.sourceInfo);
parsedElement = new ElementAst(nodeName, attrs, elementProps, events, vars, directives, var elementExportAsVars = ListWrapper.filter(vars, varAst => varAst.value.length === 0);
parsedElement =
new ElementAst(nodeName, attrs, elementProps, events, elementExportAsVars, directives,
children, elementNgContentIndex, element.sourceInfo); children, elementNgContentIndex, element.sourceInfo);
} }
if (hasInlineTemplates) { if (hasInlineTemplates) {
@ -204,7 +225,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
this._createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs); this._createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
var templateDirectives = this._createDirectiveAsts( var templateDirectives = this._createDirectiveAsts(
element.name, this._parseDirectives(this.selectorMatcher, templateCssSelector), element.name, this._parseDirectives(this.selectorMatcher, templateCssSelector),
templateElementOrDirectiveProps, element.sourceInfo); templateElementOrDirectiveProps, [], element.sourceInfo);
var templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts( var templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, templateElementOrDirectiveProps, templateDirectives); element.name, templateElementOrDirectiveProps, templateDirectives);
this._assertNoComponentsNorElementBindingsOnTemplate(templateDirectives, templateElementProps, this._assertNoComponentsNorElementBindingsOnTemplate(templateDirectives, templateElementProps,
@ -263,8 +284,8 @@ class TemplateParseVisitor implements HtmlAstVisitor {
} else if (isPresent( } else if (isPresent(
bindParts[2])) { // match: var-name / var-name="iden" / #name / #name="iden" bindParts[2])) { // match: var-name / var-name="iden" / #name / #name="iden"
var identifier = bindParts[5]; var identifier = bindParts[5];
var value = attrValue.length === 0 ? IMPLICIT_VAR_NAME : attrValue; this._parseVariable(identifier, attrValue, attr.sourceInfo, targetMatchableAttrs,
this._parseVariable(identifier, value, attr.sourceInfo, targetMatchableAttrs, targetVars); targetVars);
} else if (isPresent(bindParts[3])) { // match: on-event } else if (isPresent(bindParts[3])) { // match: on-event
this._parseEvent(bindParts[5], attrValue, attr.sourceInfo, targetMatchableAttrs, this._parseEvent(bindParts[5], attrValue, attr.sourceInfo, targetMatchableAttrs,
@ -377,7 +398,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
} }
private _parseDirectives(selectorMatcher: SelectorMatcher, private _parseDirectives(selectorMatcher: SelectorMatcher,
elementCssSelector: CssSelector): NormalizedDirectiveMetadata[] { elementCssSelector: CssSelector): CompileDirectiveMetadata[] {
var directives = []; var directives = [];
selectorMatcher.match(elementCssSelector, selectorMatcher.match(elementCssSelector,
(selector, directive) => { directives.push(directive); }); (selector, directive) => { directives.push(directive); });
@ -385,7 +406,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
// as selectorMatcher uses Maps inside. // as selectorMatcher uses Maps inside.
// Also need to make components the first directive in the array // Also need to make components the first directive in the array
ListWrapper.sort(directives, ListWrapper.sort(directives,
(dir1: NormalizedDirectiveMetadata, dir2: NormalizedDirectiveMetadata) => { (dir1: CompileDirectiveMetadata, dir2: CompileDirectiveMetadata) => {
var dir1Comp = dir1.isComponent; var dir1Comp = dir1.isComponent;
var dir2Comp = dir2.isComponent; var dir2Comp = dir2.isComponent;
if (dir1Comp && !dir2Comp) { if (dir1Comp && !dir2Comp) {
@ -393,29 +414,44 @@ class TemplateParseVisitor implements HtmlAstVisitor {
} else if (!dir1Comp && dir2Comp) { } else if (!dir1Comp && dir2Comp) {
return 1; return 1;
} else { } else {
return StringWrapper.compare(dir1.type.name, dir2.type.name); return this.directivesIndexByTypeId.get(dir1.type.id) -
this.directivesIndexByTypeId.get(dir2.type.id);
} }
}); });
return directives; return directives;
} }
private _createDirectiveAsts(elementName: string, directives: NormalizedDirectiveMetadata[], private _createDirectiveAsts(elementName: string, directives: CompileDirectiveMetadata[],
props: BoundElementOrDirectiveProperty[], props: BoundElementOrDirectiveProperty[],
possibleExportAsVars: VariableAst[],
sourceInfo: string): DirectiveAst[] { sourceInfo: string): DirectiveAst[] {
return directives.map((directive: NormalizedDirectiveMetadata) => { var matchedVariables: Set<string> = new Set();
var directiveAsts = directives.map((directive: CompileDirectiveMetadata) => {
var hostProperties: BoundElementPropertyAst[] = []; var hostProperties: BoundElementPropertyAst[] = [];
var hostEvents: BoundEventAst[] = []; var hostEvents: BoundEventAst[] = [];
var directiveProperties: BoundDirectivePropertyAst[] = []; var directiveProperties: BoundDirectivePropertyAst[] = [];
var changeDetection = directive.changeDetection; this._createDirectiveHostPropertyAsts(elementName, directive.hostProperties, sourceInfo,
if (isPresent(changeDetection)) { hostProperties);
this._createDirectiveHostPropertyAsts(elementName, changeDetection.hostProperties, this._createDirectiveHostEventAsts(directive.hostListeners, sourceInfo, hostEvents);
sourceInfo, hostProperties); this._createDirectivePropertyAsts(directive.properties, props, directiveProperties);
this._createDirectiveHostEventAsts(changeDetection.hostListeners, sourceInfo, hostEvents); var exportAsVars = [];
this._createDirectivePropertyAsts(changeDetection.properties, props, directiveProperties); possibleExportAsVars.forEach((varAst) => {
if ((varAst.value.length === 0 && directive.isComponent) ||
(directive.exportAs == varAst.value)) {
exportAsVars.push(varAst);
matchedVariables.add(varAst.name);
} }
return new DirectiveAst(directive, directiveProperties, hostProperties, hostEvents,
sourceInfo);
}); });
return new DirectiveAst(directive, directiveProperties, hostProperties, hostEvents,
exportAsVars, sourceInfo);
});
possibleExportAsVars.forEach((varAst) => {
if (varAst.value.length > 0 && !SetWrapper.has(matchedVariables, varAst.name)) {
this._reportError(
`There is no directive with "exportAs" set to "${varAst.value}" at ${varAst.sourceInfo}`);
}
});
return directiveAsts;
} }
private _createDirectiveHostPropertyAsts(elementName: string, private _createDirectiveHostPropertyAsts(elementName: string,
@ -439,7 +475,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
} }
} }
private _createDirectivePropertyAsts(directiveProperties: string[], private _createDirectivePropertyAsts(directiveProperties: StringMap<string, string>,
boundProps: BoundElementOrDirectiveProperty[], boundProps: BoundElementOrDirectiveProperty[],
targetBoundDirectiveProps: BoundDirectivePropertyAst[]) { targetBoundDirectiveProps: BoundDirectivePropertyAst[]) {
if (isPresent(directiveProperties)) { if (isPresent(directiveProperties)) {
@ -447,12 +483,8 @@ class TemplateParseVisitor implements HtmlAstVisitor {
boundProps.forEach(boundProp => boundProps.forEach(boundProp =>
boundPropsByName.set(dashCaseToCamelCase(boundProp.name), boundProp)); boundPropsByName.set(dashCaseToCamelCase(boundProp.name), boundProp));
directiveProperties.forEach((bindConfig: string) => { StringMapWrapper.forEach(directiveProperties, (elProp: string, dirProp: string) => {
// canonical syntax: `dirProp: elProp` elProp = dashCaseToCamelCase(elProp);
// if there is no `:`, use dirProp = elProp
var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]);
var dirProp = parts[0];
var elProp = dashCaseToCamelCase(parts[1]);
var boundProp = boundPropsByName.get(elProp); var boundProp = boundPropsByName.get(elProp);
// Bindings are optional, so this binding only needs to be set up if an expression is given. // Bindings are optional, so this binding only needs to be set up if an expression is given.
@ -566,15 +598,6 @@ export function splitClasses(classAttrValue: string): string[] {
return StringWrapper.split(classAttrValue.trim(), /\s+/g); return StringWrapper.split(classAttrValue.trim(), /\s+/g);
} }
export function splitAtColon(input: string, defaultValues: string[]): string[] {
var parts = StringWrapper.split(input.trim(), /\s*:\s*/g);
if (parts.length > 1) {
return parts;
} else {
return defaultValues;
}
}
class Component { class Component {
static create(directives: DirectiveAst[]): Component { static create(directives: DirectiveAst[]): Component {
if (directives.length === 0 || !directives[0].directive.isComponent) { if (directives.length === 0 || !directives[0].directive.isComponent) {

View File

@ -0,0 +1,67 @@
import {HtmlElementAst} from './html_ast';
import {isBlank, isPresent} from 'angular2/src/core/facade/lang';
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';
const SCRIPT_ELEMENT = 'script';
const NG_NON_BINDABLE_ATTR = 'ng-non-bindable';
export function preparseElement(ast: HtmlElementAst): PreparsedElement {
var selectAttr = null;
var hrefAttr = null;
var relAttr = null;
var nonBindable = false;
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;
} else if (attr.name == NG_NON_BINDABLE_ATTR) {
nonBindable = true;
}
});
selectAttr = normalizeNgContentSelect(selectAttr);
var nodeName = ast.name;
var type = PreparsedElementType.OTHER;
if (nonBindable) {
type = PreparsedElementType.NON_BINDABLE;
} else if (nodeName == NG_CONTENT_ELEMENT) {
type = PreparsedElementType.NG_CONTENT;
} else if (nodeName == STYLE_ELEMENT) {
type = PreparsedElementType.STYLE;
} else if (nodeName == SCRIPT_ELEMENT) {
type = PreparsedElementType.SCRIPT;
} else if (nodeName == LINK_ELEMENT && relAttr == LINK_STYLE_REL_VALUE) {
type = PreparsedElementType.STYLESHEET;
}
return new PreparsedElement(type, selectAttr, hrefAttr);
}
export enum PreparsedElementType {
NG_CONTENT,
STYLE,
STYLESHEET,
SCRIPT,
NON_BINDABLE,
OTHER
}
export class PreparsedElement {
constructor(public type: PreparsedElementType, public selectAttr: string,
public hrefAttr: string) {}
}
function normalizeNgContentSelect(selectAttr: string): string {
if (isBlank(selectAttr) || selectAttr.length === 0) {
return '*';
}
return selectAttr;
}

View File

@ -72,3 +72,13 @@ export function codeGenValueFn(params: string[], value: string): string {
return `function(${params.join(',')}) { return ${value}; }`; return `function(${params.join(',')}) { return ${value}; }`;
} }
} }
export function splitAtColon(input: string, defaultValues: string[]): string[] {
var parts = StringWrapper.split(input.trim(), /\s*:\s*/g);
if (parts.length > 1) {
return parts;
} else {
return defaultValues;
}
}

View File

@ -42,7 +42,7 @@ export {
DebugContext, DebugContext,
ChangeDetectorGenConfig ChangeDetectorGenConfig
} from './interfaces'; } from './interfaces';
export {ChangeDetectionStrategy, changeDetectionStrategyFromJson} from './constants'; export {ChangeDetectionStrategy, CHANGE_DECTION_STRATEGY_VALUES} from './constants';
export {DynamicProtoChangeDetector} from './proto_change_detector'; export {DynamicProtoChangeDetector} from './proto_change_detector';
export {BindingRecord, BindingTarget} from './binding_record'; export {BindingRecord, BindingTarget} from './binding_record';
export {DirectiveIndex, DirectiveRecord} from './directive_record'; export {DirectiveIndex, DirectiveRecord} from './directive_record';

View File

@ -1,11 +1,4 @@
import { import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/core/facade/lang';
StringWrapper,
normalizeBool,
isBlank,
serializeEnum,
deserializeEnum
} from 'angular2/src/core/facade/lang';
import {MapWrapper} from 'angular2/src/core/facade/collection';
export enum ChangeDetectionStrategy { export enum ChangeDetectionStrategy {
/** /**
@ -48,19 +41,15 @@ export enum ChangeDetectionStrategy {
OnPushObserve OnPushObserve
} }
var strategyMap: Map<number, ChangeDetectionStrategy> = MapWrapper.createFromPairs([ export var CHANGE_DECTION_STRATEGY_VALUES = [
[0, ChangeDetectionStrategy.CheckOnce], ChangeDetectionStrategy.CheckOnce,
[1, ChangeDetectionStrategy.Checked], ChangeDetectionStrategy.Checked,
[2, ChangeDetectionStrategy.CheckAlways], ChangeDetectionStrategy.CheckAlways,
[3, ChangeDetectionStrategy.Detached], ChangeDetectionStrategy.Detached,
[4, ChangeDetectionStrategy.OnPush], ChangeDetectionStrategy.OnPush,
[5, ChangeDetectionStrategy.Default], ChangeDetectionStrategy.Default,
[6, ChangeDetectionStrategy.OnPushObserve] ChangeDetectionStrategy.OnPushObserve
]); ];
export function changeDetectionStrategyFromJson(value: number): ChangeDetectionStrategy {
return deserializeEnum(value, strategyMap);
}
export function isDefaultChangeDetectionStrategy(changeDetectionStrategy: ChangeDetectionStrategy): export function isDefaultChangeDetectionStrategy(changeDetectionStrategy: ChangeDetectionStrategy):
boolean { boolean {

View File

@ -117,7 +117,6 @@ export class DirectiveResolver {
properties: mergedProperties, properties: mergedProperties,
events: mergedEvents, events: mergedEvents,
host: mergedHost, host: mergedHost,
dynamicLoadable: dm.dynamicLoadable,
bindings: dm.bindings, bindings: dm.bindings,
exportAs: dm.exportAs, exportAs: dm.exportAs,
moduleId: dm.moduleId, moduleId: dm.moduleId,

View File

@ -1,4 +1,4 @@
import {StringMap} from 'angular2/src/core/facade/collection'; import {StringMap, MapWrapper} from 'angular2/src/core/facade/collection';
export enum LifecycleHooks { export enum LifecycleHooks {
OnInit, OnInit,
@ -11,6 +11,17 @@ export enum LifecycleHooks {
AfterViewChecked AfterViewChecked
} }
export var LIFECYCLE_HOOKS_VALUES = [
LifecycleHooks.OnInit,
LifecycleHooks.OnDestroy,
LifecycleHooks.DoCheck,
LifecycleHooks.OnChanges,
LifecycleHooks.AfterContentInit,
LifecycleHooks.AfterContentChecked,
LifecycleHooks.AfterViewInit,
LifecycleHooks.AfterViewChecked
];
/** /**
* Lifecycle hooks are guaranteed to be called in the following order: * Lifecycle hooks are guaranteed to be called in the following order:
* - `OnChanges` (if any bindings have changed), * - `OnChanges` (if any bindings have changed),

View File

@ -10,7 +10,7 @@ import {
} from 'angular2/src/core/render/render'; } from 'angular2/src/core/render/render';
export class CompiledTemplate { export class CompiledTemplate {
private _changeDetectorFactories: Function[] = null; private _changeDetectorFactory: Function = null;
private _styles: string[] = null; private _styles: string[] = null;
private _commands: TemplateCmd[] = null; private _commands: TemplateCmd[] = null;
// Note: paramGetter is a function so that we can have cycles between templates! // Note: paramGetter is a function so that we can have cycles between templates!
@ -19,15 +19,15 @@ export class CompiledTemplate {
private _init() { private _init() {
if (isBlank(this._commands)) { if (isBlank(this._commands)) {
var params = this._paramGetter(); var params = this._paramGetter();
this._changeDetectorFactories = params[0]; this._changeDetectorFactory = params[0];
this._commands = params[1]; this._commands = params[1];
this._styles = params[2]; this._styles = params[2];
} }
} }
get changeDetectorFactories(): Function[] { get changeDetectorFactory(): Function {
this._init(); this._init();
return this._changeDetectorFactories; return this._changeDetectorFactory;
} }
get styles(): string[] { get styles(): string[] {
@ -69,26 +69,28 @@ export function ngContent(ngContentIndex: number): NgContentCmd {
} }
export interface IBeginElementCmd extends TemplateCmd, RenderBeginElementCmd { export interface IBeginElementCmd extends TemplateCmd, RenderBeginElementCmd {
variableNameAndValues: string[]; variableNameAndValues: Array<string | number>;
eventNames: string[]; eventTargetAndNames: string[];
directives: Type[]; directives: Type[];
visit(visitor: CommandVisitor, context: any): any; visit(visitor: CommandVisitor, context: any): any;
} }
export class BeginElementCmd implements TemplateCmd, IBeginElementCmd, RenderBeginElementCmd { export class BeginElementCmd implements TemplateCmd, IBeginElementCmd, RenderBeginElementCmd {
constructor(public name: string, public attrNameAndValues: string[], public eventNames: string[], constructor(public name: string, public attrNameAndValues: string[],
public variableNameAndValues: string[], public directives: Type[], public eventTargetAndNames: string[],
public variableNameAndValues: Array<string | number>, public directives: Type[],
public isBound: boolean, public ngContentIndex: number) {} public isBound: boolean, public ngContentIndex: number) {}
visit(visitor: CommandVisitor, context: any): any { visit(visitor: CommandVisitor, context: any): any {
return visitor.visitBeginElement(this, context); return visitor.visitBeginElement(this, context);
} }
} }
export function beginElement(name: string, attrNameAndValues: string[], eventNames: string[], export function beginElement(name: string, attrNameAndValues: string[],
variableNameAndValues: string[], directives: Type[], isBound: boolean, eventTargetAndNames: string[],
ngContentIndex: number): BeginElementCmd { variableNameAndValues: Array<string | number>, directives: Type[],
return new BeginElementCmd(name, attrNameAndValues, eventNames, variableNameAndValues, directives, isBound: boolean, ngContentIndex: number): BeginElementCmd {
isBound, ngContentIndex); return new BeginElementCmd(name, attrNameAndValues, eventTargetAndNames, variableNameAndValues,
directives, isBound, ngContentIndex);
} }
export class EndElementCmd implements TemplateCmd { export class EndElementCmd implements TemplateCmd {
@ -103,8 +105,9 @@ export class BeginComponentCmd implements TemplateCmd, IBeginElementCmd, RenderB
isBound: boolean = true; isBound: boolean = true;
templateId: number; templateId: number;
component: Type; component: Type;
constructor(public name: string, public attrNameAndValues: string[], public eventNames: string[], constructor(public name: string, public attrNameAndValues: string[],
public variableNameAndValues: string[], public directives: Type[], public eventTargetAndNames: string[],
public variableNameAndValues: Array<string | number>, public directives: Type[],
public nativeShadow: boolean, public ngContentIndex: number, public nativeShadow: boolean, public ngContentIndex: number,
public template: CompiledTemplate) { public template: CompiledTemplate) {
this.component = directives[0]; this.component = directives[0];
@ -115,11 +118,11 @@ export class BeginComponentCmd implements TemplateCmd, IBeginElementCmd, RenderB
} }
} }
export function beginComponent(name: string, attrNameAnsValues: string[], eventNames: string[], export function beginComponent(
variableNameAndValues: string[], directives: Type[], name: string, attrNameAnsValues: string[], eventTargetAndNames: string[],
nativeShadow: boolean, ngContentIndex: number, variableNameAndValues: Array<string | number>, directives: Type[], nativeShadow: boolean,
template: CompiledTemplate): BeginComponentCmd { ngContentIndex: number, template: CompiledTemplate): BeginComponentCmd {
return new BeginComponentCmd(name, attrNameAnsValues, eventNames, variableNameAndValues, return new BeginComponentCmd(name, attrNameAnsValues, eventTargetAndNames, variableNameAndValues,
directives, nativeShadow, ngContentIndex, template); directives, nativeShadow, ngContentIndex, template);
} }
@ -135,10 +138,10 @@ export class EmbeddedTemplateCmd implements TemplateCmd, IBeginElementCmd,
RenderEmbeddedTemplateCmd { RenderEmbeddedTemplateCmd {
isBound: boolean = true; isBound: boolean = true;
name: string = null; name: string = null;
eventNames: string[] = EMPTY_ARR; eventTargetAndNames: string[] = EMPTY_ARR;
constructor(public attrNameAndValues: string[], public variableNameAndValues: string[], constructor(public attrNameAndValues: string[], public variableNameAndValues: string[],
public directives: Type[], public isMerged: boolean, public ngContentIndex: number, public directives: Type[], public isMerged: boolean, public ngContentIndex: number,
public children: TemplateCmd[]) {} public changeDetectorFactory: Function, public children: TemplateCmd[]) {}
visit(visitor: CommandVisitor, context: any): any { visit(visitor: CommandVisitor, context: any): any {
return visitor.visitEmbeddedTemplate(this, context); return visitor.visitEmbeddedTemplate(this, context);
} }
@ -146,20 +149,13 @@ export class EmbeddedTemplateCmd implements TemplateCmd, IBeginElementCmd,
export function embeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[], export function embeddedTemplate(attrNameAndValues: string[], variableNameAndValues: string[],
directives: Type[], isMerged: boolean, ngContentIndex: number, directives: Type[], isMerged: boolean, ngContentIndex: number,
children: TemplateCmd[]): EmbeddedTemplateCmd { changeDetectorFactory: Function, children: TemplateCmd[]):
EmbeddedTemplateCmd {
return new EmbeddedTemplateCmd(attrNameAndValues, variableNameAndValues, directives, isMerged, return new EmbeddedTemplateCmd(attrNameAndValues, variableNameAndValues, directives, isMerged,
ngContentIndex, children); ngContentIndex, changeDetectorFactory, children);
} }
export interface CommandVisitor extends RenderCommandVisitor { export interface CommandVisitor extends RenderCommandVisitor {}
visitText(cmd: TextCmd, context: any): any;
visitNgContent(cmd: NgContentCmd, context: any): any;
visitBeginElement(cmd: BeginElementCmd, context: any): any;
visitEndElement(context: any): any;
visitBeginComponent(cmd: BeginComponentCmd, context: any): any;
visitEndComponent(context: any): any;
visitEmbeddedTemplate(cmd: EmbeddedTemplateCmd, context: any): any;
}
export function visitAllCommands(visitor: CommandVisitor, cmds: TemplateCmd[], export function visitAllCommands(visitor: CommandVisitor, cmds: TemplateCmd[],
context: any = null) { context: any = null) {

View File

@ -36,7 +36,7 @@ class Directive extends DirectiveMetadata {
*/ */
class Component extends ComponentMetadata { class Component extends ComponentMetadata {
const Component({String selector, List<String> properties, const Component({String selector, List<String> properties,
List<String> events, Map<String, String> host, bool dynamicLoadable, List<String> events, Map<String, String> host,
List bindings, String exportAs, String moduleId, List bindings, String exportAs, String moduleId,
Map<String, dynamic> queries, Map<String, dynamic> queries,
bool compileChildren, List viewBindings, ChangeDetectionStrategy changeDetection}) bool compileChildren, List viewBindings, ChangeDetectionStrategy changeDetection})
@ -45,7 +45,6 @@ class Component extends ComponentMetadata {
properties: properties, properties: properties,
events: events, events: events,
host: host, host: host,
dynamicLoadable: dynamicLoadable,
bindings: bindings, bindings: bindings,
exportAs: exportAs, exportAs: exportAs,
moduleId: moduleId, moduleId: moduleId,

View File

@ -76,8 +76,8 @@ export interface ComponentDecorator extends TypeDecorator {
View(obj: { View(obj: {
templateUrl?: string, templateUrl?: string,
template?: string, template?: string,
directives?: Array<Type | any | any[]>, directives?: Array<Type | any[]>,
pipes?: Array<Type | any | any[]>, pipes?: Array<Type | any[]>,
renderer?: string, renderer?: string,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],
@ -96,8 +96,8 @@ export interface ViewDecorator extends TypeDecorator {
View(obj: { View(obj: {
templateUrl?: string, templateUrl?: string,
template?: string, template?: string,
directives?: Array<Type | any | any[]>, directives?: Array<Type | any[]>,
pipes?: Array<Type | any | any[]>, pipes?: Array<Type | any[]>,
renderer?: string, renderer?: string,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],
@ -218,7 +218,6 @@ export interface ComponentFactory {
properties?: string[], properties?: string[],
events?: string[], events?: string[],
host?: StringMap<string, string>, host?: StringMap<string, string>,
dynamicLoadable?: boolean,
bindings?: any[], bindings?: any[],
exportAs?: string, exportAs?: string,
moduleId?: string, moduleId?: string,
@ -232,7 +231,6 @@ export interface ComponentFactory {
properties?: string[], properties?: string[],
events?: string[], events?: string[],
host?: StringMap<string, string>, host?: StringMap<string, string>,
dynamicLoadable?: boolean,
bindings?: any[], bindings?: any[],
exportAs?: string, exportAs?: string,
moduleId?: string, moduleId?: string,
@ -290,7 +288,8 @@ export interface ViewFactory {
(obj: { (obj: {
templateUrl?: string, templateUrl?: string,
template?: string, template?: string,
directives?: Array<Type | any | any[]>, directives?: Array<Type | any[]>,
pipes?: Array<Type | any[]>,
encapsulation?: ViewEncapsulation, encapsulation?: ViewEncapsulation,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],
@ -298,7 +297,8 @@ export interface ViewFactory {
new (obj: { new (obj: {
templateUrl?: string, templateUrl?: string,
template?: string, template?: string,
directives?: Array<Type | any | any[]>, directives?: Array<Type | any[]>,
pipes?: Array<Type | any[]>,
encapsulation?: ViewEncapsulation, encapsulation?: ViewEncapsulation,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],

View File

@ -826,27 +826,6 @@ export class DirectiveMetadata extends InjectableMetadata {
*/ */
@CONST() @CONST()
export class ComponentMetadata extends DirectiveMetadata { export class ComponentMetadata extends DirectiveMetadata {
/**
* Declare that this component can be programatically loaded.
* Every component that is used in bootstrap, routing, ... has to be
* annotated with this.
*
* ## Example
*
* ```
* @Component({
* selector: 'root',
* dynamicLoadable: true
* })
* @View({
* template: 'hello world!'
* })
* class RootComponent {
* }
* ```
*/
dynamicLoadable: boolean;
/** /**
* Defines the used change detection strategy. * Defines the used change detection strategy.
* *
@ -900,14 +879,13 @@ export class ComponentMetadata extends DirectiveMetadata {
*/ */
viewBindings: any[]; viewBindings: any[];
constructor({selector, properties, events, host, dynamicLoadable, exportAs, moduleId, bindings, constructor({selector, properties, events, host, exportAs, moduleId, bindings, viewBindings,
queries, viewBindings, changeDetection = ChangeDetectionStrategy.Default, changeDetection = ChangeDetectionStrategy.Default, queries, compileChildren = true}:
compileChildren = true}: { {
selector?: string, selector?: string,
properties?: string[], properties?: string[],
events?: string[], events?: string[],
host?: StringMap<string, string>, host?: StringMap<string, string>,
dynamicLoadable?: boolean,
bindings?: any[], bindings?: any[],
exportAs?: string, exportAs?: string,
moduleId?: string, moduleId?: string,
@ -930,7 +908,6 @@ export class ComponentMetadata extends DirectiveMetadata {
this.changeDetection = changeDetection; this.changeDetection = changeDetection;
this.viewBindings = viewBindings; this.viewBindings = viewBindings;
this.dynamicLoadable = dynamicLoadable;
} }
} }

View File

@ -82,12 +82,9 @@ export class ViewMetadata {
* } * }
* ``` * ```
*/ */
// TODO(tbosch): use Type | Binding | any[] when Dart supports union types, directives: Array<Type | any[]>;
// as otherwise we would need to import Binding type and Dart would warn
// for an unused import.
directives: Array<Type | any | any[]>;
pipes: Array<Type | any | any[]>; pipes: Array<Type | any[]>;
/** /**
* Specify how the template and the styles should be encapsulated. * Specify how the template and the styles should be encapsulated.
@ -100,8 +97,8 @@ export class ViewMetadata {
constructor({templateUrl, template, directives, pipes, encapsulation, styles, styleUrls}: { constructor({templateUrl, template, directives, pipes, encapsulation, styles, styleUrls}: {
templateUrl?: string, templateUrl?: string,
template?: string, template?: string,
directives?: Array<Type | any | any[]>, directives?: Array<Type | any[]>,
pipes?: Array<Type | any | any[]>, pipes?: Array<Type | any[]>,
encapsulation?: ViewEncapsulation, encapsulation?: ViewEncapsulation,
styles?: string[], styles?: string[],
styleUrls?: string[], styleUrls?: string[],

View File

@ -1,4 +1,4 @@
import {isPresent, isBlank, RegExpWrapper, deserializeEnum} from 'angular2/src/core/facade/lang'; import {isPresent, isBlank, RegExpWrapper} from 'angular2/src/core/facade/lang';
import {Promise} from 'angular2/src/core/facade/async'; import {Promise} from 'angular2/src/core/facade/async';
import {Map, MapWrapper, StringMap, StringMapWrapper} from 'angular2/src/core/facade/collection'; import {Map, MapWrapper, StringMap, StringMapWrapper} from 'angular2/src/core/facade/collection';
import { import {
@ -309,11 +309,8 @@ export enum ViewEncapsulation {
None None
} }
var encapsulationMap: Map<number, ViewEncapsulation> = MapWrapper.createFromPairs( export var VIEW_ENCAPSULATION_VALUES =
[[0, ViewEncapsulation.Emulated], [1, ViewEncapsulation.Native], [2, ViewEncapsulation.None]]); [ViewEncapsulation.Emulated, ViewEncapsulation.Native, ViewEncapsulation.None];
export function viewEncapsulationFromJson(value: number): ViewEncapsulation {
return deserializeEnum(value, encapsulationMap);
}
export class ViewDefinition { export class ViewDefinition {
componentId: string; componentId: string;
@ -409,7 +406,7 @@ export interface RenderNgContentCmd extends RenderBeginCmd { ngContentIndex: num
export interface RenderBeginElementCmd extends RenderBeginCmd { export interface RenderBeginElementCmd extends RenderBeginCmd {
name: string; name: string;
attrNameAndValues: string[]; attrNameAndValues: string[];
eventNames: string[]; eventTargetAndNames: string[];
} }
export interface RenderBeginComponentCmd extends RenderBeginElementCmd { export interface RenderBeginComponentCmd extends RenderBeginElementCmd {
@ -422,15 +419,14 @@ export interface RenderEmbeddedTemplateCmd extends RenderBeginElementCmd {
children: RenderTemplateCmd[]; children: RenderTemplateCmd[];
} }
// TODO(tbosch): change ts2dart to allow to use `CMD` as type in these methods!
export interface RenderCommandVisitor { export interface RenderCommandVisitor {
visitText /*<CMD extends RenderTextCmd>*/ (cmd: any, context: any): any; visitText(cmd: any, context: any): any;
visitNgContent /*<CMD extends RenderNgContentCmd>*/ (cmd: any, context: any): any; visitNgContent(cmd: any, context: any): any;
visitBeginElement /*<CMD extends RenderBeginElementCmd>*/ (cmd: any, context: any): any; visitBeginElement(cmd: any, context: any): any;
visitEndElement(context: any): any; visitEndElement(context: any): any;
visitBeginComponent /*<CMD extends RenderBeginComponentCmd>*/ (cmd: any, context: any): any; visitBeginComponent(cmd: any, context: any): any;
visitEndComponent(context: any): any; visitEndComponent(context: any): any;
visitEmbeddedTemplate /*<CMD extends RenderEmbeddedTemplateCmd>*/ (cmd: any, context: any): any; visitEmbeddedTemplate(cmd: any, context: any): any;
} }

View File

@ -29,7 +29,6 @@ export class MockDirectiveResolver extends DirectiveResolver {
properties: dm.properties, properties: dm.properties,
events: dm.events, events: dm.events,
host: dm.host, host: dm.host,
dynamicLoadable: dm.dynamicLoadable,
bindings: bindings, bindings: bindings,
exportAs: dm.exportAs, exportAs: dm.exportAs,
moduleId: dm.moduleId, moduleId: dm.moduleId,

View File

@ -14,9 +14,8 @@ import {
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {MapWrapper} from 'angular2/src/core/facade/collection'; import {MapWrapper} from 'angular2/src/core/facade/collection';
import { import {
NormalizedDirectiveMetadata, CompileDirectiveMetadata,
TypeMetadata, CompileTypeMetadata
ChangeDetectionMetadata
} from 'angular2/src/compiler/directive_metadata'; } from 'angular2/src/compiler/directive_metadata';
import {TemplateParser} from 'angular2/src/compiler/template_parser'; import {TemplateParser} from 'angular2/src/compiler/template_parser';
import { import {
@ -60,10 +59,10 @@ export function main() {
pipes = new TestPipes(); pipes = new TestPipes();
})); }));
function createChangeDetector(template: string, directives: NormalizedDirectiveMetadata[], function createChangeDetector(template: string, directives: CompileDirectiveMetadata[],
protoViewIndex: number = 0): ChangeDetector { protoViewIndex: number = 0): ChangeDetector {
var protoChangeDetectors = var protoChangeDetectors =
createChangeDetectorDefinitions(new TypeMetadata({name: 'SomeComp'}), createChangeDetectorDefinitions(new CompileTypeMetadata({name: 'SomeComp'}),
ChangeDetectionStrategy.Default, ChangeDetectionStrategy.Default,
new ChangeDetectorGenConfig(true, true, false, false), new ChangeDetectorGenConfig(true, true, false, false),
parser.parse(template, directives, 'TestComp')) parser.parse(template, directives, 'TestComp'))
@ -97,6 +96,14 @@ export function main() {
expect(context.eventLog).toEqual(['click']); expect(context.eventLog).toEqual(['click']);
}); });
it('should handle events with targets', () => {
var changeDetector = createChangeDetector('<div (window:click)="onEvent($event)">', [], 0);
eventLocals.set('$event', 'click');
changeDetector.handleEvent('window:click', 0, eventLocals);
expect(context.eventLog).toEqual(['click']);
});
it('should watch variables', () => { it('should watch variables', () => {
var changeDetector = createChangeDetector('<div #some-var [el-prop]="someVar">', [], 0); var changeDetector = createChangeDetector('<div #some-var [el-prop]="someVar">', [], 0);
@ -106,10 +113,10 @@ export function main() {
}); });
it('should write directive properties', () => { it('should write directive properties', () => {
var dirMeta = new NormalizedDirectiveMetadata({ var dirMeta = CompileDirectiveMetadata.create({
type: new TypeMetadata({name: 'SomeDir'}), type: new CompileTypeMetadata({name: 'SomeDir'}),
selector: 'div', selector: '[dir-prop]',
changeDetection: new ChangeDetectionMetadata({properties: ['dirProp']}) properties: ['dirProp']
}); });
var changeDetector = createChangeDetector('<div [dir-prop]="someProp">', [dirMeta], 0); var changeDetector = createChangeDetector('<div [dir-prop]="someProp">', [dirMeta], 0);
@ -119,11 +126,25 @@ export function main() {
expect(directive.dirProp).toEqual('someValue'); expect(directive.dirProp).toEqual('someValue');
}); });
it('should write template directive properties', () => {
var dirMeta = CompileDirectiveMetadata.create({
type: new CompileTypeMetadata({name: 'SomeDir'}),
selector: '[dir-prop]',
properties: ['dirProp']
});
var changeDetector = createChangeDetector('<template [dir-prop]="someProp">', [dirMeta], 0);
context.someProp = 'someValue';
changeDetector.detectChanges();
expect(directive.dirProp).toEqual('someValue');
});
it('should watch directive host properties', () => { it('should watch directive host properties', () => {
var dirMeta = new NormalizedDirectiveMetadata({ var dirMeta = CompileDirectiveMetadata.create({
type: new TypeMetadata({name: 'SomeDir'}), type: new CompileTypeMetadata({name: 'SomeDir'}),
selector: 'div', selector: 'div',
changeDetection: new ChangeDetectionMetadata({hostProperties: {'elProp': 'dirProp'}}) host: {'[elProp]': 'dirProp'}
}); });
var changeDetector = createChangeDetector('<div>', [dirMeta], 0); var changeDetector = createChangeDetector('<div>', [dirMeta], 0);
@ -134,11 +155,10 @@ export function main() {
}); });
it('should handle directive events', () => { it('should handle directive events', () => {
var dirMeta = new NormalizedDirectiveMetadata({ var dirMeta = CompileDirectiveMetadata.create({
type: new TypeMetadata({name: 'SomeDir'}), type: new CompileTypeMetadata({name: 'SomeDir'}),
selector: 'div', selector: 'div',
changeDetection: host: {'(click)': 'onEvent($event)'}
new ChangeDetectionMetadata({hostListeners: {'click': 'onEvent($event)'}})
}); });
var changeDetector = createChangeDetector('<div>', [dirMeta], 0); var changeDetector = createChangeDetector('<div>', [dirMeta], 0);

View File

@ -21,11 +21,15 @@ import {Promise} from 'angular2/src/core/facade/async';
import {ChangeDetectionCompiler} from 'angular2/src/compiler/change_detector_compiler'; import {ChangeDetectionCompiler} from 'angular2/src/compiler/change_detector_compiler';
import { import {
NormalizedDirectiveMetadata, CompileDirectiveMetadata,
TypeMetadata, CompileTypeMetadata
ChangeDetectionMetadata
} from 'angular2/src/compiler/directive_metadata'; } from 'angular2/src/compiler/directive_metadata';
import {SourceModule, SourceExpression, moduleRef} from 'angular2/src/compiler/source_module'; import {
SourceModule,
SourceExpression,
SourceExpressions,
moduleRef
} from 'angular2/src/compiler/source_module';
import {TemplateParser} from 'angular2/src/compiler/template_parser'; import {TemplateParser} from 'angular2/src/compiler/template_parser';
@ -63,8 +67,8 @@ export function main() {
describe('compileComponentRuntime', () => { describe('compileComponentRuntime', () => {
function detectChanges(compiler: ChangeDetectionCompiler, template: string, function detectChanges(compiler: ChangeDetectionCompiler, template: string,
directives: NormalizedDirectiveMetadata[] = CONST_EXPR([])): string[] { directives: CompileDirectiveMetadata[] = CONST_EXPR([])): string[] {
var type = new TypeMetadata({name: 'SomeComp'}); var type = new CompileTypeMetadata({name: 'SomeComp'});
var parsedTemplate = parser.parse(template, directives, 'TestComp'); var parsedTemplate = parser.parse(template, directives, 'TestComp');
var factories = var factories =
compiler.compileComponentRuntime(type, ChangeDetectionStrategy.Default, parsedTemplate); compiler.compileComponentRuntime(type, ChangeDetectionStrategy.Default, parsedTemplate);
@ -99,13 +103,13 @@ export function main() {
describe('compileComponentCodeGen', () => { describe('compileComponentCodeGen', () => {
function detectChanges(compiler: ChangeDetectionCompiler, template: string, function detectChanges(compiler: ChangeDetectionCompiler, template: string,
directives: NormalizedDirectiveMetadata[] = CONST_EXPR([])): directives: CompileDirectiveMetadata[] = CONST_EXPR([])):
Promise<string[]> { Promise<string[]> {
var type = new TypeMetadata({name: 'SomeComp'}); var type = new CompileTypeMetadata({name: 'SomeComp'});
var parsedTemplate = parser.parse(template, directives, 'TestComp'); var parsedTemplate = parser.parse(template, directives, 'TestComp');
var sourceExpression = var sourceExpressions =
compiler.compileComponentCodeGen(type, ChangeDetectionStrategy.Default, parsedTemplate); compiler.compileComponentCodeGen(type, ChangeDetectionStrategy.Default, parsedTemplate);
var testableModule = createTestableModule(sourceExpression, 0).getSourceWithImports(); var testableModule = createTestableModule(sourceExpressions, 0).getSourceWithImports();
return evalModule(testableModule.source, testableModule.imports, null); return evalModule(testableModule.source, testableModule.imports, null);
} }
@ -122,9 +126,10 @@ export function main() {
}); });
} }
function createTestableModule(source: SourceExpression, changeDetectorIndex: number): SourceModule { function createTestableModule(source: SourceExpressions, changeDetectorIndex: number):
SourceModule {
var resultExpression = var resultExpression =
`${THIS_MODULE_REF}testChangeDetector((${source.expression})[${changeDetectorIndex}])`; `${THIS_MODULE_REF}testChangeDetector(([${source.expressions.join(',')}])[${changeDetectorIndex}])`;
var testableSource = `${source.declarations.join('\n')} var testableSource = `${source.declarations.join('\n')}
${codeGenExportVariable('run')}${codeGenValueFn(['_'], resultExpression)};`; ${codeGenExportVariable('run')}${codeGenValueFn(['_'], resultExpression)};`;
return new SourceModule(null, testableSource); return new SourceModule(null, testableSource);

View File

@ -29,9 +29,9 @@ import {
} from 'angular2/src/core/compiler/template_commands'; } from 'angular2/src/core/compiler/template_commands';
import {CommandCompiler} from 'angular2/src/compiler/command_compiler'; import {CommandCompiler} from 'angular2/src/compiler/command_compiler';
import { import {
NormalizedDirectiveMetadata, CompileDirectiveMetadata,
TypeMetadata, CompileTypeMetadata,
NormalizedTemplateMetadata CompileTemplateMetadata
} from 'angular2/src/compiler/directive_metadata'; } from 'angular2/src/compiler/directive_metadata';
import {SourceModule, SourceExpression, moduleRef} from 'angular2/src/compiler/source_module'; import {SourceModule, SourceExpression, moduleRef} from 'angular2/src/compiler/source_module';
import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {ViewEncapsulation} from 'angular2/src/core/render/api';
@ -61,12 +61,12 @@ export class RootComp {}
export class SomeDir {} export class SomeDir {}
export class AComp {} export class AComp {}
var RootCompTypeMeta = var RootCompTypeMeta = new CompileTypeMetadata(
new TypeMetadata({id: 1, name: 'RootComp', runtime: RootComp, moduleId: THIS_MODULE_NAME}); {id: 1, name: 'RootComp', runtime: RootComp, moduleId: THIS_MODULE_NAME});
var SomeDirTypeMeta = var SomeDirTypeMeta =
new TypeMetadata({id: 2, name: 'SomeDir', runtime: SomeDir, moduleId: THIS_MODULE_NAME}); new CompileTypeMetadata({id: 2, name: 'SomeDir', runtime: SomeDir, moduleId: THIS_MODULE_NAME});
var ACompTypeMeta = var ACompTypeMeta =
new TypeMetadata({id: 3, name: 'AComp', runtime: AComp, moduleId: THIS_MODULE_NAME}); new CompileTypeMetadata({id: 3, name: 'AComp', runtime: AComp, moduleId: THIS_MODULE_NAME});
var NESTED_COMPONENT = new CompiledTemplate(45, () => []); var NESTED_COMPONENT = new CompiledTemplate(45, () => []);
@ -84,12 +84,12 @@ export function main() {
})); }));
function createComp({type, selector, template, encapsulation, ngContentSelectors}: { function createComp({type, selector, template, encapsulation, ngContentSelectors}: {
type?: TypeMetadata, type?: CompileTypeMetadata,
selector?: string, selector?: string,
template?: string, template?: string,
encapsulation?: ViewEncapsulation, encapsulation?: ViewEncapsulation,
ngContentSelectors?: string[] ngContentSelectors?: string[]
}): NormalizedDirectiveMetadata { }): CompileDirectiveMetadata {
if (isBlank(encapsulation)) { if (isBlank(encapsulation)) {
encapsulation = ViewEncapsulation.None; encapsulation = ViewEncapsulation.None;
} }
@ -102,11 +102,11 @@ export function main() {
if (isBlank(template)) { if (isBlank(template)) {
template = ''; template = '';
} }
return new NormalizedDirectiveMetadata({ return CompileDirectiveMetadata.create({
selector: selector, selector: selector,
isComponent: true, isComponent: true,
type: type, type: type,
template: new NormalizedTemplateMetadata({ template: new CompileTemplateMetadata({
template: template, template: template,
ngContentSelectors: ngContentSelectors, ngContentSelectors: ngContentSelectors,
encapsulation: encapsulation encapsulation: encapsulation
@ -114,8 +114,10 @@ export function main() {
}); });
} }
function createDirective(type: TypeMetadata, selector: string): NormalizedDirectiveMetadata { function createDirective(type: CompileTypeMetadata, selector: string, exportAs: string = null):
return new NormalizedDirectiveMetadata({selector: selector, isComponent: false, type: type}); CompileDirectiveMetadata {
return CompileDirectiveMetadata.create(
{selector: selector, exportAs: exportAs, isComponent: false, type: type});
} }
@ -159,18 +161,47 @@ export function main() {
it('should create bound element commands', inject([AsyncTestCompleter], (async) => { it('should create bound element commands', inject([AsyncTestCompleter], (async) => {
var rootComp = createComp({ var rootComp = createComp({
type: RootCompTypeMeta, type: RootCompTypeMeta,
template: '<div a="b" #some-var="someValue" (click)="someHandler">' template: '<div a="b" #some-var (click)="someHandler" (window:scroll)="scrollTo()">'
}); });
var dir = createDirective(SomeDirTypeMeta, '[a]'); run(rootComp, [])
run(rootComp, [dir])
.then((data) => { .then((data) => {
expect(data).toEqual([ expect(data).toEqual([
[ [
BEGIN_ELEMENT, BEGIN_ELEMENT,
'div', 'div',
['a', 'b'], ['a', 'b'],
['click'], [null, 'click', 'window', 'scroll'],
['someVar', 'someValue'], ['someVar', '%implicit'],
[],
true,
null
],
[END_ELEMENT]
]);
async.done();
});
}));
it('should create element commands with directives',
inject([AsyncTestCompleter], (async) => {
var rootComp =
createComp({type: RootCompTypeMeta, template: '<div a #some-var="someExport">'});
var dir = CompileDirectiveMetadata.create({
selector: '[a]',
exportAs: 'someExport',
isComponent: false,
type: SomeDirTypeMeta,
host: {'(click)': 'doIt()', '(window:scroll)': 'doIt()', 'role': 'button'}
});
run(rootComp, [dir])
.then((data) => {
expect(data).toEqual([
[
BEGIN_ELEMENT,
'div',
['a', '', 'role', 'button'],
[null, 'click', 'window', 'scroll'],
['someVar', 0],
['SomeDirType'], ['SomeDirType'],
true, true,
null null
@ -214,10 +245,8 @@ export function main() {
describe('components', () => { describe('components', () => {
it('should create component commands', inject([AsyncTestCompleter], (async) => { it('should create component commands', inject([AsyncTestCompleter], (async) => {
var rootComp = createComp({ var rootComp = createComp(
type: RootCompTypeMeta, {type: RootCompTypeMeta, template: '<a a="b" #some-var (click)="someHandler">'});
template: '<a a="b" #some-var="someValue" (click)="someHandler">'
});
var comp = createComp({type: ACompTypeMeta, selector: 'a'}); var comp = createComp({type: ACompTypeMeta, selector: 'a'});
run(rootComp, [comp]) run(rootComp, [comp])
.then((data) => { .then((data) => {
@ -226,8 +255,8 @@ export function main() {
BEGIN_COMPONENT, BEGIN_COMPONENT,
'a', 'a',
['a', 'b'], ['a', 'b'],
['click'], [null, 'click'],
['someVar', 'someValue'], ['someVar', 0],
['ACompType'], ['ACompType'],
false, false,
null, null,
@ -305,7 +334,7 @@ export function main() {
template: '<template a="b" #some-var="someValue"></template>' template: '<template a="b" #some-var="someValue"></template>'
}); });
var dir = createDirective(SomeDirTypeMeta, '[a]'); var dir = createDirective(SomeDirTypeMeta, '[a]');
run(rootComp, [dir]) run(rootComp, [dir], 1)
.then((data) => { .then((data) => {
expect(data).toEqual([ expect(data).toEqual([
[ [
@ -315,6 +344,7 @@ export function main() {
['SomeDirType'], ['SomeDirType'],
false, false,
null, null,
'cd1',
[] []
] ]
]); ]);
@ -325,10 +355,20 @@ export function main() {
it('should created nested nodes', inject([AsyncTestCompleter], (async) => { it('should created nested nodes', inject([AsyncTestCompleter], (async) => {
var rootComp = var rootComp =
createComp({type: RootCompTypeMeta, template: '<template>t</template>'}); createComp({type: RootCompTypeMeta, template: '<template>t</template>'});
run(rootComp, []) run(rootComp, [], 1)
.then((data) => { .then((data) => {
expect(data).toEqual( expect(data).toEqual([
[[EMBEDDED_TEMPLATE, [], [], [], false, null, [[TEXT, 't', false, null]]]]); [
EMBEDDED_TEMPLATE,
[],
[],
[],
false,
null,
'cd1',
[[TEXT, 't', false, null]]
]
]);
async.done(); async.done();
}); });
})); }));
@ -339,10 +379,10 @@ export function main() {
type: RootCompTypeMeta, type: RootCompTypeMeta,
template: '<template><ng-content></ng-content></template>' template: '<template><ng-content></ng-content></template>'
}); });
run(rootComp, []) run(rootComp, [], 1)
.then((data) => { .then((data) => {
expect(data).toEqual( expect(data).toEqual(
[[EMBEDDED_TEMPLATE, [], [], [], true, null, [[NG_CONTENT, null]]]]); [[EMBEDDED_TEMPLATE, [], [], [], true, null, 'cd1', [[NG_CONTENT, null]]]]);
async.done(); async.done();
}); });
})); }));
@ -364,17 +404,21 @@ export function main() {
describe('compileComponentRuntime', () => { describe('compileComponentRuntime', () => {
beforeEach(() => { beforeEach(() => {
componentTemplateFactory = (directive: NormalizedDirectiveMetadata) => { componentTemplateFactory = (directive: CompileDirectiveMetadata) => {
return new CompiledTemplate(directive.type.id, () => []); return new CompiledTemplate(directive.type.id, () => []);
}; };
}); });
function run(component: NormalizedDirectiveMetadata, function run(component: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[],
directives: NormalizedDirectiveMetadata[]): Promise<any[][]> { embeddedTemplateCount: number = 0): Promise<any[][]> {
var changeDetectorFactories = [];
for (var i = 0; i < embeddedTemplateCount + 1; i++) {
(function(i) { changeDetectorFactories.push((_) => `cd${i}`); })(i);
}
var parsedTemplate = var parsedTemplate =
parser.parse(component.template.template, directives, component.type.name); parser.parse(component.template.template, directives, component.type.name);
var commands = commandCompiler.compileComponentRuntime(component, parsedTemplate, var commands = commandCompiler.compileComponentRuntime(
componentTemplateFactory); component, parsedTemplate, changeDetectorFactories, componentTemplateFactory);
return PromiseWrapper.resolve(humanize(commands)); return PromiseWrapper.resolve(humanize(commands));
} }
@ -384,17 +428,21 @@ export function main() {
describe('compileComponentCodeGen', () => { describe('compileComponentCodeGen', () => {
beforeEach(() => { beforeEach(() => {
componentTemplateFactory = (directive: NormalizedDirectiveMetadata) => { componentTemplateFactory = (directive: CompileDirectiveMetadata) => {
return `new ${TEMPLATE_COMMANDS_MODULE_REF}CompiledTemplate(${directive.type.id}, ${codeGenValueFn([], '{}')})`; return `new ${TEMPLATE_COMMANDS_MODULE_REF}CompiledTemplate(${directive.type.id}, ${codeGenValueFn([], '{}')})`;
}; };
}); });
function run(component: NormalizedDirectiveMetadata, function run(component: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[],
directives: NormalizedDirectiveMetadata[]): Promise<any[][]> { embeddedTemplateCount: number = 0): Promise<any[][]> {
var changeDetectorFactoryExpressions = [];
for (var i = 0; i < embeddedTemplateCount + 1; i++) {
changeDetectorFactoryExpressions.push(codeGenValueFn(['_'], `'cd${i}'`));
}
var parsedTemplate = var parsedTemplate =
parser.parse(component.template.template, directives, component.type.name); parser.parse(component.template.template, directives, component.type.name);
var sourceModule = commandCompiler.compileComponentCodeGen(component, parsedTemplate, var sourceModule = commandCompiler.compileComponentCodeGen(
componentTemplateFactory); component, parsedTemplate, changeDetectorFactoryExpressions, componentTemplateFactory);
var testableModule = createTestableModule(sourceModule).getSourceWithImports(); var testableModule = createTestableModule(sourceModule).getSourceWithImports();
return evalModule(testableModule.source, testableModule.imports, null); return evalModule(testableModule.source, testableModule.imports, null);
} }
@ -432,7 +480,7 @@ class CommandHumanizer implements CommandVisitor {
BEGIN_ELEMENT, BEGIN_ELEMENT,
cmd.name, cmd.name,
cmd.attrNameAndValues, cmd.attrNameAndValues,
cmd.eventNames, cmd.eventTargetAndNames,
cmd.variableNameAndValues, cmd.variableNameAndValues,
cmd.directives.map(checkAndStringifyType), cmd.directives.map(checkAndStringifyType),
cmd.isBound, cmd.isBound,
@ -449,12 +497,11 @@ class CommandHumanizer implements CommandVisitor {
BEGIN_COMPONENT, BEGIN_COMPONENT,
cmd.name, cmd.name,
cmd.attrNameAndValues, cmd.attrNameAndValues,
cmd.eventNames, cmd.eventTargetAndNames,
cmd.variableNameAndValues, cmd.variableNameAndValues,
cmd.directives.map(checkAndStringifyType), cmd.directives.map(checkAndStringifyType),
cmd.nativeShadow, cmd.nativeShadow,
cmd.ngContentIndex, cmd.ngContentIndex,
// TODO humanizeTemplate(cmd.template)
cmd.template.id cmd.template.id
]); ]);
return null; return null;
@ -471,6 +518,7 @@ class CommandHumanizer implements CommandVisitor {
cmd.directives.map(checkAndStringifyType), cmd.directives.map(checkAndStringifyType),
cmd.isMerged, cmd.isMerged,
cmd.ngContentIndex, cmd.ngContentIndex,
cmd.changeDetectorFactory(null),
humanize(cmd.children) humanize(cmd.children)
]); ]);
return null; return null;

View File

@ -13,98 +13,76 @@ import {
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import { import {
NormalizedDirectiveMetadata, CompileDirectiveMetadata,
TypeMetadata, CompileTypeMetadata,
NormalizedTemplateMetadata, CompileTemplateMetadata
ChangeDetectionMetadata
} from 'angular2/src/compiler/directive_metadata'; } from 'angular2/src/compiler/directive_metadata';
import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {ViewEncapsulation} from 'angular2/src/core/render/api';
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection'; import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection';
import {LifecycleHooks} from 'angular2/src/core/compiler/interfaces';
export function main() { export function main() {
describe('DirectiveMetadata', () => { describe('DirectiveMetadata', () => {
var fullTypeMeta: TypeMetadata; var fullTypeMeta: CompileTypeMetadata;
var fullTemplateMeta: NormalizedTemplateMetadata; var fullTemplateMeta: CompileTemplateMetadata;
var fullChangeDetectionMeta: ChangeDetectionMetadata; var fullDirectiveMeta: CompileDirectiveMetadata;
var fullDirectiveMeta: NormalizedDirectiveMetadata;
beforeEach(() => { beforeEach(() => {
fullTypeMeta = new TypeMetadata({id: 23, name: 'SomeType', moduleId: 'someUrl'}); fullTypeMeta = new CompileTypeMetadata({id: 23, name: 'SomeType', moduleId: 'someUrl'});
fullTemplateMeta = new NormalizedTemplateMetadata({ fullTemplateMeta = new CompileTemplateMetadata({
encapsulation: ViewEncapsulation.Emulated, encapsulation: ViewEncapsulation.Emulated,
template: '<a></a>', template: '<a></a>',
templateUrl: 'someTemplateUrl',
styles: ['someStyle'], styles: ['someStyle'],
styleAbsUrls: ['someStyleUrl'], styleUrls: ['someStyleUrl'],
hostAttributes: {'attr1': 'attrValue2'},
ngContentSelectors: ['*'] ngContentSelectors: ['*']
}); });
fullChangeDetectionMeta = new ChangeDetectionMetadata({ fullDirectiveMeta = CompileDirectiveMetadata.create({
changeDetection: ChangeDetectionStrategy.Default,
properties: ['someProp'],
events: ['someEvent'],
hostListeners: {'event1': 'handler1'},
hostProperties: {'prop1': 'expr1'},
callAfterContentInit: true,
callAfterContentChecked: true,
callAfterViewInit: true,
callAfterViewChecked: true,
callOnChanges: true,
callDoCheck: true,
callOnInit: true
});
fullDirectiveMeta = new NormalizedDirectiveMetadata({
selector: 'someSelector', selector: 'someSelector',
isComponent: true, isComponent: true,
dynamicLoadable: true, dynamicLoadable: true,
type: fullTypeMeta, template: fullTemplateMeta, type: fullTypeMeta, template: fullTemplateMeta,
changeDetection: fullChangeDetectionMeta, changeDetection: ChangeDetectionStrategy.Default,
properties: ['someProp'],
events: ['someEvent'],
host: {'(event1)': 'handler1', '[prop1]': 'expr1', 'attr1': 'attrValue2'},
lifecycleHooks: [LifecycleHooks.OnChanges]
}); });
}); });
describe('DirectiveMetadata', () => { describe('DirectiveMetadata', () => {
it('should serialize with full data', () => { it('should serialize with full data', () => {
expect(NormalizedDirectiveMetadata.fromJson(fullDirectiveMeta.toJson())) expect(CompileDirectiveMetadata.fromJson(fullDirectiveMeta.toJson()))
.toEqual(fullDirectiveMeta); .toEqual(fullDirectiveMeta);
}); });
it('should serialize with no data', () => { it('should serialize with no data', () => {
var empty = new NormalizedDirectiveMetadata(); var empty = CompileDirectiveMetadata.create();
expect(NormalizedDirectiveMetadata.fromJson(empty.toJson())).toEqual(empty); expect(CompileDirectiveMetadata.fromJson(empty.toJson())).toEqual(empty);
}); });
}); });
describe('TypeMetadata', () => { describe('TypeMetadata', () => {
it('should serialize with full data', it('should serialize with full data', () => {
() => { expect(TypeMetadata.fromJson(fullTypeMeta.toJson())).toEqual(fullTypeMeta); }); expect(CompileTypeMetadata.fromJson(fullTypeMeta.toJson())).toEqual(fullTypeMeta);
});
it('should serialize with no data', () => { it('should serialize with no data', () => {
var empty = new TypeMetadata(); var empty = new CompileTypeMetadata();
expect(TypeMetadata.fromJson(empty.toJson())).toEqual(empty); expect(CompileTypeMetadata.fromJson(empty.toJson())).toEqual(empty);
}); });
}); });
describe('TemplateMetadata', () => { describe('TemplateMetadata', () => {
it('should serialize with full data', () => { it('should serialize with full data', () => {
expect(NormalizedTemplateMetadata.fromJson(fullTemplateMeta.toJson())) expect(CompileTemplateMetadata.fromJson(fullTemplateMeta.toJson()))
.toEqual(fullTemplateMeta); .toEqual(fullTemplateMeta);
}); });
it('should serialize with no data', () => { it('should serialize with no data', () => {
var empty = new NormalizedTemplateMetadata(); var empty = new CompileTemplateMetadata();
expect(NormalizedTemplateMetadata.fromJson(empty.toJson())).toEqual(empty); expect(CompileTemplateMetadata.fromJson(empty.toJson())).toEqual(empty);
});
});
describe('ChangeDetectionMetadata', () => {
it('should serialize with full data', () => {
expect(ChangeDetectionMetadata.fromJson(fullChangeDetectionMeta.toJson()))
.toEqual(fullChangeDetectionMeta);
});
it('should serialize with no data', () => {
var empty = new ChangeDetectionMetadata();
expect(ChangeDetectionMetadata.fromJson(empty.toJson())).toEqual(empty);
}); });
}); });
}); });

View File

@ -80,22 +80,6 @@ export function main() {
]); ]);
}); });
}); });
describe('ng-non-bindable', () => {
it('should ignore text nodes and elements inside of elements with ng-non-bindable', () => {
expect(humanizeDom(
parser.parse('<div ng-non-bindable>hello<span></span></div>', 'TestComp')))
.toEqual([
[HtmlElementAst, 'div', 'TestComp > div:nth-child(0)'],
[
HtmlAttrAst,
'ng-non-bindable',
'',
'TestComp > div:nth-child(0)[ng-non-bindable=]'
]
]);
});
});
}); });
describe('unparse', () => { describe('unparse', () => {

View File

@ -15,6 +15,7 @@ import {
import {stringify} from 'angular2/src/core/facade/lang'; import {stringify} from 'angular2/src/core/facade/lang';
import {RuntimeMetadataResolver} from 'angular2/src/compiler/runtime_metadata'; import {RuntimeMetadataResolver} from 'angular2/src/compiler/runtime_metadata';
import {LifecycleHooks, LIFECYCLE_HOOKS_VALUES} from 'angular2/src/core/compiler/interfaces';
import { import {
Component, Component,
View, View,
@ -43,27 +44,20 @@ export function main() {
inject([RuntimeMetadataResolver], (resolver: RuntimeMetadataResolver) => { inject([RuntimeMetadataResolver], (resolver: RuntimeMetadataResolver) => {
var meta = resolver.getMetadata(ComponentWithEverything); var meta = resolver.getMetadata(ComponentWithEverything);
expect(meta.selector).toEqual('someSelector'); expect(meta.selector).toEqual('someSelector');
expect(meta.exportAs).toEqual('someExportAs');
expect(meta.isComponent).toBe(true); expect(meta.isComponent).toBe(true);
expect(meta.dynamicLoadable).toBe(true); expect(meta.dynamicLoadable).toBe(true);
expect(meta.type.runtime).toBe(ComponentWithEverything); expect(meta.type.runtime).toBe(ComponentWithEverything);
expect(meta.type.name).toEqual(stringify(ComponentWithEverything)); expect(meta.type.name).toEqual(stringify(ComponentWithEverything));
expect(meta.type.moduleId).toEqual('someModuleId'); expect(meta.type.moduleId).toEqual('someModuleId');
expect(meta.changeDetection.callAfterContentChecked).toBe(true); expect(meta.lifecycleHooks).toEqual(LIFECYCLE_HOOKS_VALUES);
expect(meta.changeDetection.callAfterContentInit).toBe(true); expect(meta.changeDetection).toBe(ChangeDetectionStrategy.CheckAlways);
expect(meta.changeDetection.callAfterViewChecked).toBe(true); expect(meta.properties).toEqual({'someProp': 'someProp'});
expect(meta.changeDetection.callAfterViewInit).toBe(true); expect(meta.events).toEqual({'someEvent': 'someEvent'});
expect(meta.changeDetection.callDoCheck).toBe(true); expect(meta.hostListeners).toEqual({'someHostListener': 'someHostListenerExpr'});
expect(meta.changeDetection.callOnChanges).toBe(true); expect(meta.hostProperties).toEqual({'someHostProp': 'someHostPropExpr'});
expect(meta.changeDetection.callOnInit).toBe(true); expect(meta.hostAttributes).toEqual({'someHostAttr': 'someHostAttrValue'});
expect(meta.changeDetection.changeDetection).toBe(ChangeDetectionStrategy.CheckAlways);
expect(meta.changeDetection.properties).toEqual(['someProp']);
expect(meta.changeDetection.events).toEqual(['someEvent']);
expect(meta.changeDetection.hostListeners)
.toEqual({'someHostListener': 'someHostListenerExpr'});
expect(meta.changeDetection.hostProperties)
.toEqual({'someHostProp': 'someHostPropExpr'});
expect(meta.template.encapsulation).toBe(ViewEncapsulation.Emulated); expect(meta.template.encapsulation).toBe(ViewEncapsulation.Emulated);
expect(meta.template.hostAttributes).toEqual({'someHostAttr': 'someHostAttrValue'});
expect(meta.template.styles).toEqual(['someStyle']); expect(meta.template.styles).toEqual(['someStyle']);
expect(meta.template.styleUrls).toEqual(['someStyleUrl']); expect(meta.template.styleUrls).toEqual(['someStyleUrl']);
expect(meta.template.template).toEqual('someTemplate'); expect(meta.template.template).toEqual('someTemplate');
@ -105,7 +99,7 @@ class DirectiveWithoutModuleId {
'(someHostListener)': 'someHostListenerExpr', '(someHostListener)': 'someHostListenerExpr',
'someHostAttr': 'someHostAttrValue' 'someHostAttr': 'someHostAttrValue'
}, },
dynamicLoadable: true, exportAs: 'someExportAs',
moduleId: 'someModuleId', moduleId: 'someModuleId',
changeDetection: ChangeDetectionStrategy.CheckAlways changeDetection: ChangeDetectionStrategy.CheckAlways
}) })

View File

@ -17,14 +17,14 @@ import {SpyXHR} from '../core/spies';
import {XHR} from 'angular2/src/core/render/xhr'; import {XHR} from 'angular2/src/core/render/xhr';
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions'; import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
import {CONST_EXPR, isPresent, StringWrapper} from 'angular2/src/core/facade/lang'; import {CONST_EXPR, isPresent, isBlank, StringWrapper} from 'angular2/src/core/facade/lang';
import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async'; import {PromiseWrapper, Promise} from 'angular2/src/core/facade/async';
import {evalModule} from './eval_module'; import {evalModule} from './eval_module';
import {StyleCompiler} from 'angular2/src/compiler/style_compiler'; import {StyleCompiler} from 'angular2/src/compiler/style_compiler';
import { import {
NormalizedDirectiveMetadata, CompileDirectiveMetadata,
NormalizedTemplateMetadata, CompileTemplateMetadata,
TypeMetadata CompileTypeMetadata
} from 'angular2/src/compiler/directive_metadata'; } from 'angular2/src/compiler/directive_metadata';
import {SourceExpression, SourceModule} from 'angular2/src/compiler/source_module'; import {SourceExpression, SourceModule} from 'angular2/src/compiler/source_module';
import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {ViewEncapsulation} from 'angular2/src/core/render/api';
@ -52,152 +52,249 @@ export function main() {
beforeEach(inject([StyleCompiler], (_compiler) => { compiler = _compiler; })); beforeEach(inject([StyleCompiler], (_compiler) => { compiler = _compiler; }));
function comp(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation): function comp(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation):
NormalizedDirectiveMetadata { CompileDirectiveMetadata {
return new NormalizedDirectiveMetadata({ return CompileDirectiveMetadata.create({
type: new TypeMetadata({id: 23, moduleId: 'someUrl'}), type: new CompileTypeMetadata({id: 23, moduleId: 'someUrl'}),
template: new NormalizedTemplateMetadata( template: new CompileTemplateMetadata(
{styles: styles, styleAbsUrls: styleAbsUrls, encapsulation: encapsulation}) {styles: styles, styleUrls: styleAbsUrls, encapsulation: encapsulation})
}); });
} }
describe('compileComponentRuntime', () => { describe('compileComponentRuntime', () => {
function runTest(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation, var xhrUrlResults;
expectedStyles: string[]) { var xhrCount;
return inject([AsyncTestCompleter], (async) => {
beforeEach(() => {
xhrCount = 0;
xhrUrlResults = {};
xhrUrlResults[IMPORT_ABS_MODULE_NAME] = 'span {color: blue}';
xhrUrlResults[IMPORT_ABS_MODULE_NAME_WITH_IMPORT] =
`a {color: green}@import ${IMPORT_REL_MODULE_NAME};`;
});
function compile(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation):
Promise<string[]> {
// Note: Can't use MockXHR as the xhr is called recursively, // Note: Can't use MockXHR as the xhr is called recursively,
// so we can't trigger flush. // so we can't trigger flush.
xhr.spy('get').andCallFake((url) => { xhr.spy('get').andCallFake((url) => {
var response; var response = xhrUrlResults[url];
if (url == IMPORT_ABS_MODULE_NAME) { xhrCount++;
response = 'span {color: blue}'; if (isBlank(response)) {
} else if (url == IMPORT_ABS_MODULE_NAME_WITH_IMPORT) {
response = `a {color: green}@import ${IMPORT_REL_MODULE_NAME};`;
} else {
throw new BaseException(`Unexpected url ${url}`); throw new BaseException(`Unexpected url ${url}`);
} }
return PromiseWrapper.resolve(response); return PromiseWrapper.resolve(response);
}); });
compiler.compileComponentRuntime(comp(styles, styleAbsUrls, encapsulation)) return compiler.compileComponentRuntime(comp(styles, styleAbsUrls, encapsulation));
.then((value) => {
compareStyles(value, expectedStyles);
async.done();
});
});
} }
describe('no shim', () => { describe('no shim', () => {
var encapsulation = ViewEncapsulation.None; var encapsulation = ViewEncapsulation.None;
it('should compile plain css rules', it('should compile plain css rules', inject([AsyncTestCompleter], (async) => {
runTest(['div {color: red}', 'span {color: blue}'], [], encapsulation, compile(['div {color: red}', 'span {color: blue}'], [], encapsulation)
['div {color: red}', 'span {color: blue}'])); .then(styles => {
expect(styles).toEqual(['div {color: red}', 'span {color: blue}']);
async.done();
});
}));
it('should allow to import rules', it('should allow to import rules', inject([AsyncTestCompleter], (async) => {
runTest(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation, compile(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation)
['div {color: red}', 'span {color: blue}'])); .then(styles => {
expect(styles).toEqual(['div {color: red}', 'span {color: blue}']);
async.done();
});
}));
it('should allow to import rules transitively', it('should allow to import rules transitively', inject([AsyncTestCompleter], (async) => {
runTest(['div {color: red}'], [IMPORT_ABS_MODULE_NAME_WITH_IMPORT], encapsulation, compile(['div {color: red}'], [IMPORT_ABS_MODULE_NAME_WITH_IMPORT], encapsulation)
['div {color: red}', 'a {color: green}', 'span {color: blue}'])); .then(styles => {
expect(styles)
.toEqual(['div {color: red}', 'a {color: green}', 'span {color: blue}']);
async.done();
});
}));
}); });
describe('with shim', () => { describe('with shim', () => {
var encapsulation = ViewEncapsulation.Emulated; var encapsulation = ViewEncapsulation.Emulated;
it('should compile plain css rules', it('should compile plain css rules', inject([AsyncTestCompleter], (async) => {
runTest( compile(['div {\ncolor: red;\n}', 'span {\ncolor: blue;\n}'], [], encapsulation)
['div {\ncolor: red;\n}', 'span {\ncolor: blue;\n}'], [], encapsulation, .then(styles => {
['div[_ngcontent-23] {\ncolor: red;\n}', 'span[_ngcontent-23] {\ncolor: blue;\n}'])); expect(styles).toEqual([
'div[_ngcontent-23] {\ncolor: red;\n}',
'span[_ngcontent-23] {\ncolor: blue;\n}'
]);
async.done();
});
}));
it('should allow to import rules', it('should allow to import rules', inject([AsyncTestCompleter], (async) => {
runTest( compile(['div {\ncolor: red;\n}'], [IMPORT_ABS_MODULE_NAME], encapsulation)
['div {\ncolor: red;\n}'], [IMPORT_ABS_MODULE_NAME], encapsulation, .then(styles => {
['div[_ngcontent-23] {\ncolor: red;\n}', 'span[_ngcontent-23] {\ncolor: blue;\n}'])); expect(styles).toEqual([
'div[_ngcontent-23] {\ncolor: red;\n}',
'span[_ngcontent-23] {\ncolor: blue;\n}'
]);
async.done();
});
}));
it('should allow to import rules transitively', it('should allow to import rules transitively', inject([AsyncTestCompleter], (async) => {
runTest(['div {\ncolor: red;\n}'], [IMPORT_ABS_MODULE_NAME_WITH_IMPORT], encapsulation, [ compile(['div {\ncolor: red;\n}'], [IMPORT_ABS_MODULE_NAME_WITH_IMPORT], encapsulation)
.then(styles => {
expect(styles).toEqual([
'div[_ngcontent-23] {\ncolor: red;\n}', 'div[_ngcontent-23] {\ncolor: red;\n}',
'a[_ngcontent-23] {\ncolor: green;\n}', 'a[_ngcontent-23] {\ncolor: green;\n}',
'span[_ngcontent-23] {\ncolor: blue;\n}' 'span[_ngcontent-23] {\ncolor: blue;\n}'
])); ]);
async.done();
}); });
}));
}); });
describe('compileComponentCodeGen', () => { it('should cache stylesheets for parallel requests', inject([AsyncTestCompleter], (async) => {
function runTest(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation, PromiseWrapper.all([
expectedStyles: string[]) { compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None),
return inject([AsyncTestCompleter], (async) => { compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None)
var sourceExpression = ])
compiler.compileComponentCodeGen(comp(styles, styleAbsUrls, encapsulation)); .then((styleArrays) => {
var sourceWithImports = testableExpression(sourceExpression).getSourceWithImports(); expect(styleArrays[0]).toEqual(['span {color: blue}']);
evalModule(sourceWithImports.source, sourceWithImports.imports, null) expect(styleArrays[1]).toEqual(['span {color: blue}']);
.then((value) => { expect(xhrCount).toBe(1);
compareStyles(value, expectedStyles); async.done();
});
}));
it('should cache stylesheets for serial requests', inject([AsyncTestCompleter], (async) => {
compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None)
.then((styles0) => {
xhrUrlResults[IMPORT_ABS_MODULE_NAME] = 'span {color: black}';
return compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None)
.then((styles1) => {
expect(styles0).toEqual(['span {color: blue}']);
expect(styles1).toEqual(['span {color: blue}']);
expect(xhrCount).toBe(1);
async.done(); async.done();
}); });
}); });
} }));
it('should allow to clear the cache', inject([AsyncTestCompleter], (async) => {
compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None)
.then((_) => {
compiler.clearCache();
xhrUrlResults[IMPORT_ABS_MODULE_NAME] = 'span {color: black}';
return compile([], [IMPORT_ABS_MODULE_NAME], ViewEncapsulation.None);
})
.then((styles) => {
expect(xhrCount).toBe(2);
expect(styles).toEqual(['span {color: black}']);
async.done();
});
}));
});
describe('compileComponentCodeGen', () => {
function compile(styles: string[], styleAbsUrls: string[], encapsulation: ViewEncapsulation):
Promise<string[]> {
var sourceExpression =
compiler.compileComponentCodeGen(comp(styles, styleAbsUrls, encapsulation));
var sourceWithImports = testableExpression(sourceExpression).getSourceWithImports();
return evalModule(sourceWithImports.source, sourceWithImports.imports, null);
};
describe('no shim', () => { describe('no shim', () => {
var encapsulation = ViewEncapsulation.None; var encapsulation = ViewEncapsulation.None;
it('should compile plain css ruless', it('should compile plain css ruless', inject([AsyncTestCompleter], (async) => {
runTest(['div {color: red}', 'span {color: blue}'], [], encapsulation, compile(['div {color: red}', 'span {color: blue}'], [], encapsulation)
['div {color: red}', 'span {color: blue}'])); .then(styles => {
expect(styles).toEqual(['div {color: red}', 'span {color: blue}']);
async.done();
});
}));
it('should compile css rules with newlines and quotes', it('should compile css rules with newlines and quotes',
runTest(['div\n{"color": \'red\'}'], [], encapsulation, ['div\n{"color": \'red\'}'])); inject([AsyncTestCompleter], (async) => {
compile(['div\n{"color": \'red\'}'], [], encapsulation)
.then(styles => {
expect(styles).toEqual(['div\n{"color": \'red\'}']);
async.done();
});
}));
it('should allow to import rules', it('should allow to import rules', inject([AsyncTestCompleter], (async) => {
runTest(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation, compile(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation)
['div {color: red}', 'span {color: blue}']), .then(styles => {
1000); expect(styles).toEqual(['div {color: red}', 'span {color: blue}']);
async.done();
});
}));
}); });
describe('with shim', () => { describe('with shim', () => {
var encapsulation = ViewEncapsulation.Emulated; var encapsulation = ViewEncapsulation.Emulated;
it('should compile plain css ruless', it('should compile plain css ruless', inject([AsyncTestCompleter], (async) => {
runTest( compile(['div {\ncolor: red;\n}', 'span {\ncolor: blue;\n}'], [], encapsulation)
['div {\ncolor: red;\n}', 'span {\ncolor: blue;\n}'], [], encapsulation, .then(styles => {
['div[_ngcontent-23] {\ncolor: red;\n}', 'span[_ngcontent-23] {\ncolor: blue;\n}'])); expect(styles).toEqual([
'div[_ngcontent-23] {\ncolor: red;\n}',
'span[_ngcontent-23] {\ncolor: blue;\n}'
]);
async.done();
});
}));
it('should allow to import rules', it('should allow to import rules', inject([AsyncTestCompleter], (async) => {
runTest( compile(['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation)
['div {color: red}'], [IMPORT_ABS_MODULE_NAME], encapsulation, .then(styles => {
['div[_ngcontent-23] {\ncolor: red;\n}', 'span[_ngcontent-23] {\ncolor: blue;\n}']), expect(styles).toEqual([
1000); 'div[_ngcontent-23] {\ncolor: red;\n}',
'span[_ngcontent-23] {\ncolor: blue;\n}'
]);
async.done();
});
}));
}); });
}); });
describe('compileStylesheetCodeGen', () => { describe('compileStylesheetCodeGen', () => {
function runTest(style: string, expectedStyles: string[], expectedShimStyles: string[]) { function compile(style: string): Promise<string[]> {
return inject([AsyncTestCompleter], (async) => {
var sourceModules = compiler.compileStylesheetCodeGen(MODULE_NAME, style); var sourceModules = compiler.compileStylesheetCodeGen(MODULE_NAME, style);
PromiseWrapper.all(sourceModules.map(sourceModule => { return PromiseWrapper.all(sourceModules.map(sourceModule => {
var sourceWithImports = var sourceWithImports = testableModule(sourceModule).getSourceWithImports();
testableModule(sourceModule).getSourceWithImports(); return evalModule(sourceWithImports.source, sourceWithImports.imports, null);
return evalModule(sourceWithImports.source, sourceWithImports.imports, }));
null);
}))
.then((values) => {
compareStyles(values[0], expectedStyles);
compareStyles(values[1], expectedShimStyles);
async.done();
});
});
} }
it('should compile plain css rules', runTest('div {color: red;}', ['div {color: red;}'], it('should compile plain css rules', inject([AsyncTestCompleter], (async) => {
['div[_ngcontent-%COMP%] {\ncolor: red;\n}'])); compile('div {color: red;}')
.then(stylesAndShimStyles => {
expect(stylesAndShimStyles)
.toEqual(
[['div {color: red;}'], ['div[_ngcontent-%COMP%] {\ncolor: red;\n}']]);
async.done();
});
}));
it('should allow to import rules with relative paths', it('should allow to import rules with relative paths',
runTest(`div {color: red}@import ${IMPORT_REL_MODULE_NAME};`, inject([AsyncTestCompleter], (async) => {
['div {color: red}', 'span {color: blue}'], [ compile(`div {color: red}@import ${IMPORT_REL_MODULE_NAME};`)
.then(stylesAndShimStyles => {
expect(stylesAndShimStyles)
.toEqual([
['div {color: red}', 'span {color: blue}'],
[
'div[_ngcontent-%COMP%] {\ncolor: red;\n}', 'div[_ngcontent-%COMP%] {\ncolor: red;\n}',
'span[_ngcontent-%COMP%] {\ncolor: blue;\n}' 'span[_ngcontent-%COMP%] {\ncolor: blue;\n}'
])); ]
]);
async.done();
});
}));
}); });
}); });
} }

View File

@ -21,11 +21,7 @@ import {
TemplateCompiler, TemplateCompiler,
NormalizedComponentWithViewDirectives NormalizedComponentWithViewDirectives
} from 'angular2/src/compiler/template_compiler'; } from 'angular2/src/compiler/template_compiler';
import { import {CompileDirectiveMetadata} from 'angular2/src/compiler/directive_metadata';
DirectiveMetadata,
NormalizedDirectiveMetadata,
INormalizedDirectiveMetadata
} from 'angular2/src/compiler/directive_metadata';
import {evalModule} from './eval_module'; import {evalModule} from './eval_module';
import {SourceModule, moduleRef} from 'angular2/src/compiler/source_module'; import {SourceModule, moduleRef} from 'angular2/src/compiler/source_module';
import {XHR} from 'angular2/src/core/render/xhr'; import {XHR} from 'angular2/src/core/render/xhr';
@ -104,6 +100,18 @@ export function main() {
async.done(); async.done();
}); });
})); }));
it('should pass the right change detector to embedded templates',
inject([AsyncTestCompleter], (async) => {
compile([CompWithEmbeddedTemplate])
.then((humanizedTemplate) => {
expect(humanizedTemplate['commands'][1]['commands'][0]).toEqual('<template>');
expect(humanizedTemplate['commands'][1]['commands'][1]['cd'])
.toEqual(['elementProperty(href)=someCtxValue']);
async.done();
});
}));
} }
describe('compileHostComponentRuntime', () => { describe('compileHostComponentRuntime', () => {
@ -113,28 +121,53 @@ export function main() {
runTests(compile); runTests(compile);
it('should cache components', inject([AsyncTestCompleter, XHR], (async, xhr: MockXHR) => { it('should cache components for parallel requests',
// we expect only one request! inject([AsyncTestCompleter, XHR], (async, xhr: MockXHR) => {
xhr.expect('angular2/test/compiler/compUrl.html', ''); xhr.expect('angular2/test/compiler/compUrl.html', 'a');
PromiseWrapper.all([ PromiseWrapper.all([compile([CompWithTemplateUrl]), compile([CompWithTemplateUrl])])
compiler.compileHostComponentRuntime(CompWithTemplateUrl),
compiler.compileHostComponentRuntime(CompWithTemplateUrl)
])
.then((humanizedTemplates) => { .then((humanizedTemplates) => {
expect(humanizedTemplates[0]).toEqual(humanizedTemplates[1]); expect(humanizedTemplates[0]['commands'][1]['commands']).toEqual(['#text(a)']);
expect(humanizedTemplates[1]['commands'][1]['commands']).toEqual(['#text(a)']);
async.done(); async.done();
}); });
xhr.flush(); xhr.flush();
})); }));
it('should only allow dynamic loadable components', () => { it('should cache components for sequential requests',
expect(() => compiler.compileHostComponentRuntime(PlainDirective)) inject([AsyncTestCompleter, XHR], (async, xhr: MockXHR) => {
.toThrowError( xhr.expect('angular2/test/compiler/compUrl.html', 'a');
`Could not compile '${stringify(PlainDirective)}' because it is not dynamically loadable.`); compile([CompWithTemplateUrl])
expect(() => compiler.compileHostComponentRuntime(CompWithoutHost)) .then((humanizedTemplate0) => {
.toThrowError( return compile([CompWithTemplateUrl])
`Could not compile '${stringify(CompWithoutHost)}' because it is not dynamically loadable.`); .then((humanizedTemplate1) => {
expect(humanizedTemplate0['commands'][1]['commands'])
.toEqual(['#text(a)']);
expect(humanizedTemplate1['commands'][1]['commands'])
.toEqual(['#text(a)']);
async.done();
}); });
});
xhr.flush();
}));
it('should allow to clear the cache',
inject([AsyncTestCompleter, XHR], (async, xhr: MockXHR) => {
xhr.expect('angular2/test/compiler/compUrl.html', 'a');
compile([CompWithTemplateUrl])
.then((humanizedTemplate) => {
compiler.clearCache();
xhr.expect('angular2/test/compiler/compUrl.html', 'b');
var result = compile([CompWithTemplateUrl]);
xhr.flush();
return result;
})
.then((humanizedTemplate) => {
expect(humanizedTemplate['commands'][1]['commands']).toEqual(['#text(b)']);
async.done();
});
xhr.flush();
}));
}); });
@ -145,7 +178,7 @@ export function main() {
runtimeMetadataResolver.getViewDirectivesMetadata(component)); runtimeMetadataResolver.getViewDirectivesMetadata(component));
return PromiseWrapper.all(compAndViewDirMetas.map( return PromiseWrapper.all(compAndViewDirMetas.map(
meta => compiler.normalizeDirectiveMetadata(meta))) meta => compiler.normalizeDirectiveMetadata(meta)))
.then((normalizedCompAndViewDirMetas: NormalizedDirectiveMetadata[]) => .then((normalizedCompAndViewDirMetas: CompileDirectiveMetadata[]) =>
new NormalizedComponentWithViewDirectives( new NormalizedComponentWithViewDirectives(
normalizedCompAndViewDirMetas[0], normalizedCompAndViewDirMetas[0],
normalizedCompAndViewDirMetas.slice(1))); normalizedCompAndViewDirMetas.slice(1)));
@ -173,15 +206,15 @@ export function main() {
it('should serialize and deserialize', inject([AsyncTestCompleter], (async) => { it('should serialize and deserialize', inject([AsyncTestCompleter], (async) => {
compiler.normalizeDirectiveMetadata( compiler.normalizeDirectiveMetadata(
runtimeMetadataResolver.getMetadata(CompWithBindingsAndStyles)) runtimeMetadataResolver.getMetadata(CompWithBindingsAndStyles))
.then((meta: NormalizedDirectiveMetadata) => { .then((meta: CompileDirectiveMetadata) => {
var json = compiler.serializeDirectiveMetadata(meta); var json = compiler.serializeDirectiveMetadata(meta);
expect(isString(json)).toBe(true); expect(isString(json)).toBe(true);
// Note: serializing will clear our the runtime type! // Note: serializing will clear our the runtime type!
var clonedMeta = var clonedMeta = compiler.deserializeDirectiveMetadata(json);
<NormalizedDirectiveMetadata>compiler.deserializeDirectiveMetadata(json);
expect(meta.changeDetection).toEqual(clonedMeta.changeDetection); expect(meta.changeDetection).toEqual(clonedMeta.changeDetection);
expect(meta.template).toEqual(clonedMeta.template); expect(meta.template).toEqual(clonedMeta.template);
expect(meta.selector).toEqual(clonedMeta.selector); expect(meta.selector).toEqual(clonedMeta.selector);
expect(meta.exportAs).toEqual(clonedMeta.exportAs);
expect(meta.type.name).toEqual(clonedMeta.type.name); expect(meta.type.name).toEqual(clonedMeta.type.name);
async.done(); async.done();
}); });
@ -194,7 +227,7 @@ export function main() {
xhr.expect('angular2/test/compiler/compUrl.html', 'loadedTemplate'); xhr.expect('angular2/test/compiler/compUrl.html', 'loadedTemplate');
compiler.normalizeDirectiveMetadata( compiler.normalizeDirectiveMetadata(
runtimeMetadataResolver.getMetadata(CompWithTemplateUrl)) runtimeMetadataResolver.getMetadata(CompWithTemplateUrl))
.then((meta: NormalizedDirectiveMetadata) => { .then((meta: CompileDirectiveMetadata) => {
expect(meta.template.template).toEqual('loadedTemplate'); expect(meta.template.template).toEqual('loadedTemplate');
async.done(); async.done();
}); });
@ -203,13 +236,19 @@ export function main() {
it('should copy all the other fields', inject([AsyncTestCompleter], (async) => { it('should copy all the other fields', inject([AsyncTestCompleter], (async) => {
var meta = runtimeMetadataResolver.getMetadata(CompWithBindingsAndStyles); var meta = runtimeMetadataResolver.getMetadata(CompWithBindingsAndStyles);
compiler.normalizeDirectiveMetadata(meta) compiler.normalizeDirectiveMetadata(meta).then((normMeta: CompileDirectiveMetadata) => {
.then((normMeta: NormalizedDirectiveMetadata) => {
expect(normMeta.selector).toEqual(meta.selector);
expect(normMeta.dynamicLoadable).toEqual(meta.dynamicLoadable);
expect(normMeta.isComponent).toEqual(meta.isComponent);
expect(normMeta.type).toEqual(meta.type); expect(normMeta.type).toEqual(meta.type);
expect(normMeta.isComponent).toEqual(meta.isComponent);
expect(normMeta.dynamicLoadable).toEqual(meta.dynamicLoadable);
expect(normMeta.selector).toEqual(meta.selector);
expect(normMeta.exportAs).toEqual(meta.exportAs);
expect(normMeta.changeDetection).toEqual(meta.changeDetection); expect(normMeta.changeDetection).toEqual(meta.changeDetection);
expect(normMeta.properties).toEqual(meta.properties);
expect(normMeta.events).toEqual(meta.events);
expect(normMeta.hostListeners).toEqual(meta.hostListeners);
expect(normMeta.hostProperties).toEqual(meta.hostProperties);
expect(normMeta.hostAttributes).toEqual(meta.hostAttributes);
expect(normMeta.lifecycleHooks).toEqual(meta.lifecycleHooks);
async.done(); async.done();
}); });
})); }));
@ -233,24 +272,30 @@ export function main() {
@Component({ @Component({
selector: 'comp-a', selector: 'comp-a',
dynamicLoadable: true,
host: {'[title]': 'someProp'}, host: {'[title]': 'someProp'},
moduleId: THIS_MODULE moduleId: THIS_MODULE,
exportAs: 'someExportAs'
}) })
@View({template: '<a [href]="someProp"></a>', styles: ['div {color: red}']}) @View({template: '<a [href]="someProp"></a>', styles: ['div {color: red}']})
class CompWithBindingsAndStyles { class CompWithBindingsAndStyles {
} }
@Component({selector: 'tree', dynamicLoadable: true, moduleId: THIS_MODULE}) @Component({selector: 'tree', moduleId: THIS_MODULE})
@View({template: '<tree></tree>', directives: [TreeComp]}) @View({template: '<tree></tree>', directives: [TreeComp]})
class TreeComp { class TreeComp {
} }
@Component({selector: 'comp-url', dynamicLoadable: true, moduleId: THIS_MODULE}) @Component({selector: 'comp-url', moduleId: THIS_MODULE})
@View({templateUrl: 'compUrl.html'}) @View({templateUrl: 'compUrl.html'})
class CompWithTemplateUrl { class CompWithTemplateUrl {
} }
@Component({selector: 'comp-tpl', moduleId: THIS_MODULE})
@View({template: '<template><a [href]="someProp"></a></template>'})
class CompWithEmbeddedTemplate {
}
@Directive({selector: 'plain', moduleId: THIS_MODULE}) @Directive({selector: 'plain', moduleId: THIS_MODULE})
class PlainDirective { class PlainDirective {
} }
@ -260,9 +305,8 @@ class PlainDirective {
class CompWithoutHost { class CompWithoutHost {
} }
function testableTemplateModule(sourceModule: SourceModule, comp: INormalizedDirectiveMetadata): function testableTemplateModule(sourceModule: SourceModule, normComp: CompileDirectiveMetadata):
SourceModule { SourceModule {
var normComp = <NormalizedDirectiveMetadata>comp;
var resultExpression = `${THIS_MODULE_REF}humanizeTemplate(Host${normComp.type.name}Template)`; var resultExpression = `${THIS_MODULE_REF}humanizeTemplate(Host${normComp.type.name}Template)`;
var testableSource = `${sourceModule.sourceWithModuleRefs} var testableSource = `${sourceModule.sourceWithModuleRefs}
${codeGenExportVariable('run')}${codeGenValueFn(['_'], resultExpression)};`; ${codeGenExportVariable('run')}${codeGenValueFn(['_'], resultExpression)};`;
@ -290,7 +334,7 @@ export function humanizeTemplate(template: CompiledTemplate,
result = { result = {
'styles': template.styles, 'styles': template.styles,
'commands': commands, 'commands': commands,
'cd': testChangeDetector(template.changeDetectorFactories[0]) 'cd': testChangeDetector(template.changeDetectorFactory)
}; };
humanizedTemplates.set(template.id, result); humanizedTemplates.set(template.id, result);
visitAllCommands(new CommandHumanizer(commands, humanizedTemplates), template.commands); visitAllCommands(new CommandHumanizer(commands, humanizedTemplates), template.commands);
@ -316,7 +360,10 @@ function testChangeDetector(changeDetectorFactory: Function): string[] {
class CommandHumanizer implements CommandVisitor { class CommandHumanizer implements CommandVisitor {
constructor(private result: any[], constructor(private result: any[],
private humanizedTemplates: Map<number, StringMap<string, any>>) {} private humanizedTemplates: Map<number, StringMap<string, any>>) {}
visitText(cmd: TextCmd, context: any): any { return null; } visitText(cmd: TextCmd, context: any): any {
this.result.push(`#text(${cmd.value})`);
return null;
}
visitNgContent(cmd: NgContentCmd, context: any): any { return null; } visitNgContent(cmd: NgContentCmd, context: any): any { return null; }
visitBeginElement(cmd: BeginElementCmd, context: any): any { visitBeginElement(cmd: BeginElementCmd, context: any): any {
this.result.push(`<${cmd.name}>`); this.result.push(`<${cmd.name}>`);
@ -332,5 +379,10 @@ class CommandHumanizer implements CommandVisitor {
return null; return null;
} }
visitEndComponent(context: any): any { return this.visitEndElement(context); } visitEndComponent(context: any): any { return this.visitEndElement(context); }
visitEmbeddedTemplate(cmd: EmbeddedTemplateCmd, context: any): any { return null; } visitEmbeddedTemplate(cmd: EmbeddedTemplateCmd, context: any): any {
this.result.push(`<template>`);
this.result.push({'cd': testChangeDetector(cmd.changeDetectorFactory)});
this.result.push(`</template>`);
return null;
}
} }

View File

@ -14,9 +14,8 @@ import {
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import { import {
TypeMetadata, CompileTypeMetadata,
NormalizedTemplateMetadata, CompileTemplateMetadata
TemplateMetadata
} from 'angular2/src/compiler/directive_metadata'; } from 'angular2/src/compiler/directive_metadata';
import {ViewEncapsulation} from 'angular2/src/core/render/api'; import {ViewEncapsulation} from 'angular2/src/core/render/api';
@ -27,27 +26,29 @@ import {TEST_BINDINGS} from './test_bindings';
export function main() { export function main() {
describe('TemplateNormalizer', () => { describe('TemplateNormalizer', () => {
var dirType: TypeMetadata; var dirType: CompileTypeMetadata;
beforeEachBindings(() => TEST_BINDINGS); beforeEachBindings(() => TEST_BINDINGS);
beforeEach( beforeEach(() => {
() => { dirType = new TypeMetadata({moduleId: 'some/module/id', name: 'SomeComp'}); }); dirType = new CompileTypeMetadata({moduleId: 'some/module/id', name: 'SomeComp'});
});
describe('loadTemplate', () => { describe('loadTemplate', () => {
describe('inline template', () => { describe('inline template', () => {
it('should store the template', it('should store the template',
inject([AsyncTestCompleter, TemplateNormalizer], inject([AsyncTestCompleter, TemplateNormalizer],
(async, normalizer: TemplateNormalizer) => { (async, normalizer: TemplateNormalizer) => {
normalizer.normalizeTemplate(dirType, new TemplateMetadata({ normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null, encapsulation: null,
template: 'a', template: 'a',
templateUrl: null, templateUrl: null,
styles: [], styles: [],
styleUrls: ['test.css'] styleUrls: ['test.css']
})) }))
.then((template: NormalizedTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.template).toEqual('a'); expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('some/module/id');
async.done(); async.done();
}); });
})); }));
@ -55,15 +56,15 @@ export function main() {
it('should resolve styles on the annotation against the moduleId', it('should resolve styles on the annotation against the moduleId',
inject([AsyncTestCompleter, TemplateNormalizer], inject([AsyncTestCompleter, TemplateNormalizer],
(async, normalizer: TemplateNormalizer) => { (async, normalizer: TemplateNormalizer) => {
normalizer.normalizeTemplate(dirType, new TemplateMetadata({ normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null, encapsulation: null,
template: '', template: '',
templateUrl: null, templateUrl: null,
styles: [], styles: [],
styleUrls: ['test.css'] styleUrls: ['test.css']
})) }))
.then((template: NormalizedTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.styleAbsUrls).toEqual(['some/module/test.css']); expect(template.styleUrls).toEqual(['some/module/test.css']);
async.done(); async.done();
}); });
})); }));
@ -71,15 +72,15 @@ export function main() {
it('should resolve styles in the template against the moduleId', it('should resolve styles in the template against the moduleId',
inject([AsyncTestCompleter, TemplateNormalizer], inject([AsyncTestCompleter, TemplateNormalizer],
(async, normalizer: TemplateNormalizer) => { (async, normalizer: TemplateNormalizer) => {
normalizer.normalizeTemplate(dirType, new TemplateMetadata({ normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null, encapsulation: null,
template: '<style>@import test.css</style>', template: '<style>@import test.css</style>',
templateUrl: null, templateUrl: null,
styles: [], styles: [],
styleUrls: [] styleUrls: []
})) }))
.then((template: NormalizedTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.styleAbsUrls).toEqual(['some/module/test.css']); expect(template.styleUrls).toEqual(['some/module/test.css']);
async.done(); async.done();
}); });
})); }));
@ -91,15 +92,16 @@ export function main() {
inject([AsyncTestCompleter, TemplateNormalizer, XHR], inject([AsyncTestCompleter, TemplateNormalizer, XHR],
(async, normalizer: TemplateNormalizer, xhr: MockXHR) => { (async, normalizer: TemplateNormalizer, xhr: MockXHR) => {
xhr.expect('some/module/sometplurl', 'a'); xhr.expect('some/module/sometplurl', 'a');
normalizer.normalizeTemplate(dirType, new TemplateMetadata({ normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null, encapsulation: null,
template: null, template: null,
templateUrl: 'sometplurl', templateUrl: 'sometplurl',
styles: [], styles: [],
styleUrls: ['test.css'] styleUrls: ['test.css']
})) }))
.then((template: NormalizedTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.template).toEqual('a'); expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('some/module/sometplurl');
async.done(); async.done();
}); });
xhr.flush(); xhr.flush();
@ -109,15 +111,15 @@ export function main() {
inject([AsyncTestCompleter, TemplateNormalizer, XHR], inject([AsyncTestCompleter, TemplateNormalizer, XHR],
(async, normalizer: TemplateNormalizer, xhr: MockXHR) => { (async, normalizer: TemplateNormalizer, xhr: MockXHR) => {
xhr.expect('some/module/tpl/sometplurl', ''); xhr.expect('some/module/tpl/sometplurl', '');
normalizer.normalizeTemplate(dirType, new TemplateMetadata({ normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null, encapsulation: null,
template: null, template: null,
templateUrl: 'tpl/sometplurl', templateUrl: 'tpl/sometplurl',
styles: [], styles: [],
styleUrls: ['test.css'] styleUrls: ['test.css']
})) }))
.then((template: NormalizedTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.styleAbsUrls).toEqual(['some/module/test.css']); expect(template.styleUrls).toEqual(['some/module/test.css']);
async.done(); async.done();
}); });
xhr.flush(); xhr.flush();
@ -127,15 +129,15 @@ export function main() {
inject([AsyncTestCompleter, TemplateNormalizer, XHR], inject([AsyncTestCompleter, TemplateNormalizer, XHR],
(async, normalizer: TemplateNormalizer, xhr: MockXHR) => { (async, normalizer: TemplateNormalizer, xhr: MockXHR) => {
xhr.expect('some/module/tpl/sometplurl', '<style>@import test.css</style>'); xhr.expect('some/module/tpl/sometplurl', '<style>@import test.css</style>');
normalizer.normalizeTemplate(dirType, new TemplateMetadata({ normalizer.normalizeTemplate(dirType, new CompileTemplateMetadata({
encapsulation: null, encapsulation: null,
template: null, template: null,
templateUrl: 'tpl/sometplurl', templateUrl: 'tpl/sometplurl',
styles: [], styles: [],
styleUrls: [] styleUrls: []
})) }))
.then((template: NormalizedTemplateMetadata) => { .then((template: CompileTemplateMetadata) => {
expect(template.styleAbsUrls).toEqual(['some/module/tpl/test.css']); expect(template.styleUrls).toEqual(['some/module/tpl/test.css']);
async.done(); async.done();
}); });
xhr.flush(); xhr.flush();
@ -151,8 +153,8 @@ export function main() {
var viewEncapsulation = ViewEncapsulation.Native; var viewEncapsulation = ViewEncapsulation.Native;
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, dirType, new CompileTemplateMetadata(
new TemplateMetadata({encapsulation: viewEncapsulation, styles: [], styleUrls: []}), {encapsulation: viewEncapsulation, styles: [], styleUrls: []}),
'', 'some/module/'); '', 'some/module/');
expect(template.encapsulation).toBe(viewEncapsulation); expect(template.encapsulation).toBe(viewEncapsulation);
})); }));
@ -160,88 +162,90 @@ export function main() {
it('should keep the template as html', it('should keep the template as html',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), 'a', dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), 'a',
'some/module/'); 'some/module/');
expect(template.template).toEqual('a') expect(template.template).toEqual('a')
})); }));
it('should collect and keep ngContent', it('should collect ngContent',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<ng-content select="a"></ng-content>', 'some/module/'); '<ng-content select="a"></ng-content>', 'some/module/');
expect(template.ngContentSelectors).toEqual(['a']); expect(template.ngContentSelectors).toEqual(['a']);
expect(template.template).toEqual('<ng-content select="a"></ng-content>');
})); }));
it('should normalize ngContent wildcard selector', it('should normalize ngContent wildcard selector',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<ng-content></ng-content><ng-content select></ng-content><ng-content select="*"></ng-content>', '<ng-content></ng-content><ng-content select></ng-content><ng-content select="*"></ng-content>',
'some/module/'); 'some/module/');
expect(template.ngContentSelectors).toEqual(['*', '*', '*']); expect(template.ngContentSelectors).toEqual(['*', '*', '*']);
})); }));
it('should collect and remove top level styles in the template', it('should collect top level styles in the template',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<style>a</style>', 'some/module/'); '<style>a</style>', 'some/module/');
expect(template.styles).toEqual(['a']); expect(template.styles).toEqual(['a']);
expect(template.template).toEqual('');
})); }));
it('should collect and remove styles inside in elements', it('should collect styles inside in elements',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<div><style>a</style></div>', 'some/module/'); '<div><style>a</style></div>', 'some/module/');
expect(template.styles).toEqual(['a']); expect(template.styles).toEqual(['a']);
expect(template.template).toEqual('<div></div>');
})); }));
it('should collect and remove styleUrls in the template', it('should collect styleUrls in the template',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<link rel="stylesheet" href="aUrl">', 'some/module/'); '<link rel="stylesheet" href="aUrl">', 'some/module/');
expect(template.styleAbsUrls).toEqual(['some/module/aUrl']); expect(template.styleUrls).toEqual(['some/module/aUrl']);
expect(template.template).toEqual('');
})); }));
it('should collect and remove styleUrls in elements', it('should collect styleUrls in elements',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<div><link rel="stylesheet" href="aUrl"></div>', 'some/module/'); '<div><link rel="stylesheet" href="aUrl"></div>', 'some/module/');
expect(template.styleAbsUrls).toEqual(['some/module/aUrl']); expect(template.styleUrls).toEqual(['some/module/aUrl']);
expect(template.template).toEqual('<div></div>');
})); }));
it('should keep link elements with non stylesheet rel attribute', it('should ignore link elements with non stylesheet rel attribute',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<link href="b" rel="a"></link>', 'some/module/'); '<link href="b" rel="a"></link>', 'some/module/');
expect(template.styleAbsUrls).toEqual([]); expect(template.styleUrls).toEqual([]);
expect(template.template).toEqual('<link href="b" rel="a"></link>');
})); }));
it('should extract @import style urls into styleAbsUrl', it('should extract @import style urls into styleAbsUrl',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata( dirType, new CompileTemplateMetadata(
{encapsulation: null, styles: ['@import "test.css";'], styleUrls: []}), {encapsulation: null, styles: ['@import "test.css";'], styleUrls: []}),
'', 'some/module/id'); '', 'some/module/id');
expect(template.styles).toEqual(['']); expect(template.styles).toEqual(['']);
expect(template.styleAbsUrls).toEqual(['some/module/test.css']); expect(template.styleUrls).toEqual(['some/module/test.css']);
})); }));
it('should resolve relative urls in inline styles', it('should resolve relative urls in inline styles',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, new TemplateMetadata({ dirType, new CompileTemplateMetadata({
encapsulation: null, encapsulation: null,
styles: ['.foo{background-image: url(\'double.jpg\');'], styles: ['.foo{background-image: url(\'double.jpg\');'],
styleUrls: [] styleUrls: []
@ -254,15 +258,32 @@ export function main() {
it('should resolve relative style urls in styleUrls', it('should resolve relative style urls in styleUrls',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => { inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate( var template = normalizer.normalizeLoadedTemplate(
dirType, dirType, new CompileTemplateMetadata(
new TemplateMetadata({encapsulation: null, styles: [], styleUrls: ['test.css']}), '', {encapsulation: null, styles: [], styleUrls: ['test.css']}),
'some/module/id'); '', 'some/module/id');
expect(template.styles).toEqual([]); expect(template.styles).toEqual([]);
expect(template.styleAbsUrls).toEqual(['some/module/test.css']); expect(template.styleUrls).toEqual(['some/module/test.css']);
}));
it('should normalize ViewEncapsulation.Emulated to ViewEncapsulation.None if there are no stlyes nor stylesheets',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType, new CompileTemplateMetadata(
{encapsulation: ViewEncapsulation.Emulated, styles: [], styleUrls: []}),
'', 'some/module/id');
expect(template.encapsulation).toEqual(ViewEncapsulation.None);
}));
it('should ignore elements with ng-non-bindable attribute and their children',
inject([TemplateNormalizer], (normalizer: TemplateNormalizer) => {
var template = normalizer.normalizeLoadedTemplate(
dirType,
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
'<div ng-non-bindable><ng-content select="a"></ng-content></div><ng-content ng-non-bindable select="b"></ng-content>',
'some/module/');
expect(template.ngContentSelectors).toEqual([]);
})); }));
}); });
}); });
} }

View File

@ -16,10 +16,9 @@ import {TEST_BINDINGS} from './test_bindings';
import {isPresent} from 'angular2/src/core/facade/lang'; import {isPresent} from 'angular2/src/core/facade/lang';
import {TemplateParser, splitClasses} from 'angular2/src/compiler/template_parser'; import {TemplateParser, splitClasses} from 'angular2/src/compiler/template_parser';
import { import {
NormalizedDirectiveMetadata, CompileDirectiveMetadata,
TypeMetadata, CompileTypeMetadata,
ChangeDetectionMetadata, CompileTemplateMetadata
NormalizedTemplateMetadata
} from 'angular2/src/compiler/directive_metadata'; } from 'angular2/src/compiler/directive_metadata';
import { import {
templateVisitAll, templateVisitAll,
@ -59,14 +58,14 @@ export function main() {
beforeEach(inject([TemplateParser], (_parser) => { beforeEach(inject([TemplateParser], (_parser) => {
parser = _parser; parser = _parser;
ngIf = new NormalizedDirectiveMetadata({ ngIf = CompileDirectiveMetadata.create({
selector: '[ng-if]', selector: '[ng-if]',
type: new TypeMetadata({name: 'NgIf'}), type: new CompileTypeMetadata({name: 'NgIf'}),
changeDetection: new ChangeDetectionMetadata({properties: ['ngIf']}) properties: ['ngIf']
}); });
})); }));
function parse(template: string, directives: NormalizedDirectiveMetadata[]): TemplateAst[] { function parse(template: string, directives: CompileDirectiveMetadata[]): TemplateAst[] {
return parser.parse(template, directives, 'TestComp'); return parser.parse(template, directives, 'TestComp');
} }
@ -319,68 +318,39 @@ export function main() {
}); });
describe('variables', () => {
it('should parse variables via #... and not report them as attributes', () => {
expect(humanizeTemplateAsts(parse('<div #a="b">', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > div:nth-child(0)[#a=b]']
]);
});
it('should parse variables via var-... and not report them as attributes', () => {
expect(humanizeTemplateAsts(parse('<div var-a="b">', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > div:nth-child(0)[var-a=b]']
]);
});
it('should camel case variables', () => {
expect(humanizeTemplateAsts(parse('<div var-some-a="b">', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'someA', 'b', 'TestComp > div:nth-child(0)[var-some-a=b]']
]);
});
it('should use $implicit as variable name if none was specified', () => {
expect(humanizeTemplateAsts(parse('<div var-a>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', '$implicit', 'TestComp > div:nth-child(0)[var-a=]']
]);
});
});
describe('directives', () => { describe('directives', () => {
it('should locate directives ordered by name and components first', () => { it('should locate directives components first and ordered by the directives array in the View',
var dirA = new NormalizedDirectiveMetadata( () => {
{selector: '[a=b]', type: new TypeMetadata({name: 'DirA'})}); var dirA = CompileDirectiveMetadata.create(
var dirB = new NormalizedDirectiveMetadata( {selector: '[a]', type: new CompileTypeMetadata({name: 'DirA', id: 3})});
{selector: '[a]', type: new TypeMetadata({name: 'DirB'})}); var dirB = CompileDirectiveMetadata.create(
var comp = new NormalizedDirectiveMetadata({ {selector: '[b]', type: new CompileTypeMetadata({name: 'DirB', id: 2})});
var dirC = CompileDirectiveMetadata.create(
{selector: '[c]', type: new CompileTypeMetadata({name: 'DirC', id: 1})});
var comp = CompileDirectiveMetadata.create({
selector: 'div', selector: 'div',
isComponent: true, isComponent: true,
type: new TypeMetadata({name: 'ZComp'}), type: new CompileTypeMetadata({name: 'ZComp'}),
template: new NormalizedTemplateMetadata({ngContentSelectors: []}) template: new CompileTemplateMetadata({ngContentSelectors: []})
}); });
expect(humanizeTemplateAsts(parse('<div a="b">', [dirB, dirA, comp]))) expect(humanizeTemplateAsts(parse('<div a c b>', [dirA, dirB, dirC, comp])))
.toEqual([ .toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[AttrAst, 'a', 'b', 'TestComp > div:nth-child(0)[a=b]'], [AttrAst, 'a', '', 'TestComp > div:nth-child(0)[a=]'],
[AttrAst, 'b', '', 'TestComp > div:nth-child(0)[b=]'],
[AttrAst, 'c', '', 'TestComp > div:nth-child(0)[c=]'],
[DirectiveAst, comp, 'TestComp > div:nth-child(0)'], [DirectiveAst, comp, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'], [DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirB, 'TestComp > div:nth-child(0)'] [DirectiveAst, dirB, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirC, 'TestComp > div:nth-child(0)']
]); ]);
}); });
it('should locate directives in property bindings', () => { it('should locate directives in property bindings', () => {
var dirA = new NormalizedDirectiveMetadata( var dirA = CompileDirectiveMetadata.create(
{selector: '[a=b]', type: new TypeMetadata({name: 'DirA'})}); {selector: '[a=b]', type: new CompileTypeMetadata({name: 'DirA'})});
var dirB = new NormalizedDirectiveMetadata( var dirB = CompileDirectiveMetadata.create(
{selector: '[b]', type: new TypeMetadata({name: 'DirB'})}); {selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})});
expect(humanizeTemplateAsts(parse('<div [a]="b">', [dirA, dirB]))) expect(humanizeTemplateAsts(parse('<div [a]="b">', [dirA, dirB])))
.toEqual([ .toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ElementAst, 'div', 'TestComp > div:nth-child(0)'],
@ -397,23 +367,23 @@ export function main() {
}); });
it('should locate directives in variable bindings', () => { it('should locate directives in variable bindings', () => {
var dirA = new NormalizedDirectiveMetadata( var dirA = CompileDirectiveMetadata.create(
{selector: '[a=b]', type: new TypeMetadata({name: 'DirA'})}); {selector: '[a=b]', exportAs: 'b', type: new CompileTypeMetadata({name: 'DirA'})});
var dirB = new NormalizedDirectiveMetadata( var dirB = CompileDirectiveMetadata.create(
{selector: '[b]', type: new TypeMetadata({name: 'DirB'})}); {selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})});
expect(humanizeTemplateAsts(parse('<div #a="b">', [dirA, dirB]))) expect(humanizeTemplateAsts(parse('<div #a="b">', [dirA, dirB])))
.toEqual([ .toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > div:nth-child(0)[#a=b]'], [DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'] [VariableAst, 'a', 'b', 'TestComp > div:nth-child(0)[#a=b]']
]); ]);
}); });
it('should parse directive host properties', () => { it('should parse directive host properties', () => {
var dirA = new NormalizedDirectiveMetadata({ var dirA = CompileDirectiveMetadata.create({
selector: 'div', selector: 'div',
type: new TypeMetadata({name: 'DirA'}), type: new CompileTypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({hostProperties: {'a': 'expr'}}) host: {'[a]': 'expr'}
}); });
expect(humanizeTemplateAsts(parse('<div></div>', [dirA]))) expect(humanizeTemplateAsts(parse('<div></div>', [dirA])))
.toEqual([ .toEqual([
@ -431,10 +401,10 @@ export function main() {
}); });
it('should parse directive host listeners', () => { it('should parse directive host listeners', () => {
var dirA = new NormalizedDirectiveMetadata({ var dirA = CompileDirectiveMetadata.create({
selector: 'div', selector: 'div',
type: new TypeMetadata({name: 'DirA'}), type: new CompileTypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({hostListeners: {'a': 'expr'}}) host: {'(a)': 'expr'}
}); });
expect(humanizeTemplateAsts(parse('<div></div>', [dirA]))) expect(humanizeTemplateAsts(parse('<div></div>', [dirA])))
.toEqual([ .toEqual([
@ -445,10 +415,10 @@ export function main() {
}); });
it('should parse directive properties', () => { it('should parse directive properties', () => {
var dirA = new NormalizedDirectiveMetadata({ var dirA = CompileDirectiveMetadata.create({
selector: 'div', selector: 'div',
type: new TypeMetadata({name: 'DirA'}), type: new CompileTypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['aProp']}) properties: ['aProp']
}); });
expect(humanizeTemplateAsts(parse('<div [a-prop]="expr"></div>', [dirA]))) expect(humanizeTemplateAsts(parse('<div [a-prop]="expr"></div>', [dirA])))
.toEqual([ .toEqual([
@ -464,10 +434,10 @@ export function main() {
}); });
it('should parse renamed directive properties', () => { it('should parse renamed directive properties', () => {
var dirA = new NormalizedDirectiveMetadata({ var dirA = CompileDirectiveMetadata.create({
selector: 'div', selector: 'div',
type: new TypeMetadata({name: 'DirA'}), type: new CompileTypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['b:a']}) properties: ['b:a']
}); });
expect(humanizeTemplateAsts(parse('<div [a]="expr"></div>', [dirA]))) expect(humanizeTemplateAsts(parse('<div [a]="expr"></div>', [dirA])))
.toEqual([ .toEqual([
@ -478,11 +448,8 @@ export function main() {
}); });
it('should parse literal directive properties', () => { it('should parse literal directive properties', () => {
var dirA = new NormalizedDirectiveMetadata({ var dirA = CompileDirectiveMetadata.create(
selector: 'div', {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), properties: ['a']});
type: new TypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['a']})
});
expect(humanizeTemplateAsts(parse('<div a="literal"></div>', [dirA]))) expect(humanizeTemplateAsts(parse('<div a="literal"></div>', [dirA])))
.toEqual([ .toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ElementAst, 'div', 'TestComp > div:nth-child(0)'],
@ -498,11 +465,8 @@ export function main() {
}); });
it('should support optional directive properties', () => { it('should support optional directive properties', () => {
var dirA = new NormalizedDirectiveMetadata({ var dirA = CompileDirectiveMetadata.create(
selector: 'div', {selector: 'div', type: new CompileTypeMetadata({name: 'DirA'}), properties: ['a']});
type: new TypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['a']})
});
expect(humanizeTemplateAsts(parse('<div></div>', [dirA]))) expect(humanizeTemplateAsts(parse('<div></div>', [dirA])))
.toEqual([ .toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'], [ElementAst, 'div', 'TestComp > div:nth-child(0)'],
@ -512,6 +476,83 @@ export function main() {
}); });
describe('variables', () => {
it('should parse variables via #... and not report them as attributes', () => {
expect(humanizeTemplateAsts(parse('<div #a>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', '', 'TestComp > div:nth-child(0)[#a=]']
]);
});
it('should parse variables via var-... and not report them as attributes', () => {
expect(humanizeTemplateAsts(parse('<div var-a>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', '', 'TestComp > div:nth-child(0)[var-a=]']
]);
});
it('should camel case variables', () => {
expect(humanizeTemplateAsts(parse('<div var-some-a>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'someA', '', 'TestComp > div:nth-child(0)[var-some-a=]']
]);
});
it('should assign variables with empty value to element', () => {
expect(humanizeTemplateAsts(parse('<div #a></div>', [])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', '', 'TestComp > div:nth-child(0)[#a=]']
]);
});
it('should assign variables to directives via exportAs', () => {
var dirA = CompileDirectiveMetadata.create(
{selector: '[a]', type: new CompileTypeMetadata({name: 'DirA'}), exportAs: 'dirA'});
expect(humanizeTemplateAsts(parse('<div #a="dirA"></div>', [dirA])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', 'dirA', 'TestComp > div:nth-child(0)[#a=dirA]']
]);
});
it('should report variables with values that dont match a directive as errors', () => {
expect(() => parse('<div #a="dirA"></div>', [])).toThrowError(`Template parse errors:
There is no directive with "exportAs" set to "dirA" at TestComp > div:nth-child(0)[#a=dirA]`);
});
it('should allow variables with values that dont match a directive on embedded template elements',
() => {
expect(humanizeTemplateAsts(parse('<template #a="b"></template>', [])))
.toEqual([
[EmbeddedTemplateAst, 'TestComp > template:nth-child(0)'],
[VariableAst, 'a', 'b', 'TestComp > template:nth-child(0)[#a=b]']
]);
});
it('should assign variables with empty value to components', () => {
var dirA = CompileDirectiveMetadata.create({
selector: '[a]',
isComponent: true,
type: new CompileTypeMetadata({name: 'DirA'}),
exportAs: 'dirA', template: new CompileTemplateMetadata({ngContentSelectors: []})
});
expect(humanizeTemplateAsts(parse('<div #a></div>', [dirA])))
.toEqual([
[ElementAst, 'div', 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', '', 'TestComp > div:nth-child(0)[#a=]'],
[DirectiveAst, dirA, 'TestComp > div:nth-child(0)'],
[VariableAst, 'a', '', 'TestComp > div:nth-child(0)[#a=]']
]);
});
});
describe('explicit templates', () => { describe('explicit templates', () => {
it('should create embedded templates for <template> elements', () => { it('should create embedded templates for <template> elements', () => {
expect(humanizeTemplateAsts(parse('<template></template>', []))) expect(humanizeTemplateAsts(parse('<template></template>', [])))
@ -563,13 +604,13 @@ export function main() {
describe('directives', () => { describe('directives', () => {
it('should locate directives in property bindings', () => { it('should locate directives in property bindings', () => {
var dirA = new NormalizedDirectiveMetadata({ var dirA = CompileDirectiveMetadata.create({
selector: '[a=b]', selector: '[a=b]',
type: new TypeMetadata({name: 'DirA'}), type: new CompileTypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['a']}) properties: ['a']
}); });
var dirB = new NormalizedDirectiveMetadata( var dirB = CompileDirectiveMetadata.create(
{selector: '[b]', type: new TypeMetadata({name: 'DirB'})}); {selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})});
expect(humanizeTemplateAsts(parse('<div template="a b" b>', [dirA, dirB]))) expect(humanizeTemplateAsts(parse('<div template="a b" b>', [dirA, dirB])))
.toEqual([ .toEqual([
[EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'], [EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'],
@ -587,10 +628,10 @@ export function main() {
}); });
it('should locate directives in variable bindings', () => { it('should locate directives in variable bindings', () => {
var dirA = new NormalizedDirectiveMetadata( var dirA = CompileDirectiveMetadata.create(
{selector: '[a=b]', type: new TypeMetadata({name: 'DirA'})}); {selector: '[a=b]', type: new CompileTypeMetadata({name: 'DirA'})});
var dirB = new NormalizedDirectiveMetadata( var dirB = CompileDirectiveMetadata.create(
{selector: '[b]', type: new TypeMetadata({name: 'DirB'})}); {selector: '[b]', type: new CompileTypeMetadata({name: 'DirB'})});
expect(humanizeTemplateAsts(parse('<div template="#a=b" b>', [dirA, dirB]))) expect(humanizeTemplateAsts(parse('<div template="#a=b" b>', [dirA, dirB])))
.toEqual([ .toEqual([
[EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'], [EmbeddedTemplateAst, 'TestComp > div:nth-child(0)'],
@ -624,12 +665,12 @@ export function main() {
describe('content projection', () => { describe('content projection', () => {
function createComp(selector: string, ngContentSelectors: string[]): function createComp(selector: string, ngContentSelectors: string[]):
NormalizedDirectiveMetadata { CompileDirectiveMetadata {
return new NormalizedDirectiveMetadata({ return CompileDirectiveMetadata.create({
selector: selector, selector: selector,
isComponent: true, isComponent: true,
type: new TypeMetadata({name: 'SomeComp'}), type: new CompileTypeMetadata({name: 'SomeComp'}),
template: new NormalizedTemplateMetadata({ngContentSelectors: ngContentSelectors}) template: new CompileTemplateMetadata({ngContentSelectors: ngContentSelectors})
}) })
} }
@ -725,38 +766,38 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp > div:nth-ch
it('should not throw on invalid property names if the property is used by a directive', it('should not throw on invalid property names if the property is used by a directive',
() => { () => {
var dirA = new NormalizedDirectiveMetadata({ var dirA = CompileDirectiveMetadata.create({
selector: 'div', selector: 'div',
type: new TypeMetadata({name: 'DirA'}), type: new CompileTypeMetadata({name: 'DirA'}),
changeDetection: new ChangeDetectionMetadata({properties: ['invalidProp']}) properties: ['invalidProp']
}); });
expect(() => parse('<div [invalid-prop]></div>', [dirA])).not.toThrow(); expect(() => parse('<div [invalid-prop]></div>', [dirA])).not.toThrow();
}); });
it('should not allow more than 1 component per element', () => { it('should not allow more than 1 component per element', () => {
var dirA = new NormalizedDirectiveMetadata({ var dirA = CompileDirectiveMetadata.create({
selector: 'div', selector: 'div',
isComponent: true, isComponent: true,
type: new TypeMetadata({name: 'DirA'}), type: new CompileTypeMetadata({name: 'DirA'}),
template: new NormalizedTemplateMetadata({ngContentSelectors: []}) template: new CompileTemplateMetadata({ngContentSelectors: []})
}); });
var dirB = new NormalizedDirectiveMetadata({ var dirB = CompileDirectiveMetadata.create({
selector: 'div', selector: 'div',
isComponent: true, isComponent: true,
type: new TypeMetadata({name: 'DirB'}), type: new CompileTypeMetadata({name: 'DirB'}),
template: new NormalizedTemplateMetadata({ngContentSelectors: []}) template: new CompileTemplateMetadata({ngContentSelectors: []})
}); });
expect(() => parse('<div>', [dirB, dirA])).toThrowError(`Template parse errors: expect(() => parse('<div>', [dirB, dirA])).toThrowError(`Template parse errors:
More than one component: DirA,DirB in TestComp > div:nth-child(0)`); More than one component: DirB,DirA in TestComp > div:nth-child(0)`);
}); });
it('should not allow components or element nor event bindings on explicit embedded templates', it('should not allow components or element nor event bindings on explicit embedded templates',
() => { () => {
var dirA = new NormalizedDirectiveMetadata({ var dirA = CompileDirectiveMetadata.create({
selector: '[a]', selector: '[a]',
isComponent: true, isComponent: true,
type: new TypeMetadata({name: 'DirA'}), type: new CompileTypeMetadata({name: 'DirA'}),
template: new NormalizedTemplateMetadata({ngContentSelectors: []}) template: new CompileTemplateMetadata({ngContentSelectors: []})
}); });
expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA])) expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA]))
.toThrowError(`Template parse errors: .toThrowError(`Template parse errors:
@ -766,17 +807,45 @@ Event binding e on an embedded template in TestComp > template:nth-child(0)[(e)=
}); });
it('should not allow components or element bindings on inline embedded templates', () => { it('should not allow components or element bindings on inline embedded templates', () => {
var dirA = new NormalizedDirectiveMetadata({ var dirA = CompileDirectiveMetadata.create({
selector: '[a]', selector: '[a]',
isComponent: true, isComponent: true,
type: new TypeMetadata({name: 'DirA'}), type: new CompileTypeMetadata({name: 'DirA'}),
template: new NormalizedTemplateMetadata({ngContentSelectors: []}) template: new CompileTemplateMetadata({ngContentSelectors: []})
}); });
expect(() => parse('<div *a="b">', [dirA])).toThrowError(`Template parse errors: expect(() => parse('<div *a="b">', [dirA])).toThrowError(`Template parse errors:
Components on an embedded template: DirA in TestComp > div:nth-child(0) Components on an embedded template: DirA in TestComp > div:nth-child(0)
Property binding a not used by any directive on an embedded template in TestComp > div:nth-child(0)[*a=b]`); Property binding a not used by any directive on an embedded template in TestComp > div:nth-child(0)[*a=b]`);
}); });
}); });
describe('ignore elements', () => {
it('should ignore <script> elements but include them for source info', () => {
expect(humanizeTemplateAsts(parse('<script></script>a', [])))
.toEqual([[TextAst, 'a', 'TestComp > #text(a):nth-child(1)']]);
});
it('should ignore <style> elements but include them for source info', () => {
expect(humanizeTemplateAsts(parse('<style></style>a', [])))
.toEqual([[TextAst, 'a', 'TestComp > #text(a):nth-child(1)']]);
});
it('should ignore <link rel="stylesheet"> elements but include them for source info', () => {
expect(humanizeTemplateAsts(parse('<link rel="stylesheet"></link>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('<div ng-non-bindable>b</div>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.attrs);
templateVisitAll(this, ast.properties); templateVisitAll(this, ast.properties);
templateVisitAll(this, ast.events); templateVisitAll(this, ast.events);
templateVisitAll(this, ast.vars); templateVisitAll(this, ast.exportAsVars);
templateVisitAll(this, ast.directives); templateVisitAll(this, ast.directives);
templateVisitAll(this, ast.children); templateVisitAll(this, ast.children);
return null; return null;
@ -852,6 +921,7 @@ class TemplateHumanizer implements TemplateAstVisitor {
templateVisitAll(this, ast.properties); templateVisitAll(this, ast.properties);
templateVisitAll(this, ast.hostProperties); templateVisitAll(this, ast.hostProperties);
templateVisitAll(this, ast.hostEvents); templateVisitAll(this, ast.hostEvents);
templateVisitAll(this, ast.exportAsVars);
return null; return null;
} }
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any { visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {

View File

@ -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: '<p no-duplicate></p>',
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', it('should support directives where a selector matches property binding',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata( 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: '<child-cmp></child-cmp>',
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', it('should read directives metadata from their binding token',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({ tcb.overrideView(MyComp, new ViewMetadata({
template: '<div public-api><div needs-public-api></div></div>', template: '<div public-api><div needs-public-api></div></div>',
directives: [bind(PublicApi).toClass(PrivateImpl), NeedsPublicApi] directives: [PrivateImpl, NeedsPublicApi]
})) }))
.createAsync(MyComp) .createAsync(MyComp)
@ -2043,12 +2010,14 @@ class NeedsAttribute {
} }
} }
@Directive({selector: '[public-api]'})
@Injectable() @Injectable()
class PublicApi { class PublicApi {
} }
@Directive({selector: '[private-impl]'}) @Directive({
selector: '[public-api]',
bindings: [new Binding(PublicApi, {toAlias: PrivateImpl, deps: []})]
})
@Injectable() @Injectable()
class PrivateImpl extends PublicApi { class PrivateImpl extends PublicApi {
} }

View File

@ -42,11 +42,7 @@ export function main() {
@Component({selector: 'app', viewBindings: [forwardRef(() => Frame)]}) @Component({selector: 'app', viewBindings: [forwardRef(() => Frame)]})
@View({ @View({
template: `<door><lock></lock></door>`, template: `<door><lock></lock></door>`,
directives: [ directives: [forwardRef(() => Door), forwardRef(() => Lock)]
bind(forwardRef(() => Door))
.toClass(forwardRef(() => Door)),
bind(forwardRef(() => Lock)).toClass(forwardRef(() => Lock))
]
}) })
class App { class App {
} }