diff --git a/modules/@angular/compiler-cli/src/codegen.ts b/modules/@angular/compiler-cli/src/codegen.ts index ed2439f1fa..b1e56725db 100644 --- a/modules/@angular/compiler-cli/src/codegen.ts +++ b/modules/@angular/compiler-cli/src/codegen.ts @@ -11,7 +11,7 @@ * Intended to be used in a build step. */ import * as compiler from '@angular/compiler'; -import {Component, NgModule, ViewEncapsulation} from '@angular/core'; +import {Directive, NgModule, ViewEncapsulation} from '@angular/core'; import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped'; import * as path from 'path'; import * as ts from 'typescript'; @@ -58,7 +58,7 @@ export class CodeGeneratorModuleCollector { private readFileMetadata(absSourcePath: string): FileMetadata { const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath); - const result: FileMetadata = {components: [], ngModules: [], fileUrl: absSourcePath}; + const result: FileMetadata = {directives: [], ngModules: [], fileUrl: absSourcePath}; if (!moduleMetadata) { console.log(`WARNING: no metadata found for ${absSourcePath}`); return result; @@ -78,8 +78,8 @@ export class CodeGeneratorModuleCollector { annotations.forEach((annotation) => { if (annotation instanceof NgModule) { result.ngModules.push(staticType); - } else if (annotation instanceof Component) { - result.components.push(staticType); + } else if (annotation instanceof Directive) { + result.directives.push(staticType); } }); } @@ -127,7 +127,7 @@ export class CodeGenerator { (fileMeta) => this.compiler .compile( - fileMeta.fileUrl, analyzedNgModules, fileMeta.components, fileMeta.ngModules) + fileMeta.fileUrl, analyzedNgModules, fileMeta.directives, fileMeta.ngModules) .then((generatedModules) => { generatedModules.forEach((generatedModule) => { const sourceFile = this.program.getSourceFile(fileMeta.fileUrl); @@ -193,8 +193,9 @@ export class CodeGenerator { // TODO(vicb): do not pass cliOptions.i18nFormat here const offlineCompiler = new compiler.OfflineCompiler( resolver, normalizer, tmplParser, new compiler.StyleCompiler(urlResolver), - new compiler.ViewCompiler(config), new compiler.NgModuleCompiler(), - new compiler.TypeScriptEmitter(reflectorHost), cliOptions.locale, cliOptions.i18nFormat); + new compiler.ViewCompiler(config), new compiler.DirectiveWrapperCompiler(config), + new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost), + cliOptions.locale, cliOptions.i18nFormat); return new CodeGenerator( options, program, compilerHost, staticReflector, offlineCompiler, reflectorHost); @@ -203,6 +204,6 @@ export class CodeGenerator { export interface FileMetadata { fileUrl: string; - components: StaticSymbol[]; + directives: StaticSymbol[]; ngModules: StaticSymbol[]; } diff --git a/modules/@angular/compiler-cli/src/extract_i18n.ts b/modules/@angular/compiler-cli/src/extract_i18n.ts index b32fa92f3f..91b7c663e1 100644 --- a/modules/@angular/compiler-cli/src/extract_i18n.ts +++ b/modules/@angular/compiler-cli/src/extract_i18n.ts @@ -113,7 +113,7 @@ export class Extractor { const url = fileMeta.fileUrl; return Promise.all(fileMeta.components.map(compType => { const compMeta = this.metadataResolver.getDirectiveMetadata(compType); - const ngModule = analyzedNgModules.ngModuleByComponent.get(compType); + const ngModule = analyzedNgModules.ngModuleByDirective.get(compType); if (!ngModule) { throw new Error( `Cannot determine the module for component ${compMeta.type.name}!`); diff --git a/modules/@angular/compiler/index.ts b/modules/@angular/compiler/index.ts index ea36efc006..fc5517df20 100644 --- a/modules/@angular/compiler/index.ts +++ b/modules/@angular/compiler/index.ts @@ -44,6 +44,7 @@ export * from './src/metadata_resolver'; export * from './src/ml_parser/html_parser'; export * from './src/ml_parser/interpolation_config'; export {NgModuleCompiler} from './src/ng_module_compiler'; +export {DirectiveWrapperCompiler} from './src/directive_wrapper_compiler'; export * from './src/output/path_util'; export * from './src/output/ts_emitter'; export * from './src/parse_util'; diff --git a/modules/@angular/compiler/src/compiler.ts b/modules/@angular/compiler/src/compiler.ts index 091ed8df15..268a62bbfb 100644 --- a/modules/@angular/compiler/src/compiler.ts +++ b/modules/@angular/compiler/src/compiler.ts @@ -11,6 +11,7 @@ import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, In import {CompilerConfig} from './config'; import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveResolver} from './directive_resolver'; +import {DirectiveWrapperCompiler} from './directive_wrapper_compiler'; import {Lexer} from './expression_parser/lexer'; import {Parser} from './expression_parser/parser'; import * as i18n from './i18n/index'; @@ -64,6 +65,7 @@ export const COMPILER_PROVIDERS: Array|{[k: string]: any}|any[]> = StyleCompiler, ViewCompiler, NgModuleCompiler, + DirectiveWrapperCompiler, {provide: CompilerConfig, useValue: new CompilerConfig()}, RuntimeCompiler, {provide: Compiler, useExisting: RuntimeCompiler}, diff --git a/modules/@angular/compiler/src/directive_wrapper_compiler.ts b/modules/@angular/compiler/src/directive_wrapper_compiler.ts new file mode 100644 index 0000000000..0aca4c284b --- /dev/null +++ b/modules/@angular/compiler/src/directive_wrapper_compiler.ts @@ -0,0 +1,186 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Injectable} from '@angular/core'; + +import {CompileDirectiveMetadata, CompileIdentifierMetadata} from './compile_metadata'; +import {CompilerConfig} from './config'; +import {Identifiers, resolveIdentifier} from './identifiers'; +import * as o from './output/output_ast'; +import {LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core'; + +export class DirectiveWrapperCompileResult { + constructor(public statements: o.Statement[], public dirWrapperClassVar: string) {} +} + +const CONTEXT_FIELD_NAME = 'context'; +const CHANGES_FIELD_NAME = 'changes'; +const CHANGED_FIELD_NAME = 'changed'; + +const CURR_VALUE_VAR = o.variable('currValue'); +const THROW_ON_CHANGE_VAR = o.variable('throwOnChange'); +const FORCE_UPDATE_VAR = o.variable('forceUpdate'); +const VIEW_VAR = o.variable('view'); +const RENDER_EL_VAR = o.variable('el'); + +const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap([])).toStmt(); + +/** + * We generate directive wrappers to prevent code bloat when a directive is used. + * A directive wrapper encapsulates + * the dirty checking for `@Input`, the handling of `@HostListener` / `@HostBinding` + * and calling the lifecyclehooks `ngOnInit`, `ngOnChanges`, `ngDoCheck`. + * + * So far, only `@Input` and the lifecycle hooks have been implemented. + */ +@Injectable() +export class DirectiveWrapperCompiler { + static dirWrapperClassName(id: CompileIdentifierMetadata) { return `Wrapper_${id.name}`; } + + constructor(private compilerConfig: CompilerConfig) {} + + compile(dirMeta: CompileDirectiveMetadata): DirectiveWrapperCompileResult { + const dirDepParamNames: string[] = []; + for (let i = 0; i < dirMeta.type.diDeps.length; i++) { + dirDepParamNames.push(`p${i}`); + } + const dirLifecycleHooks = dirMeta.type.lifecycleHooks; + let lifecycleHooks: GenConfig = { + genChanges: dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1 || + this.compilerConfig.logBindingUpdate, + ngOnChanges: dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1, + ngOnInit: dirLifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1, + ngDoCheck: dirLifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1 + }; + + const fields: o.ClassField[] = [ + new o.ClassField(CONTEXT_FIELD_NAME, o.importType(dirMeta.type)), + new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE), + ]; + const ctorStmts: o.Statement[] = + [o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt()]; + if (lifecycleHooks.genChanges) { + fields.push(new o.ClassField(CHANGES_FIELD_NAME, new o.MapType(o.DYNAMIC_TYPE))); + ctorStmts.push(RESET_CHANGES_STMT); + } + + const methods: o.ClassMethod[] = []; + Object.keys(dirMeta.inputs).forEach((inputFieldName, idx) => { + const fieldName = `_${inputFieldName}`; + // private is fine here as no child view will reference the cached value... + fields.push(new o.ClassField(fieldName, null, [o.StmtModifier.Private])); + ctorStmts.push(o.THIS_EXPR.prop(fieldName) + .set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED))) + .toStmt()); + methods.push(checkInputMethod(inputFieldName, o.THIS_EXPR.prop(fieldName), lifecycleHooks)); + }); + methods.push(detectChangesInternalMethod(lifecycleHooks, this.compilerConfig.genDebugInfo)); + + ctorStmts.push( + o.THIS_EXPR.prop(CONTEXT_FIELD_NAME) + .set(o.importExpr(dirMeta.type) + .instantiate(dirDepParamNames.map((paramName) => o.variable(paramName)))) + .toStmt()); + const ctor = new o.ClassMethod( + null, dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)), + ctorStmts); + + const wrapperClassName = DirectiveWrapperCompiler.dirWrapperClassName(dirMeta.type); + const classStmt = new o.ClassStmt(wrapperClassName, null, fields, [], ctor, methods); + return new DirectiveWrapperCompileResult([classStmt], wrapperClassName); + } +} + +function detectChangesInternalMethod( + lifecycleHooks: GenConfig, logBindingUpdate: boolean): o.ClassMethod { + const changedVar = o.variable('changed'); + const stmts: o.Statement[] = [ + changedVar.set(o.THIS_EXPR.prop(CHANGED_FIELD_NAME)).toDeclStmt(), + o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt(), + ]; + const lifecycleStmts: o.Statement[] = []; + + if (lifecycleHooks.genChanges) { + const onChangesStmts: o.Statement[] = []; + if (lifecycleHooks.ngOnChanges) { + onChangesStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME) + .callMethod('ngOnChanges', [o.THIS_EXPR.prop(CHANGES_FIELD_NAME)]) + .toStmt()); + } + if (logBindingUpdate) { + onChangesStmts.push( + o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfoForChanges)) + .callFn( + [VIEW_VAR.prop('renderer'), RENDER_EL_VAR, o.THIS_EXPR.prop(CHANGES_FIELD_NAME)]) + .toStmt()); + } + onChangesStmts.push(RESET_CHANGES_STMT); + lifecycleStmts.push(new o.IfStmt(changedVar, onChangesStmts)); + } + + if (lifecycleHooks.ngOnInit) { + lifecycleStmts.push(new o.IfStmt( + VIEW_VAR.prop('numberOfChecks').identical(new o.LiteralExpr(0)), + [o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnInit', []).toStmt()])); + } + if (lifecycleHooks.ngDoCheck) { + lifecycleStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngDoCheck', []).toStmt()); + } + if (lifecycleStmts.length > 0) { + stmts.push(new o.IfStmt(o.not(THROW_ON_CHANGE_VAR), lifecycleStmts)); + } + stmts.push(new o.ReturnStatement(changedVar)); + + return new o.ClassMethod( + 'detectChangesInternal', + [ + new o.FnParam( + VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])), + new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE), + new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE), + ], + stmts, o.BOOL_TYPE); +} + +function checkInputMethod( + input: string, fieldExpr: o.ReadPropExpr, lifecycleHooks: GenConfig): o.ClassMethod { + var onChangeStatements: o.Statement[] = [ + o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(true)).toStmt(), + o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).prop(input).set(CURR_VALUE_VAR).toStmt(), + ]; + if (lifecycleHooks.genChanges) { + onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME) + .key(o.literal(input)) + .set(o.importExpr(resolveIdentifier(Identifiers.SimpleChange)) + .instantiate([fieldExpr, CURR_VALUE_VAR])) + .toStmt()); + } + onChangeStatements.push(fieldExpr.set(CURR_VALUE_VAR).toStmt()); + + var methodBody: o.Statement[] = [ + new o.IfStmt( + FORCE_UPDATE_VAR.or(o.importExpr(resolveIdentifier(Identifiers.checkBinding)) + .callFn([THROW_ON_CHANGE_VAR, fieldExpr, CURR_VALUE_VAR])), + onChangeStatements), + ]; + return new o.ClassMethod( + `check_${input}`, + [ + new o.FnParam(CURR_VALUE_VAR.name, o.DYNAMIC_TYPE), + new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE), + new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE), + ], + methodBody); +} + +interface GenConfig { + genChanges: boolean; + ngOnChanges: boolean; + ngOnInit: boolean; + ngDoCheck: boolean; +} \ No newline at end of file diff --git a/modules/@angular/compiler/src/identifiers.ts b/modules/@angular/compiler/src/identifiers.ts index 5df3eecb1e..49f549743c 100644 --- a/modules/@angular/compiler/src/identifiers.ts +++ b/modules/@angular/compiler/src/identifiers.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ElementRef, Injector, LOCALE_ID as LOCALE_ID_, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT as TRANSLATIONS_FORMAT_, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; +import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata'; -import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes, castByValue, checkBinding, clearStyles, collectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, reflector, registerModuleFactory, renderStyles} from './private_import_core'; +import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core'; import {assetUrl} from './util'; var APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view'); @@ -33,7 +33,7 @@ export class Identifiers { static ViewUtils: IdentifierSpec = { name: 'ViewUtils', moduleUrl: assetUrl('core', 'linker/view_utils'), - runtime: ViewUtils + runtime: view_utils.ViewUtils }; static AppView: IdentifierSpec = {name: 'AppView', moduleUrl: APP_VIEW_MODULE_URL, runtime: AppView}; @@ -161,45 +161,48 @@ export class Identifiers { static checkBinding: IdentifierSpec = { name: 'checkBinding', moduleUrl: VIEW_UTILS_MODULE_URL, - runtime: checkBinding + runtime: view_utils.checkBinding }; static flattenNestedViewRenderNodes: IdentifierSpec = { name: 'flattenNestedViewRenderNodes', moduleUrl: VIEW_UTILS_MODULE_URL, - runtime: flattenNestedViewRenderNodes + runtime: view_utils.flattenNestedViewRenderNodes }; static devModeEqual: IdentifierSpec = {name: 'devModeEqual', moduleUrl: CD_MODULE_URL, runtime: devModeEqual}; static interpolate: IdentifierSpec = { name: 'interpolate', moduleUrl: VIEW_UTILS_MODULE_URL, - runtime: interpolate + runtime: view_utils.interpolate }; static castByValue: IdentifierSpec = { name: 'castByValue', moduleUrl: VIEW_UTILS_MODULE_URL, - runtime: castByValue + runtime: view_utils.castByValue }; static EMPTY_ARRAY: IdentifierSpec = { name: 'EMPTY_ARRAY', moduleUrl: VIEW_UTILS_MODULE_URL, - runtime: EMPTY_ARRAY + runtime: view_utils.EMPTY_ARRAY + }; + static EMPTY_MAP: IdentifierSpec = { + name: 'EMPTY_MAP', + moduleUrl: VIEW_UTILS_MODULE_URL, + runtime: view_utils.EMPTY_MAP }; - static EMPTY_MAP: - IdentifierSpec = {name: 'EMPTY_MAP', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: EMPTY_MAP}; static pureProxies = [ null, - {name: 'pureProxy1', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy1}, - {name: 'pureProxy2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy2}, - {name: 'pureProxy3', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy3}, - {name: 'pureProxy4', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy4}, - {name: 'pureProxy5', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy5}, - {name: 'pureProxy6', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy6}, - {name: 'pureProxy7', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy7}, - {name: 'pureProxy8', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy8}, - {name: 'pureProxy9', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy9}, - {name: 'pureProxy10', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy10}, + {name: 'pureProxy1', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy1}, + {name: 'pureProxy2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy2}, + {name: 'pureProxy3', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy3}, + {name: 'pureProxy4', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy4}, + {name: 'pureProxy5', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy5}, + {name: 'pureProxy6', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy6}, + {name: 'pureProxy7', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy7}, + {name: 'pureProxy8', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy8}, + {name: 'pureProxy9', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy9}, + {name: 'pureProxy10', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy10}, ]; static SecurityContext: IdentifierSpec = { name: 'SecurityContext', @@ -259,12 +262,22 @@ export class Identifiers { static LOCALE_ID: IdentifierSpec = { name: 'LOCALE_ID', moduleUrl: assetUrl('core', 'i18n/tokens'), - runtime: LOCALE_ID_ + runtime: LOCALE_ID }; static TRANSLATIONS_FORMAT: IdentifierSpec = { name: 'TRANSLATIONS_FORMAT', moduleUrl: assetUrl('core', 'i18n/tokens'), - runtime: TRANSLATIONS_FORMAT_ + runtime: TRANSLATIONS_FORMAT + }; + static setBindingDebugInfo: IdentifierSpec = { + name: 'setBindingDebugInfo', + moduleUrl: VIEW_UTILS_MODULE_URL, + runtime: view_utils.setBindingDebugInfo + }; + static setBindingDebugInfoForChanges: IdentifierSpec = { + name: 'setBindingDebugInfoForChanges', + moduleUrl: VIEW_UTILS_MODULE_URL, + runtime: view_utils.setBindingDebugInfoForChanges }; static AnimationTransition: IdentifierSpec = { name: 'AnimationTransition', diff --git a/modules/@angular/compiler/src/metadata_resolver.ts b/modules/@angular/compiler/src/metadata_resolver.ts index aa83ea7631..56806d717c 100644 --- a/modules/@angular/compiler/src/metadata_resolver.ts +++ b/modules/@angular/compiler/src/metadata_resolver.ts @@ -116,8 +116,7 @@ export class CompileMetadataResolver { return null; } - getDirectiveMetadata(directiveType: Type, throwIfNotFound = true): - cpl.CompileDirectiveMetadata { + getDirectiveMetadata(directiveType: any, throwIfNotFound = true): cpl.CompileDirectiveMetadata { directiveType = resolveForwardRef(directiveType); let meta = this._directiveCache.get(directiveType); if (!meta) { diff --git a/modules/@angular/compiler/src/offline_compiler.ts b/modules/@angular/compiler/src/offline_compiler.ts index e97490ed37..1cc084180d 100644 --- a/modules/@angular/compiler/src/offline_compiler.ts +++ b/modules/@angular/compiler/src/offline_compiler.ts @@ -12,6 +12,7 @@ import {AnimationCompiler} from './animation/animation_compiler'; import {AnimationParser} from './animation/animation_parser'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata'; import {DirectiveNormalizer} from './directive_normalizer'; +import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from './directive_wrapper_compiler'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers'; import {CompileMetadataResolver} from './metadata_resolver'; import {NgModuleCompiler} from './ng_module_compiler'; @@ -19,7 +20,7 @@ import {OutputEmitter} from './output/abstract_emitter'; import * as o from './output/output_ast'; import {CompiledStylesheet, StyleCompiler} from './style_compiler'; import {TemplateParser} from './template_parser/template_parser'; -import {ComponentFactoryDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler'; +import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler'; export class SourceModule { constructor(public moduleUrl: string, public source: string) {} @@ -27,25 +28,23 @@ export class SourceModule { export class NgModulesSummary { constructor( - public ngModuleByComponent: Map, + public ngModuleByDirective: Map, public ngModules: CompileNgModuleMetadata[]) {} } export function analyzeModules( ngModules: StaticSymbol[], metadataResolver: CompileMetadataResolver) { - const ngModuleByComponent = new Map(); + const ngModuleByDirective = new Map(); const modules: CompileNgModuleMetadata[] = []; ngModules.forEach((ngModule) => { const ngModuleMeta = metadataResolver.getNgModuleMetadata(ngModule); modules.push(ngModuleMeta); ngModuleMeta.declaredDirectives.forEach((dirMeta: CompileDirectiveMetadata) => { - if (dirMeta.isComponent) { - ngModuleByComponent.set(dirMeta.type.reference, ngModuleMeta); - } + ngModuleByDirective.set(dirMeta.type.reference, ngModuleMeta); }); }); - return new NgModulesSummary(ngModuleByComponent, modules); + return new NgModulesSummary(ngModuleByDirective, modules); } export class OfflineCompiler { @@ -56,6 +55,7 @@ export class OfflineCompiler { private _metadataResolver: CompileMetadataResolver, private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, + private _dirWrapperCompiler: DirectiveWrapperCompiler, private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter, private _localeId: string, private _translationFormat: string) {} @@ -69,7 +69,7 @@ export class OfflineCompiler { } compile( - moduleUrl: string, ngModulesSummary: NgModulesSummary, components: StaticSymbol[], + moduleUrl: string, ngModulesSummary: NgModulesSummary, directives: StaticSymbol[], ngModules: StaticSymbol[]): Promise { const fileSuffix = _splitTypescriptSuffix(moduleUrl)[1]; const statements: o.Statement[] = []; @@ -80,11 +80,18 @@ export class OfflineCompiler { exportedVars.push( ...ngModules.map((ngModuleType) => this._compileModule(ngModuleType, statements))); + // compile directive wrappers + exportedVars.push(...directives.map( + (directiveType) => this._compileDirectiveWrapper(directiveType, statements))); + // compile components return Promise - .all(components.map((compType) => { - const compMeta = this._metadataResolver.getDirectiveMetadata(compType); - const ngModule = ngModulesSummary.ngModuleByComponent.get(compType); + .all(directives.map((dirType) => { + const compMeta = this._metadataResolver.getDirectiveMetadata(dirType); + if (!compMeta.isComponent) { + return Promise.resolve(null); + } + const ngModule = ngModulesSummary.ngModuleByDirective.get(dirType); if (!ngModule) { throw new Error(`Cannot determine the module for component ${compMeta.type.name}!`); } @@ -148,6 +155,15 @@ export class OfflineCompiler { return appCompileResult.ngModuleFactoryVar; } + private _compileDirectiveWrapper(directiveType: StaticSymbol, targetStatements: o.Statement[]): + string { + const dirMeta = this._metadataResolver.getDirectiveMetadata(directiveType); + const dirCompileResult = this._dirWrapperCompiler.compile(dirMeta); + + targetStatements.push(...dirCompileResult.statements); + return dirCompileResult.dirWrapperClassVar; + } + private _compileComponentFactory( compMeta: CompileDirectiveMetadata, fileSuffix: string, targetStatements: o.Statement[]): string { @@ -217,6 +233,9 @@ function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[] const cfd = dep; cfd.placeholder.name = _componentFactoryName(cfd.comp); cfd.placeholder.moduleUrl = _ngfactoryModuleUrl(cfd.comp.moduleUrl); + } else if (dep instanceof DirectiveWrapperDependency) { + const dwd = dep; + dwd.placeholder.moduleUrl = _ngfactoryModuleUrl(dwd.dir.moduleUrl); } }); return compileResult.statements; @@ -231,8 +250,8 @@ function _resolveStyleStatements( return compileResult.statements; } -function _ngfactoryModuleUrl(compUrl: string): string { - const urlWithSuffix = _splitTypescriptSuffix(compUrl); +function _ngfactoryModuleUrl(dirUrl: string): string { + const urlWithSuffix = _splitTypescriptSuffix(dirUrl); return `${urlWithSuffix[0]}.ngfactory${urlWithSuffix[1]}`; } diff --git a/modules/@angular/compiler/src/output/output_interpreter.ts b/modules/@angular/compiler/src/output/output_interpreter.ts index ecd0283cf5..7f976b7147 100644 --- a/modules/@angular/compiler/src/output/output_interpreter.ts +++ b/modules/@angular/compiler/src/output/output_interpreter.ts @@ -81,7 +81,7 @@ function createDynamicClass( _executeFunctionStatements( ctorParamNames, args, _classStmt.constructorMethod.body, instanceCtx, _visitor); }; - var superClass = _classStmt.parent.visitExpression(_visitor, _ctx); + var superClass = _classStmt.parent ? _classStmt.parent.visitExpression(_visitor, _ctx) : Object; ctor.prototype = Object.create(superClass.prototype, propertyDescriptors); return ctor; } diff --git a/modules/@angular/compiler/src/private_import_core.ts b/modules/@angular/compiler/src/private_import_core.ts index 42d1486aa8..5ddb2c588c 100644 --- a/modules/@angular/compiler/src/private_import_core.ts +++ b/modules/@angular/compiler/src/private_import_core.ts @@ -27,13 +27,7 @@ export const NgModuleInjector: typeof r.NgModuleInjector = r.NgModuleInjector; export const registerModuleFactory: typeof r.registerModuleFactory = r.registerModuleFactory; export type ViewType = typeof r._ViewType; export const ViewType: typeof r.ViewType = r.ViewType; -export const MAX_INTERPOLATION_VALUES: typeof r.MAX_INTERPOLATION_VALUES = - r.MAX_INTERPOLATION_VALUES; -export const checkBinding: typeof r.checkBinding = r.checkBinding; -export const flattenNestedViewRenderNodes: typeof r.flattenNestedViewRenderNodes = - r.flattenNestedViewRenderNodes; -export const interpolate: typeof r.interpolate = r.interpolate; -export const ViewUtils: typeof r.ViewUtils = r.ViewUtils; +export const view_utils: typeof r.view_utils = r.view_utils; export const DebugContext: typeof r.DebugContext = r.DebugContext; export const StaticNodeDebugInfo: typeof r.StaticNodeDebugInfo = r.StaticNodeDebugInfo; export const devModeEqual: typeof r.devModeEqual = r.devModeEqual; @@ -42,19 +36,6 @@ export const ValueUnwrapper: typeof r.ValueUnwrapper = r.ValueUnwrapper; export const TemplateRef_: typeof r.TemplateRef_ = r.TemplateRef_; export type RenderDebugInfo = typeof r._RenderDebugInfo; export const RenderDebugInfo: typeof r.RenderDebugInfo = r.RenderDebugInfo; -export const EMPTY_ARRAY: typeof r.EMPTY_ARRAY = r.EMPTY_ARRAY; -export const EMPTY_MAP: typeof r.EMPTY_MAP = r.EMPTY_MAP; -export const pureProxy1: typeof r.pureProxy1 = r.pureProxy1; -export const pureProxy2: typeof r.pureProxy2 = r.pureProxy2; -export const pureProxy3: typeof r.pureProxy3 = r.pureProxy3; -export const pureProxy4: typeof r.pureProxy4 = r.pureProxy4; -export const pureProxy5: typeof r.pureProxy5 = r.pureProxy5; -export const pureProxy6: typeof r.pureProxy6 = r.pureProxy6; -export const pureProxy7: typeof r.pureProxy7 = r.pureProxy7; -export const pureProxy8: typeof r.pureProxy8 = r.pureProxy8; -export const pureProxy9: typeof r.pureProxy9 = r.pureProxy9; -export const pureProxy10: typeof r.pureProxy10 = r.pureProxy10; -export const castByValue: typeof r.castByValue = r.castByValue; export type Console = typeof r._Console; export const Console: typeof r.Console = r.Console; export const reflector: typeof r.reflector = r.reflector; diff --git a/modules/@angular/compiler/src/runtime_compiler.ts b/modules/@angular/compiler/src/runtime_compiler.ts index f91af0272e..388bc1725a 100644 --- a/modules/@angular/compiler/src/runtime_compiler.ts +++ b/modules/@angular/compiler/src/runtime_compiler.ts @@ -7,11 +7,13 @@ */ import {Compiler, ComponentFactory, Injectable, Injector, ModuleWithComponentFactories, NgModuleFactory, SchemaMetadata, Type} from '@angular/core'; + import {AnimationCompiler} from './animation/animation_compiler'; import {AnimationParser} from './animation/animation_parser'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta} from './compile_metadata'; import {CompilerConfig} from './config'; import {DirectiveNormalizer} from './directive_normalizer'; +import {DirectiveWrapperCompiler} from './directive_wrapper_compiler'; import {stringify} from './facade/lang'; import {CompileMetadataResolver} from './metadata_resolver'; import {NgModuleCompiler} from './ng_module_compiler'; @@ -22,7 +24,8 @@ import {ComponentStillLoadingError} from './private_import_core'; import {CompiledStylesheet, StyleCompiler} from './style_compiler'; import {TemplateParser} from './template_parser/template_parser'; import {SyncAsyncResult} from './util'; -import {ComponentFactoryDependency, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler'; +import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler'; + /** * An internal module of the Angular compiler that begins with component types, @@ -37,6 +40,7 @@ import {ComponentFactoryDependency, ViewCompiler, ViewFactoryDependency} from '. export class RuntimeCompiler implements Compiler { private _compiledTemplateCache = new Map, CompiledTemplate>(); private _compiledHostTemplateCache = new Map, CompiledTemplate>(); + private _compiledDirectiveWrapperCache = new Map, Type>(); private _compiledNgModuleCache = new Map, NgModuleFactory>(); private _animationParser = new AnimationParser(); private _animationCompiler = new AnimationCompiler(); @@ -45,7 +49,9 @@ export class RuntimeCompiler implements Compiler { private _injector: Injector, private _metadataResolver: CompileMetadataResolver, private _templateNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, - private _ngModuleCompiler: NgModuleCompiler, private _compilerConfig: CompilerConfig) {} + private _ngModuleCompiler: NgModuleCompiler, + private _directiveWrapperCompiler: DirectiveWrapperCompiler, + private _compilerConfig: CompilerConfig) {} get injector(): Injector { return this._injector; } @@ -80,10 +86,11 @@ export class RuntimeCompiler implements Compiler { const moduleMeta = this._metadataResolver.getNgModuleMetadata(moduleType); const componentFactories: ComponentFactory[] = []; const templates = new Set(); - moduleMeta.transitiveModule.modules.forEach((moduleMeta) => { - moduleMeta.declaredDirectives.forEach((dirMeta) => { + moduleMeta.transitiveModule.modules.forEach((localModuleMeta) => { + localModuleMeta.declaredDirectives.forEach((dirMeta) => { if (dirMeta.isComponent) { - const template = this._createCompiledHostTemplate(dirMeta.type.reference); + const template = + this._createCompiledHostTemplate(dirMeta.type.reference, localModuleMeta); templates.add(template); componentFactories.push(template.proxyComponentFactory); } @@ -119,7 +126,7 @@ export class RuntimeCompiler implements Compiler { interpretStatements(compileResult.statements, compileResult.ngModuleFactoryVar); } else { ngModuleFactory = jitStatements( - `${moduleMeta.type.name}.ngfactory.js`, compileResult.statements, + `/${moduleMeta.type.name}/module.ngfactory.js`, compileResult.statements, compileResult.ngModuleFactoryVar); } this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory); @@ -135,22 +142,32 @@ export class RuntimeCompiler implements Compiler { var loadingPromises: Promise[] = []; const ngModule = this._metadataResolver.getNgModuleMetadata(mainModule); + const moduleByDirective = new Map(); + ngModule.transitiveModule.modules.forEach((localModuleMeta) => { + localModuleMeta.declaredDirectives.forEach((dirMeta) => { + moduleByDirective.set(dirMeta.type.reference, localModuleMeta); + this._compileDirectiveWrapper(dirMeta, localModuleMeta); + if (dirMeta.isComponent) { + templates.add(this._createCompiledTemplate(dirMeta, localModuleMeta)); + } + }); + }); ngModule.transitiveModule.modules.forEach((localModuleMeta) => { localModuleMeta.declaredDirectives.forEach((dirMeta) => { if (dirMeta.isComponent) { - templates.add(this._createCompiledTemplate(dirMeta, localModuleMeta)); dirMeta.entryComponents.forEach((entryComponentType) => { - templates.add(this._createCompiledHostTemplate(entryComponentType.reference)); + const moduleMeta = moduleByDirective.get(entryComponentType.reference); + templates.add( + this._createCompiledHostTemplate(entryComponentType.reference, moduleMeta)); }); - // TODO: what about entryComponents of entryComponents? maybe skip here and just do the - // below? } }); localModuleMeta.entryComponents.forEach((entryComponentType) => { - templates.add(this._createCompiledHostTemplate(entryComponentType.reference)); - // TODO: what about entryComponents of entryComponents? + const moduleMeta = moduleByDirective.get(entryComponentType.reference); + templates.add(this._createCompiledHostTemplate(entryComponentType.reference, moduleMeta)); }); }); + templates.forEach((template) => { if (template.loading) { if (isSync) { @@ -189,14 +206,19 @@ export class RuntimeCompiler implements Compiler { this._compiledNgModuleCache.clear(); } - private _createCompiledHostTemplate(compType: Type): CompiledTemplate { + private _createCompiledHostTemplate(compType: Type, ngModule: CompileNgModuleMetadata): + CompiledTemplate { + if (!ngModule) { + throw new Error( + `Component ${stringify(compType)} is not part of any NgModule or the module has not been imported into your module.`); + } var compiledTemplate = this._compiledHostTemplateCache.get(compType); if (!compiledTemplate) { var compMeta = this._metadataResolver.getDirectiveMetadata(compType); assertComponent(compMeta); var hostMeta = createHostComponentMeta(compMeta); compiledTemplate = new CompiledTemplate( - true, compMeta.selector, compMeta.type, [compMeta], [], [], + true, compMeta.selector, compMeta.type, ngModule, [compMeta], this._templateNormalizer.normalizeDirective(hostMeta)); this._compiledHostTemplateCache.set(compType, compiledTemplate); } @@ -209,8 +231,7 @@ export class RuntimeCompiler implements Compiler { if (!compiledTemplate) { assertComponent(compMeta); compiledTemplate = new CompiledTemplate( - false, compMeta.selector, compMeta.type, ngModule.transitiveModule.directives, - ngModule.transitiveModule.pipes, ngModule.schemas, + false, compMeta.selector, compMeta.type, ngModule, ngModule.transitiveModule.directives, this._templateNormalizer.normalizeDirective(compMeta)); this._compiledTemplateCache.set(compMeta.type.reference, compiledTemplate); } @@ -221,13 +242,8 @@ export class RuntimeCompiler implements Compiler { const compiledTemplate = isHost ? this._compiledHostTemplateCache.get(compType) : this._compiledTemplateCache.get(compType); if (!compiledTemplate) { - if (isHost) { - throw new Error( - `Illegal state: Compiled view for component ${stringify(compType)} does not exist!`); - } else { - throw new Error( - `Component ${stringify(compType)} is not part of any NgModule or the module has not been imported into your module.`); - } + throw new Error( + `Illegal state: Compiled view for component ${stringify(compType)} does not exist!`); } return compiledTemplate; } @@ -241,6 +257,30 @@ export class RuntimeCompiler implements Compiler { return compiledTemplate; } + private _assertDirectiveWrapper(dirType: any): Type { + const dirWrapper = this._compiledDirectiveWrapperCache.get(dirType); + if (!dirWrapper) { + throw new Error( + `Illegal state: Directive wrapper for ${stringify(dirType)} has not been compiled!`); + } + return dirWrapper; + } + + private _compileDirectiveWrapper( + dirMeta: CompileDirectiveMetadata, moduleMeta: CompileNgModuleMetadata): void { + const compileResult = this._directiveWrapperCompiler.compile(dirMeta); + const statements = compileResult.statements; + let directiveWrapperClass: any; + if (!this._compilerConfig.useJit) { + directiveWrapperClass = interpretStatements(statements, compileResult.dirWrapperClassVar); + } else { + directiveWrapperClass = jitStatements( + `/${moduleMeta.type.name}/${dirMeta.type.name}/wrapper.ngfactory.js`, statements, + compileResult.dirWrapperClassVar); + } + this._compiledDirectiveWrapperCache.set(dirMeta.type.reference, directiveWrapperClass); + } + private _compileTemplate(template: CompiledTemplate) { if (template.isCompiled) { return; @@ -275,6 +315,9 @@ export class RuntimeCompiler implements Compiler { depTemplate = this._assertComponentLoaded(cfd.comp.reference, true); cfd.placeholder.reference = depTemplate.proxyComponentFactory; cfd.placeholder.name = `compFactory_${cfd.comp.name}`; + } else if (dep instanceof DirectiveWrapperDependency) { + let dwd = dep; + dwd.placeholder.reference = this._assertDirectiveWrapper(dwd.dir.reference); } }); const statements = @@ -286,8 +329,8 @@ export class RuntimeCompiler implements Compiler { factory = interpretStatements(statements, compileResult.viewFactoryVar); } else { factory = jitStatements( - `${template.compType.name}${template.isHost?'_Host':''}.ngfactory.js`, statements, - compileResult.viewFactoryVar); + `/${template.ngModule.type.name}/${template.compType.name}/${template.isHost?'host':'component'}.ngfactory.js`, + statements, compileResult.viewFactoryVar); } template.compiled(factory); } @@ -310,7 +353,7 @@ export class RuntimeCompiler implements Compiler { if (!this._compilerConfig.useJit) { return interpretStatements(result.statements, result.stylesVar); } else { - return jitStatements(`${result.meta.moduleUrl}.css.js`, result.statements, result.stylesVar); + return jitStatements(`/${result.meta.moduleUrl}.css.js`, result.statements, result.stylesVar); } } } @@ -325,13 +368,17 @@ class CompiledTemplate { isCompiledWithDeps = false; viewComponentTypes: Type[] = []; viewDirectives: CompileDirectiveMetadata[] = []; + viewPipes: CompilePipeMetadata[]; + schemas: SchemaMetadata[]; constructor( public isHost: boolean, selector: string, public compType: CompileIdentifierMetadata, - viewDirectivesAndComponents: CompileDirectiveMetadata[], - public viewPipes: CompilePipeMetadata[], public schemas: SchemaMetadata[], + public ngModule: CompileNgModuleMetadata, + viewDirectiveAndComponents: CompileDirectiveMetadata[], _normalizeResult: SyncAsyncResult) { - viewDirectivesAndComponents.forEach((dirMeta) => { + this.viewPipes = ngModule.transitiveModule.pipes; + this.schemas = ngModule.schemas; + viewDirectiveAndComponents.forEach((dirMeta) => { if (dirMeta.isComponent) { this.viewComponentTypes.push(dirMeta.type.reference); } else { diff --git a/modules/@angular/compiler/src/template_parser/template_parser.ts b/modules/@angular/compiler/src/template_parser/template_parser.ts index 3959ae209d..ff5690dbbf 100644 --- a/modules/@angular/compiler/src/template_parser/template_parser.ts +++ b/modules/@angular/compiler/src/template_parser/template_parser.ts @@ -20,7 +20,7 @@ import {expandNodes} from '../ml_parser/icu_ast_expander'; import {InterpolationConfig} from '../ml_parser/interpolation_config'; import {mergeNsAndName, splitNsName} from '../ml_parser/tags'; import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util'; -import {Console, MAX_INTERPOLATION_VALUES} from '../private_import_core'; +import {Console, view_utils} from '../private_import_core'; import {ProviderElementContext, ProviderViewContext} from '../provider_analyzer'; import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {CssSelector, SelectorMatcher} from '../selector'; @@ -246,8 +246,9 @@ class TemplateParseVisitor implements html.Visitor { if (ast) this._reportParserErrors(ast.errors, sourceSpan); this._checkPipes(ast, sourceSpan); if (isPresent(ast) && - (ast.ast).expressions.length > MAX_INTERPOLATION_VALUES) { - throw new Error(`Only support at most ${MAX_INTERPOLATION_VALUES} interpolation values!`); + (ast.ast).expressions.length > view_utils.MAX_INTERPOLATION_VALUES) { + throw new Error( + `Only support at most ${view_utils.MAX_INTERPOLATION_VALUES} interpolation values!`); } return ast; } catch (e) { diff --git a/modules/@angular/compiler/src/view_compiler/compile_element.ts b/modules/@angular/compiler/src/view_compiler/compile_element.ts index 6c85dfbd45..2dd6b0f31a 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_element.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_element.ts @@ -8,6 +8,7 @@ import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata'; +import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler'; import {ListWrapper, MapWrapper} from '../facade/collection'; import {isPresent} from '../facade/lang'; import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers'; @@ -19,7 +20,8 @@ import {createDiTokenExpression} from '../util'; import {CompileMethod} from './compile_method'; import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query'; import {CompileView} from './compile_view'; -import {InjectMethodVars} from './constants'; +import {InjectMethodVars, ViewProperties} from './constants'; +import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewFactoryDependency} from './deps'; import {getPropertyInView, injectFromViewParentInjector} from './util'; export class CompileNode { @@ -34,7 +36,7 @@ export class CompileNode { export class CompileElement extends CompileNode { static createNull(): CompileElement { - return new CompileElement(null, null, null, null, null, null, [], [], false, false, []); + return new CompileElement(null, null, null, null, null, null, [], [], false, false, [], []); } private _compViewExpr: o.Expression = null; @@ -42,6 +44,7 @@ export class CompileElement extends CompileNode { public elementRef: o.Expression; public injector: o.Expression; public instances = new Map(); + public directiveWrapperInstance = new Map(); private _resolvedProviders: Map; private _queryCount = 0; @@ -57,7 +60,9 @@ export class CompileElement extends CompileNode { sourceAst: TemplateAst, public component: CompileDirectiveMetadata, private _directives: CompileDirectiveMetadata[], private _resolvedProvidersArray: ProviderAst[], public hasViewContainer: boolean, - public hasEmbeddedView: boolean, references: ReferenceAst[]) { + public hasEmbeddedView: boolean, references: ReferenceAst[], + private _targetDependencies: + Array) { super(parent, view, nodeIndex, renderNode, sourceAst); this.referenceTokens = {}; references.forEach(ref => this.referenceTokens[ref.name] = ref.value); @@ -72,6 +77,9 @@ export class CompileElement extends CompileNode { if (this.hasViewContainer || this.hasEmbeddedView || isPresent(this.component)) { this._createAppElement(); } + if (this.component) { + this._createComponentFactoryResolver(); + } } private _createAppElement() { @@ -92,7 +100,13 @@ export class CompileElement extends CompileNode { this.instances.set(resolveIdentifierToken(Identifiers.AppElement).reference, this.appElement); } - public createComponentFactoryResolver(entryComponents: CompileIdentifierMetadata[]) { + private _createComponentFactoryResolver() { + let entryComponents = + this.component.entryComponents.map((entryComponent: CompileIdentifierMetadata) => { + var id = new CompileIdentifierMetadata({name: entryComponent.name}); + this._targetDependencies.push(new ComponentFactoryDependency(entryComponent, id)); + return id; + }); if (!entryComponents || entryComponents.length === 0) { return; } @@ -155,6 +169,8 @@ export class CompileElement extends CompileNode { // create all the provider instances, some in the view constructor, // some as getters. We rely on the fact that they are already sorted topologically. MapWrapper.values(this._resolvedProviders).forEach((resolvedProvider) => { + const isDirectiveWrapper = resolvedProvider.providerType === ProviderAstType.Component || + resolvedProvider.providerType === ProviderAstType.Directive; var providerValueExpressions = resolvedProvider.providers.map((provider) => { if (isPresent(provider.useExisting)) { return this._getDependency( @@ -167,8 +183,17 @@ export class CompileElement extends CompileNode { } else if (isPresent(provider.useClass)) { var deps = provider.deps || provider.useClass.diDeps; var depsExpr = deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep)); - return o.importExpr(provider.useClass) - .instantiate(depsExpr, o.importType(provider.useClass)); + if (isDirectiveWrapper) { + const directiveWrapperIdentifier = new CompileIdentifierMetadata( + {name: DirectiveWrapperCompiler.dirWrapperClassName(provider.useClass)}); + this._targetDependencies.push( + new DirectiveWrapperDependency(provider.useClass, directiveWrapperIdentifier)); + return o.importExpr(directiveWrapperIdentifier) + .instantiate(depsExpr, o.importType(directiveWrapperIdentifier)); + } else { + return o.importExpr(provider.useClass) + .instantiate(depsExpr, o.importType(provider.useClass)); + } } else { return convertValueToOutputAst(provider.useValue); } @@ -177,7 +202,12 @@ export class CompileElement extends CompileNode { var instance = createProviderProperty( propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider, resolvedProvider.eager, this); - this.instances.set(resolvedProvider.token.reference, instance); + if (isDirectiveWrapper) { + this.directiveWrapperInstance.set(resolvedProvider.token.reference, instance); + this.instances.set(resolvedProvider.token.reference, instance.prop('context')); + } else { + this.instances.set(resolvedProvider.token.reference, instance); + } }); for (var i = 0; i < this._directives.length; i++) { diff --git a/modules/@angular/compiler/src/view_compiler/deps.ts b/modules/@angular/compiler/src/view_compiler/deps.ts new file mode 100644 index 0000000000..568b6fac29 --- /dev/null +++ b/modules/@angular/compiler/src/view_compiler/deps.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {CompileIdentifierMetadata} from '../compile_metadata'; + +export class ViewFactoryDependency { + constructor( + public comp: CompileIdentifierMetadata, public placeholder: CompileIdentifierMetadata) {} +} + +export class ComponentFactoryDependency { + constructor( + public comp: CompileIdentifierMetadata, public placeholder: CompileIdentifierMetadata) {} +} + +export class DirectiveWrapperDependency { + constructor( + public dir: CompileIdentifierMetadata, public placeholder: CompileIdentifierMetadata) {} +} diff --git a/modules/@angular/compiler/src/view_compiler/lifecycle_binder.ts b/modules/@angular/compiler/src/view_compiler/lifecycle_binder.ts index e926047250..af364f029b 100644 --- a/modules/@angular/compiler/src/view_compiler/lifecycle_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/lifecycle_binder.ts @@ -20,27 +20,6 @@ import {DetectChangesVars} from './constants'; var STATE_IS_NEVER_CHECKED = o.THIS_EXPR.prop('numberOfChecks').identical(new o.LiteralExpr(0)); var NOT_THROW_ON_CHANGES = o.not(DetectChangesVars.throwOnChange); -export function bindDirectiveDetectChangesLifecycleCallbacks( - directiveAst: DirectiveAst, directiveInstance: o.Expression, compileElement: CompileElement) { - var view = compileElement.view; - var detectChangesInInputsMethod = view.detectChangesInInputsMethod; - var lifecycleHooks = directiveAst.directive.type.lifecycleHooks; - if (lifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1 && directiveAst.inputs.length > 0) { - detectChangesInInputsMethod.addStmt(new o.IfStmt( - DetectChangesVars.changes.notIdentical(o.NULL_EXPR), - [directiveInstance.callMethod('ngOnChanges', [DetectChangesVars.changes]).toStmt()])); - } - if (lifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1) { - detectChangesInInputsMethod.addStmt(new o.IfStmt( - STATE_IS_NEVER_CHECKED.and(NOT_THROW_ON_CHANGES), - [directiveInstance.callMethod('ngOnInit', []).toStmt()])); - } - if (lifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1) { - detectChangesInInputsMethod.addStmt(new o.IfStmt( - NOT_THROW_ON_CHANGES, [directiveInstance.callMethod('ngDoCheck', []).toStmt()])); - } -} - export function bindDirectiveAfterContentLifecycleCallbacks( directiveMeta: CompileDirectiveMetadata, directiveInstance: o.Expression, compileElement: CompileElement) { diff --git a/modules/@angular/compiler/src/view_compiler/property_binder.ts b/modules/@angular/compiler/src/view_compiler/property_binder.ts index 8533baf79f..7863a71ef3 100644 --- a/modules/@angular/compiler/src/view_compiler/property_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/property_binder.ts @@ -32,15 +32,18 @@ function createCurrValueExpr(exprIndex: number): o.ReadVarExpr { return o.variable(`currVal_${exprIndex}`); // fix syntax highlighting: ` } -function bind( - view: CompileView, currValExpr: o.ReadVarExpr, fieldExpr: o.ReadPropExpr, - parsedExpression: cdAst.AST, context: o.Expression, actions: o.Statement[], - method: CompileMethod, bindingIndex: number) { +class EvalResult { + constructor(public forceUpdate: o.Expression) {} +} + +function evalCdAst( + view: CompileView, currValExpr: o.ReadVarExpr, parsedExpression: cdAst.AST, + context: o.Expression, method: CompileMethod, bindingIndex: number): EvalResult { var checkExpression = convertCdExpressionToIr( view, context, parsedExpression, DetectChangesVars.valUnwrapper, bindingIndex); if (!checkExpression.expression) { // e.g. an empty expression was given - return; + return null; } if (checkExpression.temporaryCount) { @@ -49,24 +52,39 @@ function bind( } } - // private is fine here as no child view will reference the cached value... - view.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private])); - view.createMethod.addStmt(o.THIS_EXPR.prop(fieldExpr.name) - .set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED))) - .toStmt()); - if (checkExpression.needsValueUnwrapper) { var initValueUnwrapperStmt = DetectChangesVars.valUnwrapper.callMethod('reset', []).toStmt(); method.addStmt(initValueUnwrapperStmt); } method.addStmt( currValExpr.set(checkExpression.expression).toDeclStmt(null, [o.StmtModifier.Final])); + if (checkExpression.needsValueUnwrapper) { + return new EvalResult(DetectChangesVars.valUnwrapper.prop('hasWrappedValue')); + } else { + return new EvalResult(null); + } +} + +function bind( + view: CompileView, currValExpr: o.ReadVarExpr, fieldExpr: o.ReadPropExpr, + parsedExpression: cdAst.AST, context: o.Expression, actions: o.Statement[], + method: CompileMethod, bindingIndex: number) { + const evalResult = evalCdAst(view, currValExpr, parsedExpression, context, method, bindingIndex); + if (!evalResult) { + return; + } + + // private is fine here as no child view will reference the cached value... + view.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private])); + view.createMethod.addStmt(o.THIS_EXPR.prop(fieldExpr.name) + .set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED))) + .toStmt()); var condition: o.Expression = o.importExpr(resolveIdentifier(Identifiers.checkBinding)).callFn([ DetectChangesVars.throwOnChange, fieldExpr, currValExpr ]); - if (checkExpression.needsValueUnwrapper) { - condition = DetectChangesVars.valUnwrapper.prop('hasWrappedValue').or(condition); + if (evalResult.forceUpdate) { + condition = evalResult.forceUpdate.or(condition); } method.addStmt(new o.IfStmt( condition, @@ -237,82 +255,48 @@ export function bindDirectiveHostProps( } export function bindDirectiveInputs( - directiveAst: DirectiveAst, directiveInstance: o.Expression, compileElement: CompileElement) { - if (directiveAst.inputs.length === 0) { - return; - } + directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression, + compileElement: CompileElement) { var view = compileElement.view; var detectChangesInInputsMethod = view.detectChangesInInputsMethod; detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst); - var lifecycleHooks = directiveAst.directive.type.lifecycleHooks; - var calcChangesMap = lifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1; - var isOnPushComp = directiveAst.directive.isComponent && - !isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection); - if (calcChangesMap) { - detectChangesInInputsMethod.addStmt(DetectChangesVars.changes.set(o.NULL_EXPR).toStmt()); - } - if (isOnPushComp) { - detectChangesInInputsMethod.addStmt(DetectChangesVars.changed.set(o.literal(false)).toStmt()); - } directiveAst.inputs.forEach((input) => { var bindingIndex = view.bindings.length; view.bindings.push(new CompileBinding(compileElement, input)); detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input); - var fieldExpr = createBindFieldExpr(bindingIndex); var currValExpr = createCurrValueExpr(bindingIndex); - var statements: o.Statement[] = - [directiveInstance.prop(input.directiveName).set(currValExpr).toStmt()]; - if (calcChangesMap) { - statements.push(new o.IfStmt( - DetectChangesVars.changes.identical(o.NULL_EXPR), - [DetectChangesVars.changes - .set(o.literalMap( - [], new o.MapType(o.importType(resolveIdentifier(Identifiers.SimpleChange))))) - .toStmt()])); - statements.push(DetectChangesVars.changes.key(o.literal(input.directiveName)) - .set(o.importExpr(resolveIdentifier(Identifiers.SimpleChange)) - .instantiate([fieldExpr, currValExpr])) - .toStmt()); + const evalResult = evalCdAst( + view, currValExpr, input.value, view.componentContext, detectChangesInInputsMethod, + bindingIndex); + if (!evalResult) { + return; } - if (isOnPushComp) { - statements.push(DetectChangesVars.changed.set(o.literal(true)).toStmt()); - } - if (view.genConfig.logBindingUpdate) { - statements.push( - logBindingUpdateStmt(compileElement.renderNode, input.directiveName, currValExpr)); - } - bind( - view, currValExpr, fieldExpr, input.value, view.componentContext, statements, - detectChangesInInputsMethod, bindingIndex); + detectChangesInInputsMethod.addStmt(directiveWrapperInstance + .callMethod( + `check_${input.directiveName}`, + [ + currValExpr, DetectChangesVars.throwOnChange, + evalResult.forceUpdate || o.literal(false) + ]) + .toStmt()); }); - if (isOnPushComp) { - detectChangesInInputsMethod.addStmt(new o.IfStmt(DetectChangesVars.changed, [ - compileElement.appElement.prop('componentView').callMethod('markAsCheckOnce', []).toStmt() - ])); - } + var isOnPushComp = directiveAst.directive.isComponent && + !isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection); + let directiveDetectChangesExpr = directiveWrapperInstance.callMethod( + 'detectChangesInternal', + [o.THIS_EXPR, compileElement.renderNode, DetectChangesVars.throwOnChange]); + const directiveDetectChangesStmt = isOnPushComp ? + new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView') + .callMethod('markAsCheckOnce', []) + .toStmt()]) : + directiveDetectChangesExpr.toStmt(); + detectChangesInInputsMethod.addStmt(directiveDetectChangesStmt); } function logBindingUpdateStmt( renderNode: o.Expression, propName: string, value: o.Expression): o.Statement { - const tryStmt = - o.THIS_EXPR.prop('renderer') - .callMethod( - 'setBindingDebugInfo', - [ - renderNode, o.literal(`ng-reflect-${camelCaseToDashCase(propName)}`), - value.isBlank().conditional(o.NULL_EXPR, value.callMethod('toString', [])) - ]) - .toStmt(); - - const catchStmt = o.THIS_EXPR.prop('renderer') - .callMethod( - 'setBindingDebugInfo', - [ - renderNode, o.literal(`ng-reflect-${camelCaseToDashCase(propName)}`), - o.literal('[ERROR] Exception while trying to serialize the value') - ]) - .toStmt(); - - return new o.TryCatchStmt([tryStmt], [catchStmt]); + return o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfo)) + .callFn([o.THIS_EXPR.prop('renderer'), renderNode, o.literal(propName), value]) + .toStmt(); } diff --git a/modules/@angular/compiler/src/view_compiler/util.ts b/modules/@angular/compiler/src/view_compiler/util.ts index 2af8469f8c..42921b7385 100644 --- a/modules/@angular/compiler/src/view_compiler/util.ts +++ b/modules/@angular/compiler/src/view_compiler/util.ts @@ -7,7 +7,7 @@ */ -import {CompileDirectiveMetadata, CompileTokenMetadata} from '../compile_metadata'; +import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata'; import {isPresent} from '../facade/lang'; import {Identifiers, resolveIdentifier} from '../identifiers'; import * as o from '../output/output_ast'; @@ -30,15 +30,28 @@ export function getPropertyInView( throw new Error( `Internal error: Could not calculate a property in a parent view: ${property}`); } - if (property instanceof o.ReadPropExpr) { - let readPropExpr: o.ReadPropExpr = property; + return property.visitExpression(new _ReplaceViewTransformer(viewProp, definedView), null); + } +} + +class _ReplaceViewTransformer extends o.ExpressionTransformer { + constructor(private _viewExpr: o.Expression, private _view: CompileView) { super(); } + private _isThis(expr: o.Expression): boolean { + return expr instanceof o.ReadVarExpr && expr.builtin === o.BuiltinVar.This; + } + + visitReadVarExpr(ast: o.ReadVarExpr, context: any): any { + return this._isThis(ast) ? this._viewExpr : ast; + } + visitReadPropExpr(ast: o.ReadPropExpr, context: any): any { + if (this._isThis(ast.receiver)) { // Note: Don't cast for members of the AppView base class... - if (definedView.fields.some((field) => field.name == readPropExpr.name) || - definedView.getters.some((field) => field.name == readPropExpr.name)) { - viewProp = viewProp.cast(definedView.classType); + if (this._view.fields.some((field) => field.name == ast.name) || + this._view.getters.some((field) => field.name == ast.name)) { + return this._viewExpr.cast(this._view.classType).prop(ast.name); } } - return o.replaceVarInExpression(o.THIS_EXPR.name, viewProp, property); + return super.visitReadPropExpr(ast, context); } } diff --git a/modules/@angular/compiler/src/view_compiler/view_binder.ts b/modules/@angular/compiler/src/view_compiler/view_binder.ts index 3d262f6b4f..9e65859331 100644 --- a/modules/@angular/compiler/src/view_compiler/view_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/view_binder.ts @@ -11,7 +11,7 @@ import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventA import {CompileElement} from './compile_element'; import {CompileView} from './compile_view'; import {CompileEventListener, bindDirectiveOutputs, bindRenderOutputs, collectEventListeners} from './event_binder'; -import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveDetectChangesLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder'; +import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder'; import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder'; export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void { @@ -48,8 +48,9 @@ class ViewBinderVisitor implements TemplateAstVisitor { bindRenderOutputs(eventListeners); ast.directives.forEach((directiveAst) => { var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); - bindDirectiveInputs(directiveAst, directiveInstance, compileElement); - bindDirectiveDetectChangesLifecycleCallbacks(directiveAst, directiveInstance, compileElement); + var directiveWrapperInstance = + compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference); + bindDirectiveInputs(directiveAst, directiveWrapperInstance, compileElement); bindDirectiveHostProps(directiveAst, directiveInstance, compileElement, eventListeners); bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners); @@ -76,8 +77,10 @@ class ViewBinderVisitor implements TemplateAstVisitor { var eventListeners = collectEventListeners(ast.outputs, ast.directives, compileElement); ast.directives.forEach((directiveAst) => { var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); - bindDirectiveInputs(directiveAst, directiveInstance, compileElement); - bindDirectiveDetectChangesLifecycleCallbacks(directiveAst, directiveInstance, compileElement); + var directiveWrapperInstance = + compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference); + bindDirectiveInputs(directiveAst, directiveWrapperInstance, compileElement); + bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners); bindDirectiveAfterContentLifecycleCallbacks( directiveAst.directive, directiveInstance, compileElement); diff --git a/modules/@angular/compiler/src/view_compiler/view_builder.ts b/modules/@angular/compiler/src/view_compiler/view_builder.ts index 432342ee0c..34e3dbcc35 100644 --- a/modules/@angular/compiler/src/view_compiler/view_builder.ts +++ b/modules/@angular/compiler/src/view_compiler/view_builder.ts @@ -20,6 +20,7 @@ import {createDiTokenExpression} from '../util'; import {CompileElement, CompileNode} from './compile_element'; import {CompileView} from './compile_view'; import {ChangeDetectorStatusEnum, DetectChangesVars, InjectMethodVars, ViewConstructorVars, ViewEncapsulationEnum, ViewProperties, ViewTypeEnum} from './constants'; +import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewFactoryDependency} from './deps'; import {createFlatArray, getViewFactoryName} from './util'; const IMPLICIT_TEMPLATE_VAR = '\$implicit'; @@ -30,20 +31,11 @@ const NG_CONTAINER_TAG = 'ng-container'; var parentRenderNodeVar = o.variable('parentRenderNode'); var rootSelectorVar = o.variable('rootSelector'); -export class ViewFactoryDependency { - constructor( - public comp: CompileIdentifierMetadata, public placeholder: CompileIdentifierMetadata) {} -} - -export class ComponentFactoryDependency { - constructor( - public comp: CompileIdentifierMetadata, public placeholder: CompileIdentifierMetadata) {} -} - - export function buildView( view: CompileView, template: TemplateAst[], - targetDependencies: Array): number { + targetDependencies: + Array): + number { var builderVisitor = new ViewBuilderVisitor(view, targetDependencies); templateVisitAll( builderVisitor, template, @@ -66,7 +58,8 @@ class ViewBuilderVisitor implements TemplateAstVisitor { constructor( public view: CompileView, - public targetDependencies: Array) {} + public targetDependencies: + Array) {} private _isRootNode(parent: CompileElement): boolean { return parent.view !== this.view; } @@ -204,7 +197,7 @@ class ViewBuilderVisitor implements TemplateAstVisitor { } var compileElement = new CompileElement( parent, this.view, nodeIndex, renderNode, ast, component, directives, ast.providers, - ast.hasViewContainer, false, ast.references); + ast.hasViewContainer, false, ast.references, this.targetDependencies); this.view.nodes.push(compileElement); var compViewExpr: o.ReadVarExpr = null; if (isPresent(component)) { @@ -212,13 +205,6 @@ class ViewBuilderVisitor implements TemplateAstVisitor { new CompileIdentifierMetadata({name: getViewFactoryName(component, 0)}); this.targetDependencies.push( new ViewFactoryDependency(component.type, nestedComponentIdentifier)); - let entryComponentIdentifiers = - component.entryComponents.map((entryComponent: CompileIdentifierMetadata) => { - var id = new CompileIdentifierMetadata({name: entryComponent.name}); - this.targetDependencies.push(new ComponentFactoryDependency(entryComponent, id)); - return id; - }); - compileElement.createComponentFactoryResolver(entryComponentIdentifiers); compViewExpr = o.variable(`compView_${nodeIndex}`); // fix highlighting: ` compileElement.setComponentView(compViewExpr); @@ -273,7 +259,7 @@ class ViewBuilderVisitor implements TemplateAstVisitor { var directives = ast.directives.map(directiveAst => directiveAst.directive); var compileElement = new CompileElement( parent, this.view, nodeIndex, renderNode, ast, null, directives, ast.providers, - ast.hasViewContainer, true, ast.references); + ast.hasViewContainer, true, ast.references, this.targetDependencies); this.view.nodes.push(compileElement); this.nestedViewCount++; diff --git a/modules/@angular/compiler/src/view_compiler/view_compiler.ts b/modules/@angular/compiler/src/view_compiler/view_compiler.ts index 7db0f3d5f7..60d06e0777 100644 --- a/modules/@angular/compiler/src/view_compiler/view_compiler.ts +++ b/modules/@angular/compiler/src/view_compiler/view_compiler.ts @@ -16,15 +16,17 @@ import {TemplateAst} from '../template_parser/template_ast'; import {CompileElement} from './compile_element'; import {CompileView} from './compile_view'; +import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewFactoryDependency} from './deps'; import {bindView} from './view_binder'; -import {ComponentFactoryDependency, ViewFactoryDependency, buildView, finishView} from './view_builder'; +import {buildView, finishView} from './view_builder'; -export {ComponentFactoryDependency, ViewFactoryDependency} from './view_builder'; +export {ComponentFactoryDependency, DirectiveWrapperDependency, ViewFactoryDependency} from './deps'; export class ViewCompileResult { constructor( public statements: o.Statement[], public viewFactoryVar: string, - public dependencies: Array) {} + public dependencies: + Array) {} } @Injectable() @@ -35,7 +37,8 @@ export class ViewCompiler { component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression, pipes: CompilePipeMetadata[], compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult { - const dependencies: Array = []; + const dependencies: + Array = []; const view = new CompileView( component, this._genConfig, pipes, styles, compiledAnimations, 0, CompileElement.createNull(), []); diff --git a/modules/@angular/compiler/test/view_compiler/util_spec.ts b/modules/@angular/compiler/test/view_compiler/util_spec.ts new file mode 100644 index 0000000000..86c7308c91 --- /dev/null +++ b/modules/@angular/compiler/test/view_compiler/util_spec.ts @@ -0,0 +1,65 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {CompileIdentifierMetadata} from '../../src/compile_metadata'; +import * as o from '../../src/output/output_ast'; +import {CompileView} from '../../src/view_compiler/compile_view'; +import {getPropertyInView} from '../../src/view_compiler/util'; + +export function main() { + describe('getPropertyInView', () => { + it('should return the expression if it is the same view', () => { + const expr = o.THIS_EXPR.prop('someProp'); + const callingView = createCompileView({className: 'view'}); + expect(getPropertyInView(expr, callingView, callingView)).toBe(expr); + }); + + it('should access an unknown property in a parent view', () => { + const expr = o.THIS_EXPR.prop('someProp'); + const definedView = createCompileView({className: 'parentView'}); + const callingView = createCompileView({className: 'childView', parent: definedView}); + expect(getPropertyInView(expr, callingView, definedView)) + .toEqual(o.THIS_EXPR.prop('parent').prop('someProp')); + }); + + it('should access a known property in a parent view with cast', () => { + const expr = o.THIS_EXPR.prop('someProp'); + const definedView = createCompileView({className: 'parentView', fields: ['someProp']}); + const callingView = createCompileView({className: 'childView', parent: definedView}); + expect(getPropertyInView(expr, callingView, definedView)) + .toEqual(o.THIS_EXPR.prop('parent').cast(definedView.classType).prop('someProp')); + }); + + it('should access a known property in a parent view with cast also for property chain expressions', + () => { + const expr = o.THIS_EXPR.prop('someProp').prop('context'); + const definedView = createCompileView({className: 'parentView', fields: ['someProp']}); + const callingView = createCompileView({className: 'childView', parent: definedView}); + expect(getPropertyInView(expr, callingView, definedView)) + .toEqual(o.THIS_EXPR.prop('parent') + .cast(definedView.classType) + .prop('someProp') + .prop('context')); + }); + }); +} + +function createCompileView(config: {className: string, parent?: CompileView, fields?: string[]}): + CompileView { + const declarationElement: any = config.parent ? {view: config.parent} : {view: null}; + const fields: o.ClassField[] = []; + if (config.fields) { + config.fields.forEach((fieldName) => { fields.push(new o.ClassField(fieldName)); }); + } + return { + classType: o.importType(new CompileIdentifierMetadata({name: config.className})), + fields: fields, + getters: [], + declarationElement: declarationElement + }; +} diff --git a/modules/@angular/core/src/core_private_export.ts b/modules/@angular/core/src/core_private_export.ts index 3d799708bd..820c921ebb 100644 --- a/modules/@angular/core/src/core_private_export.ts +++ b/modules/@angular/core/src/core_private_export.ts @@ -64,11 +64,6 @@ export var __core_private__: { _NgModuleInjector?: ng_module_factory.NgModuleInjector, registerModuleFactory: typeof ng_module_factory_loader.registerModuleFactory, ViewType: typeof view_type.ViewType, _ViewType?: view_type.ViewType, - MAX_INTERPOLATION_VALUES: typeof view_utils.MAX_INTERPOLATION_VALUES, - checkBinding: typeof view_utils.checkBinding, - flattenNestedViewRenderNodes: typeof view_utils.flattenNestedViewRenderNodes, - interpolate: typeof view_utils.interpolate, - ViewUtils: typeof view_utils.ViewUtils, _ViewUtils?: view_utils.ViewUtils, ViewMetadata: typeof metadata_view.ViewMetadata, _ViewMetadata?: metadata_view.ViewMetadata, DebugContext: typeof debug_context.DebugContext, _DebugContext?: debug_context.DebugContext, StaticNodeDebugInfo: typeof debug_context.StaticNodeDebugInfo, @@ -84,19 +79,6 @@ export var __core_private__: { makeDecorator: typeof decorators.makeDecorator, DebugDomRootRenderer: typeof debug.DebugDomRootRenderer, _DebugDomRootRenderer?: debug.DebugDomRootRenderer, - EMPTY_ARRAY: typeof view_utils.EMPTY_ARRAY, - EMPTY_MAP: typeof view_utils.EMPTY_MAP, - pureProxy1: typeof view_utils.pureProxy1, - pureProxy2: typeof view_utils.pureProxy2, - pureProxy3: typeof view_utils.pureProxy3, - pureProxy4: typeof view_utils.pureProxy4, - pureProxy5: typeof view_utils.pureProxy5, - pureProxy6: typeof view_utils.pureProxy6, - pureProxy7: typeof view_utils.pureProxy7, - pureProxy8: typeof view_utils.pureProxy8, - pureProxy9: typeof view_utils.pureProxy9, - pureProxy10: typeof view_utils.pureProxy10, - castByValue: typeof view_utils.castByValue, Console: typeof console.Console, _Console?: console.Console, reflector: typeof reflection.reflector, Reflector: typeof reflection.Reflector, _Reflector?: reflection.Reflector, @@ -121,6 +103,7 @@ export var __core_private__: { ComponentStillLoadingError: typeof ComponentStillLoadingError, isPromise: typeof isPromise, AnimationTransition: typeof AnimationTransition + view_utils: typeof view_utils, } = { isDefaultChangeDetectionStrategy: constants.isDefaultChangeDetectionStrategy, ChangeDetectorStatus: constants.ChangeDetectorStatus, @@ -135,11 +118,7 @@ export var __core_private__: { NgModuleInjector: ng_module_factory.NgModuleInjector, registerModuleFactory: ng_module_factory_loader.registerModuleFactory, ViewType: view_type.ViewType, - MAX_INTERPOLATION_VALUES: view_utils.MAX_INTERPOLATION_VALUES, - checkBinding: view_utils.checkBinding, - flattenNestedViewRenderNodes: view_utils.flattenNestedViewRenderNodes, - interpolate: view_utils.interpolate, - ViewUtils: view_utils.ViewUtils, + view_utils: view_utils, ViewMetadata: metadata_view.ViewMetadata, DebugContext: debug_context.DebugContext, StaticNodeDebugInfo: debug_context.StaticNodeDebugInfo, @@ -151,19 +130,6 @@ export var __core_private__: { ReflectionCapabilities: reflection_capabilities.ReflectionCapabilities, makeDecorator: decorators.makeDecorator, DebugDomRootRenderer: debug.DebugDomRootRenderer, - EMPTY_ARRAY: view_utils.EMPTY_ARRAY, - EMPTY_MAP: view_utils.EMPTY_MAP, - pureProxy1: view_utils.pureProxy1, - pureProxy2: view_utils.pureProxy2, - pureProxy3: view_utils.pureProxy3, - pureProxy4: view_utils.pureProxy4, - pureProxy5: view_utils.pureProxy5, - pureProxy6: view_utils.pureProxy6, - pureProxy7: view_utils.pureProxy7, - pureProxy8: view_utils.pureProxy8, - pureProxy9: view_utils.pureProxy9, - pureProxy10: view_utils.pureProxy10, - castByValue: view_utils.castByValue, Console: console.Console, reflector: reflection.reflector, Reflector: reflection.Reflector, diff --git a/modules/@angular/core/src/linker/view_utils.ts b/modules/@angular/core/src/linker/view_utils.ts index 8780f250e9..cfee424837 100644 --- a/modules/@angular/core/src/linker/view_utils.ts +++ b/modules/@angular/core/src/linker/view_utils.ts @@ -7,13 +7,14 @@ */ import {APP_ID} from '../application_tokens'; -import {devModeEqual} from '../change_detection/change_detection'; +import {SimpleChange, devModeEqual} from '../change_detection/change_detection'; import {UNINITIALIZED} from '../change_detection/change_detection_util'; import {Inject, Injectable} from '../di'; import {isPresent, looseIdentical} from '../facade/lang'; import {ViewEncapsulation} from '../metadata/view'; import {RenderComponentType, Renderer, RootRenderer} from '../render/api'; import {Sanitizer} from '../security'; + import {AppElement} from './element'; import {ExpressionChangedAfterItHasBeenCheckedError} from './errors'; @@ -352,3 +353,27 @@ export function pureProxy10( return result; }; } + +export function setBindingDebugInfoForChanges( + renderer: Renderer, el: any, changes: {[key: string]: SimpleChange}) { + Object.keys(changes).forEach((propName) => { + setBindingDebugInfo(renderer, el, propName, changes[propName].currentValue); + }); +} + +export function setBindingDebugInfo(renderer: Renderer, el: any, propName: string, value: any) { + try { + renderer.setBindingDebugInfo( + el, `ng-reflect-${camelCaseToDashCase(propName)}`, value ? value.toString() : null); + } catch (e) { + renderer.setBindingDebugInfo( + el, `ng-reflect-${camelCaseToDashCase(propName)}`, + '[ERROR] Exception while trying to serialize the value'); + } +} + +const CAMEL_CASE_REGEXP = /([A-Z])/g; + +function camelCaseToDashCase(input: string): string { + return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase()); +}