diff --git a/modules/@angular/compiler/src/compiler_util/binding_util.ts b/modules/@angular/compiler/src/compiler_util/binding_util.ts index 7a332bdec9..31a056d628 100644 --- a/modules/@angular/compiler/src/compiler_util/binding_util.ts +++ b/modules/@angular/compiler/src/compiler_util/binding_util.ts @@ -21,28 +21,14 @@ export function createCheckBindingField(builder: ClassBuilder): CheckBindingFiel const fieldExpr = createBindFieldExpr(bindingId); // private is fine here as no child view will reference the cached value... builder.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private])); - builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name) - .set(o.importExpr(createIdentifier(Identifiers.UNINITIALIZED))) - .toStmt()); + builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name).set(o.literal(undefined)).toStmt()); return new CheckBindingField(fieldExpr, bindingId); } -export function createCheckBindingStmt( - evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr, - throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] { - let condition: o.Expression = o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([ - throwOnChangeVar, fieldExpr, evalResult.currValExpr - ]); - if (evalResult.forceUpdate) { - condition = evalResult.forceUpdate.or(condition); - } - return [ - ...evalResult.stmts, new o.IfStmt(condition, actions.concat([ - o.THIS_EXPR.prop(fieldExpr.name).set(evalResult.currValExpr).toStmt() - ])) - ]; -} - function createBindFieldExpr(bindingId: string): o.ReadPropExpr { return o.THIS_EXPR.prop(`_expr_${bindingId}`); } + +export function isFirstViewCheck(view: o.Expression): o.Expression { + return o.not(view.prop('numberOfChecks')); +} \ No newline at end of file diff --git a/modules/@angular/compiler/src/compiler_util/render_util.ts b/modules/@angular/compiler/src/compiler_util/render_util.ts index ecdaf4c2f1..c352898cbc 100644 --- a/modules/@angular/compiler/src/compiler_util/render_util.ts +++ b/modules/@angular/compiler/src/compiler_util/render_util.ts @@ -13,69 +13,64 @@ import * as o from '../output/output_ast'; import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core'; import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast'; +import {isFirstViewCheck} from './binding_util'; +import {ConvertPropertyBindingResult} from './expression_converter'; import {createEnumExpression} from './identifier_util'; -export function writeToRenderer( - view: o.Expression, boundProp: BoundElementPropertyAst, renderElement: o.Expression, - renderValue: o.Expression, logBindingUpdate: boolean, +export function createCheckRenderBindingStmt( + view: o.Expression, renderElement: o.Expression, boundProp: BoundElementPropertyAst, + oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult, securityContextExpression?: o.Expression): o.Statement[] { - const updateStmts: o.Statement[] = []; - const renderer = view.prop('renderer'); - renderValue = sanitizedValue(view, boundProp, renderValue, securityContextExpression); + const checkStmts: o.Statement[] = [...evalResult.stmts]; + const securityContext = calcSecurityContext(boundProp, securityContextExpression); switch (boundProp.type) { case PropertyBindingType.Property: - if (logBindingUpdate) { - updateStmts.push( - o.importExpr(createIdentifier(Identifiers.setBindingDebugInfo)) - .callFn([renderer, renderElement, o.literal(boundProp.name), renderValue]) - .toStmt()); - } - updateStmts.push( - renderer - .callMethod( - 'setElementProperty', [renderElement, o.literal(boundProp.name), renderValue]) - .toStmt()); + checkStmts.push(o.importExpr(createIdentifier(Identifiers.checkRenderProperty)) + .callFn([ + view, renderElement, o.literal(boundProp.name), oldValue, + oldValue.set(evalResult.currValExpr), + evalResult.forceUpdate || o.literal(false), securityContext + ]) + .toStmt()); break; case PropertyBindingType.Attribute: - renderValue = - renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', [])); - updateStmts.push( - renderer - .callMethod( - 'setElementAttribute', [renderElement, o.literal(boundProp.name), renderValue]) - .toStmt()); + checkStmts.push(o.importExpr(createIdentifier(Identifiers.checkRenderAttribute)) + .callFn([ + view, renderElement, o.literal(boundProp.name), oldValue, + oldValue.set(evalResult.currValExpr), + evalResult.forceUpdate || o.literal(false), securityContext + ]) + .toStmt()); break; case PropertyBindingType.Class: - updateStmts.push( - renderer - .callMethod( - 'setElementClass', [renderElement, o.literal(boundProp.name), renderValue]) + checkStmts.push( + o.importExpr(createIdentifier(Identifiers.checkRenderClass)) + .callFn([ + view, renderElement, o.literal(boundProp.name), oldValue, + oldValue.set(evalResult.currValExpr), evalResult.forceUpdate || o.literal(false) + ]) .toStmt()); break; case PropertyBindingType.Style: - let strValue: o.Expression = renderValue.callMethod('toString', []); - if (isPresent(boundProp.unit)) { - strValue = strValue.plus(o.literal(boundProp.unit)); - } - - renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue); - updateStmts.push( - renderer - .callMethod( - 'setElementStyle', [renderElement, o.literal(boundProp.name), renderValue]) + checkStmts.push( + o.importExpr(createIdentifier(Identifiers.checkRenderStyle)) + .callFn([ + view, renderElement, o.literal(boundProp.name), o.literal(boundProp.unit), oldValue, + oldValue.set(evalResult.currValExpr), evalResult.forceUpdate || o.literal(false), + securityContext + ]) .toStmt()); break; case PropertyBindingType.Animation: throw new Error('Illegal state: Should not come here!'); } - return updateStmts; + return checkStmts; } -function sanitizedValue( - view: o.Expression, boundProp: BoundElementPropertyAst, renderValue: o.Expression, - securityContextExpression?: o.Expression): o.Expression { +function calcSecurityContext( + boundProp: BoundElementPropertyAst, securityContextExpression?: o.Expression): o.Expression { if (boundProp.securityContext === SecurityContext.NONE) { - return renderValue; // No sanitization needed. + return o.NULL_EXPR; // No sanitization needed. } if (!boundProp.needsRuntimeSecurityContext) { securityContextExpression = @@ -84,15 +79,13 @@ function sanitizedValue( if (!securityContextExpression) { throw new Error(`internal error, no SecurityContext given ${boundProp.name}`); } - const ctx = view.prop('viewUtils').prop('sanitizer'); - const args = [securityContextExpression, renderValue]; - return ctx.callMethod('sanitize', args); + return securityContextExpression; } -export function triggerAnimation( +export function createCheckAnimationBindingStmts( view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst, boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression, - renderValue: o.Expression, lastRenderValue: o.Expression) { + oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult) { const detachStmts: o.Statement[] = []; const updateStmts: o.Statement[] = []; @@ -104,22 +97,21 @@ export function triggerAnimation( // it's important to normalize the void value as `void` explicitly // so that the styles data can be obtained from the stringmap const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE); - const unitializedValue = o.importExpr(createIdentifier(Identifiers.UNINITIALIZED)); const animationTransitionVar = o.variable('animationTransition_' + animationName); updateStmts.push( animationTransitionVar .set(animationFnExpr.callFn([ - view, renderElement, - lastRenderValue.equals(unitializedValue).conditional(emptyStateValue, lastRenderValue), - renderValue.equals(unitializedValue).conditional(emptyStateValue, renderValue) + view, renderElement, isFirstViewCheck(view).conditional(emptyStateValue, oldValue), + evalResult.currValExpr ])) .toDeclStmt()); + updateStmts.push(oldValue.set(evalResult.currValExpr).toStmt()); - detachStmts.push( - animationTransitionVar - .set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue])) - .toDeclStmt()); + detachStmts.push(animationTransitionVar + .set(animationFnExpr.callFn( + [view, renderElement, evalResult.currValExpr, emptyStateValue])) + .toDeclStmt()); const registerStmts: o.Statement[] = []; const animationStartMethodExists = boundOutputs.find( @@ -151,5 +143,14 @@ export function triggerAnimation( updateStmts.push(...registerStmts); detachStmts.push(...registerStmts); - return {updateStmts, detachStmts}; + const checkUpdateStmts: o.Statement[] = [ + ...evalResult.stmts, + new o.IfStmt( + o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([ + view, oldValue, evalResult.currValExpr, evalResult.forceUpdate || o.literal(false) + ]), + updateStmts) + ]; + const checkDetachStmts: o.Statement[] = [...evalResult.stmts, ...detachStmts]; + return {checkUpdateStmts, checkDetachStmts}; } diff --git a/modules/@angular/compiler/src/directive_wrapper_compiler.ts b/modules/@angular/compiler/src/directive_wrapper_compiler.ts index c8bca611e7..48673a7a07 100644 --- a/modules/@angular/compiler/src/directive_wrapper_compiler.ts +++ b/modules/@angular/compiler/src/directive_wrapper_compiler.ts @@ -7,9 +7,9 @@ */ import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, dirWrapperClassName, identifierModuleUrl, identifierName} from './compile_metadata'; -import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util'; +import {createCheckBindingField, isFirstViewCheck} from './compiler_util/binding_util'; import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter'; -import {triggerAnimation, writeToRenderer} from './compiler_util/render_util'; +import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from './compiler_util/render_util'; import {CompilerConfig} from './config'; import {Parser} from './expression_parser/parser'; import {Identifiers, createIdentifier} from './identifiers'; @@ -32,8 +32,8 @@ const CHANGES_FIELD_NAME = '_changes'; const CHANGED_FIELD_NAME = '_changed'; const EVENT_HANDLER_FIELD_NAME = '_eventHandler'; +const CHANGE_VAR = o.variable('change'); 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 COMPONENT_VIEW_VAR = o.variable('componentView'); @@ -130,8 +130,9 @@ class DirectiveWrapperBuilder implements ClassBuilder { new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)), new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE, [o.StmtModifier.Private]), ]; - const ctorStmts: o.Statement[] = - [o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt()]; + const ctorStmts: o.Statement[] = [ + o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt(), + ]; if (this.genChanges) { fields.push(new o.ClassField( CHANGES_FIELD_NAME, new o.MapType(o.DYNAMIC_TYPE), [o.StmtModifier.Private])); @@ -180,14 +181,14 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) { if (builder.ngOnInit) { lifecycleStmts.push(new o.IfStmt( - VIEW_VAR.prop('numberOfChecks').identical(new o.LiteralExpr(0)), + isFirstViewCheck(VIEW_VAR), [o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnInit', []).toStmt()])); } if (builder.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.IfStmt(o.not(VIEW_VAR.prop('throwOnChange')), lifecycleStmts)); } stmts.push(new o.ReturnStatement(changedVar)); @@ -197,7 +198,6 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) { new o.FnParam( VIEW_VAR.name, o.importType(createIdentifier(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)); } @@ -207,24 +207,35 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) { const 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(), + field.expression.set(CURR_VALUE_VAR).toStmt() ]; + let methodBody: o.Statement[]; if (builder.genChanges) { - onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME) - .key(o.literal(input)) - .set(o.importExpr(createIdentifier(Identifiers.SimpleChange)) - .instantiate([field.expression, CURR_VALUE_VAR])) - .toStmt()); + onChangeStatements.push( + o.THIS_EXPR.prop(CHANGES_FIELD_NAME).key(o.literal(input)).set(CHANGE_VAR).toStmt()); + methodBody = [ + CHANGE_VAR + .set(o.importExpr(createIdentifier(Identifiers.checkBindingChange)).callFn([ + VIEW_VAR, field.expression, CURR_VALUE_VAR, FORCE_UPDATE_VAR + ])) + .toDeclStmt(), + new o.IfStmt(CHANGE_VAR, onChangeStatements) + ]; + } else { + methodBody = [new o.IfStmt( + o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([ + VIEW_VAR, field.expression, CURR_VALUE_VAR, FORCE_UPDATE_VAR + ]), + onChangeStatements)]; } - const methodBody: o.Statement[] = createCheckBindingStmt( - {currValExpr: CURR_VALUE_VAR, forceUpdate: FORCE_UPDATE_VAR, stmts: []}, field.expression, - THROW_ON_CHANGE_VAR, onChangeStatements); builder.methods.push(new o.ClassMethod( `check_${input}`, [ + new o.FnParam( + VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])), 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), + new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE) ], methodBody)); } @@ -240,7 +251,6 @@ function addCheckHostMethod( COMPONENT_VIEW_VAR.name, o.importType(createIdentifier(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), ]; hostProps.forEach((hostProp, hostPropIdx) => { const field = createCheckBindingField(builder); @@ -255,23 +265,18 @@ function addCheckHostMethod( methodParams.push(new o.FnParam( securityContextExpr.name, o.importType(createIdentifier(Identifiers.SecurityContext)))); } - let checkBindingStmts: o.Statement[]; if (hostProp.isAnimation) { - const {updateStmts, detachStmts} = triggerAnimation( + const {checkUpdateStmts, checkDetachStmts} = createCheckAnimationBindingStmts( VIEW_VAR, COMPONENT_VIEW_VAR, hostProp, hostEvents, o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME) .or(o.importExpr(createIdentifier(Identifiers.noop))), - RENDER_EL_VAR, evalResult.currValExpr, field.expression); - checkBindingStmts = updateStmts; - builder.detachStmts.push(...detachStmts); + RENDER_EL_VAR, field.expression, evalResult); + builder.detachStmts.push(...checkDetachStmts); + stmts.push(...checkUpdateStmts); } else { - checkBindingStmts = writeToRenderer( - VIEW_VAR, hostProp, RENDER_EL_VAR, evalResult.currValExpr, - builder.compilerConfig.logBindingUpdate, securityContextExpr); + stmts.push(...createCheckRenderBindingStmt( + VIEW_VAR, RENDER_EL_VAR, hostProp, field.expression, evalResult, securityContextExpr)); } - - stmts.push(...createCheckBindingStmt( - evalResult, field.expression, THROW_ON_CHANGE_VAR, checkBindingStmts)); }); builder.methods.push(new o.ClassMethod('checkHost', methodParams, stmts)); } @@ -381,20 +386,19 @@ export class DirectiveWrapperExpressions { return dirWrapper.prop(CONTEXT_FIELD_NAME); } - static ngDoCheck( - dirWrapper: o.Expression, view: o.Expression, renderElement: o.Expression, - throwOnChange: o.Expression): o.Expression { - return dirWrapper.callMethod('ngDoCheck', [view, renderElement, throwOnChange]); + static ngDoCheck(dirWrapper: o.Expression, view: o.Expression, renderElement: o.Expression, ): + o.Expression { + return dirWrapper.callMethod('ngDoCheck', [view, renderElement]); } static checkHost( hostProps: BoundElementPropertyAst[], dirWrapper: o.Expression, view: o.Expression, - componentView: o.Expression, renderElement: o.Expression, throwOnChange: o.Expression, + componentView: o.Expression, renderElement: o.Expression, runtimeSecurityContexts: o.Expression[]): o.Statement[] { if (hostProps.length) { return [dirWrapper .callMethod( - 'checkHost', [view, componentView, renderElement, throwOnChange].concat( - runtimeSecurityContexts)) + 'checkHost', + [view, componentView, renderElement].concat(runtimeSecurityContexts)) .toStmt()]; } else { return []; diff --git a/modules/@angular/compiler/src/identifiers.ts b/modules/@angular/compiler/src/identifiers.ts index 2d0d80cb5b..5287259ef5 100644 --- a/modules/@angular/compiler/src/identifiers.ts +++ b/modules/@angular/compiler/src/identifiers.ts @@ -10,7 +10,7 @@ import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionS import {StaticSymbol} from './aot/static_symbol'; import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata'; -import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, ComponentRef_, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewContainer, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core'; +import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, ComponentRef_, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, ValueUnwrapper, ViewContainer, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core'; const APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view'); const VIEW_UTILS_MODULE_URL = assetUrl('core', 'linker/view_utils'); @@ -161,8 +161,6 @@ export class Identifiers { }; static SimpleChange: IdentifierSpec = {name: 'SimpleChange', moduleUrl: CD_MODULE_URL, runtime: SimpleChange}; - static UNINITIALIZED: - IdentifierSpec = {name: 'UNINITIALIZED', moduleUrl: CD_MODULE_URL, runtime: UNINITIALIZED}; static ChangeDetectorStatus: IdentifierSpec = { name: 'ChangeDetectorStatus', moduleUrl: CD_MODULE_URL, @@ -173,6 +171,36 @@ export class Identifiers { moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.checkBinding }; + static checkBindingChange: IdentifierSpec = { + name: 'checkBindingChange', + moduleUrl: VIEW_UTILS_MODULE_URL, + runtime: view_utils.checkBindingChange + }; + static checkRenderText: IdentifierSpec = { + name: 'checkRenderText', + moduleUrl: VIEW_UTILS_MODULE_URL, + runtime: view_utils.checkRenderText + }; + static checkRenderProperty: IdentifierSpec = { + name: 'checkRenderProperty', + moduleUrl: VIEW_UTILS_MODULE_URL, + runtime: view_utils.checkRenderProperty + }; + static checkRenderAttribute: IdentifierSpec = { + name: 'checkRenderAttribute', + moduleUrl: VIEW_UTILS_MODULE_URL, + runtime: view_utils.checkRenderAttribute + }; + static checkRenderClass: IdentifierSpec = { + name: 'checkRenderClass', + moduleUrl: VIEW_UTILS_MODULE_URL, + runtime: view_utils.checkRenderClass + }; + static checkRenderStyle: IdentifierSpec = { + name: 'checkRenderStyle', + moduleUrl: VIEW_UTILS_MODULE_URL, + runtime: view_utils.checkRenderStyle + }; static devModeEqual: IdentifierSpec = {name: 'devModeEqual', moduleUrl: CD_MODULE_URL, runtime: devModeEqual}; static inlineInterpolate: IdentifierSpec = { diff --git a/modules/@angular/compiler/src/private_import_core.ts b/modules/@angular/compiler/src/private_import_core.ts index 0cc67b424c..0aee872445 100644 --- a/modules/@angular/compiler/src/private_import_core.ts +++ b/modules/@angular/compiler/src/private_import_core.ts @@ -32,7 +32,6 @@ 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; -export const UNINITIALIZED: typeof r.UNINITIALIZED = r.UNINITIALIZED; export const ValueUnwrapper: typeof r.ValueUnwrapper = r.ValueUnwrapper; export const TemplateRef_: typeof r.TemplateRef_ = r.TemplateRef_; export type RenderDebugInfo = typeof r._RenderDebugInfo; diff --git a/modules/@angular/compiler/src/view_compiler/compile_view.ts b/modules/@angular/compiler/src/view_compiler/compile_view.ts index e4b8bfd1c7..cc98aacd31 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_view.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_view.ts @@ -9,7 +9,6 @@ import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeSummary, tokenName, viewClassName} from '../compile_metadata'; import {EventHandlerVars, NameResolver} from '../compiler_util/expression_converter'; -import {createPureProxy} from '../compiler_util/identifier_util'; import {CompilerConfig} from '../config'; import {isPresent} from '../facade/lang'; import {Identifiers, createIdentifier} from '../identifiers'; diff --git a/modules/@angular/compiler/src/view_compiler/constants.ts b/modules/@angular/compiler/src/view_compiler/constants.ts index e6a3b02d93..78f4688b48 100644 --- a/modules/@angular/compiler/src/view_compiler/constants.ts +++ b/modules/@angular/compiler/src/view_compiler/constants.ts @@ -47,6 +47,7 @@ export class ViewConstructorVars { export class ViewProperties { static renderer = o.THIS_EXPR.prop('renderer'); static viewUtils = o.THIS_EXPR.prop('viewUtils'); + static throwOnChange = o.THIS_EXPR.prop('throwOnChange'); } export class InjectMethodVars { @@ -54,9 +55,3 @@ export class InjectMethodVars { static requestNodeIndex = o.variable('requestNodeIndex'); static notFoundResult = o.variable('notFoundResult'); } - -export class DetectChangesVars { - static throwOnChange = o.variable(`throwOnChange`); - static changes = o.variable(`changes`); - static changed = o.variable(`changed`); -} diff --git a/modules/@angular/compiler/src/view_compiler/lifecycle_binder.ts b/modules/@angular/compiler/src/view_compiler/lifecycle_binder.ts index bed932e11c..9fd8e2fb23 100644 --- a/modules/@angular/compiler/src/view_compiler/lifecycle_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/lifecycle_binder.ts @@ -7,6 +7,7 @@ */ import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata'; +import {isFirstViewCheck} from '../compiler_util/binding_util'; import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler'; import * as o from '../output/output_ast'; import {LifecycleHooks} from '../private_import_core'; @@ -14,10 +15,6 @@ import {DirectiveAst, ProviderAst, ProviderAstType} from '../template_parser/tem import {CompileElement} from './compile_element'; import {CompileView} from './compile_view'; -import {DetectChangesVars} from './constants'; - -const STATE_IS_NEVER_CHECKED = o.THIS_EXPR.prop('numberOfChecks').identical(new o.LiteralExpr(0)); -const NOT_THROW_ON_CHANGES = o.not(DetectChangesVars.throwOnChange); export function bindDirectiveAfterContentLifecycleCallbacks( directiveMeta: CompileDirectiveSummary, directiveInstance: o.Expression, @@ -29,7 +26,8 @@ export function bindDirectiveAfterContentLifecycleCallbacks( compileElement.nodeIndex, compileElement.sourceAst); if (lifecycleHooks.indexOf(LifecycleHooks.AfterContentInit) !== -1) { afterContentLifecycleCallbacksMethod.addStmt(new o.IfStmt( - STATE_IS_NEVER_CHECKED, [directiveInstance.callMethod('ngAfterContentInit', []).toStmt()])); + isFirstViewCheck(o.THIS_EXPR), + [directiveInstance.callMethod('ngAfterContentInit', []).toStmt()])); } if (lifecycleHooks.indexOf(LifecycleHooks.AfterContentChecked) !== -1) { afterContentLifecycleCallbacksMethod.addStmt( @@ -47,7 +45,8 @@ export function bindDirectiveAfterViewLifecycleCallbacks( compileElement.nodeIndex, compileElement.sourceAst); if (lifecycleHooks.indexOf(LifecycleHooks.AfterViewInit) !== -1) { afterViewLifecycleCallbacksMethod.addStmt(new o.IfStmt( - STATE_IS_NEVER_CHECKED, [directiveInstance.callMethod('ngAfterViewInit', []).toStmt()])); + isFirstViewCheck(o.THIS_EXPR), + [directiveInstance.callMethod('ngAfterViewInit', []).toStmt()])); } if (lifecycleHooks.indexOf(LifecycleHooks.AfterViewChecked) !== -1) { afterViewLifecycleCallbacksMethod.addStmt( diff --git a/modules/@angular/compiler/src/view_compiler/property_binder.ts b/modules/@angular/compiler/src/view_compiler/property_binder.ts index ea58067ce7..c67e41c699 100644 --- a/modules/@angular/compiler/src/view_compiler/property_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/property_binder.ts @@ -8,19 +8,19 @@ import {SecurityContext} from '@angular/core'; -import {createCheckBindingField, createCheckBindingStmt} from '../compiler_util/binding_util'; +import {createCheckBindingField} from '../compiler_util/binding_util'; import {ConvertPropertyBindingResult, convertPropertyBinding} from '../compiler_util/expression_converter'; import {createEnumExpression} from '../compiler_util/identifier_util'; -import {triggerAnimation, writeToRenderer} from '../compiler_util/render_util'; +import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from '../compiler_util/render_util'; import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler'; import {Identifiers, createIdentifier} from '../identifiers'; import * as o from '../output/output_ast'; import {isDefaultChangeDetectionStrategy} from '../private_import_core'; import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, PropertyBindingType} from '../template_parser/template_ast'; + import {CompileElement, CompileNode} from './compile_element'; import {CompileView} from './compile_view'; -import {DetectChangesVars} from './constants'; import {getHandleEventMethodName} from './util'; export function bindRenderText( @@ -33,11 +33,15 @@ export function bindRenderText( } view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileNode.nodeIndex, boundText); - view.detectChangesRenderPropertiesMethod.addStmts(createCheckBindingStmt( - evalResult, valueField.expression, DetectChangesVars.throwOnChange, - [o.THIS_EXPR.prop('renderer') - .callMethod('setText', [compileNode.renderNode, evalResult.currValExpr]) - .toStmt()])); + view.detectChangesRenderPropertiesMethod.addStmts(evalResult.stmts); + view.detectChangesRenderPropertiesMethod.addStmt( + o.importExpr(createIdentifier(Identifiers.checkRenderText)) + .callFn([ + o.THIS_EXPR, compileNode.renderNode, valueField.expression, + valueField.expression.set(evalResult.currValExpr), + evalResult.forceUpdate || o.literal(false) + ]) + .toStmt()); } export function bindRenderInputs( @@ -54,31 +58,27 @@ export function bindRenderInputs( if (!evalResult) { return; } - const checkBindingStmts: o.Statement[] = []; let compileMethod = view.detectChangesRenderPropertiesMethod; switch (boundProp.type) { case PropertyBindingType.Property: case PropertyBindingType.Attribute: case PropertyBindingType.Class: case PropertyBindingType.Style: - checkBindingStmts.push(...writeToRenderer( - o.THIS_EXPR, boundProp, renderNode, evalResult.currValExpr, - view.genConfig.logBindingUpdate)); + compileMethod.addStmts(createCheckRenderBindingStmt( + o.THIS_EXPR, renderNode, boundProp, bindingField.expression, evalResult)); break; case PropertyBindingType.Animation: compileMethod = view.animationBindingsMethod; - const {updateStmts, detachStmts} = triggerAnimation( + const {checkUpdateStmts, checkDetachStmts} = createCheckAnimationBindingStmts( o.THIS_EXPR, o.THIS_EXPR, boundProp, boundOutputs, (hasEvents ? o.THIS_EXPR.prop(getHandleEventMethodName(compileElement.nodeIndex)) : o.importExpr(createIdentifier(Identifiers.noop))) .callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR]), - compileElement.renderNode, evalResult.currValExpr, bindingField.expression); - checkBindingStmts.push(...updateStmts); - view.detachMethod.addStmts(detachStmts); + compileElement.renderNode, bindingField.expression, evalResult); + view.detachMethod.addStmts(checkDetachStmts); + compileMethod.addStmts(checkUpdateStmts); break; } - compileMethod.addStmts(createCheckBindingStmt( - evalResult, bindingField.expression, DetectChangesVars.throwOnChange, checkBindingStmts)); }); } @@ -108,7 +108,7 @@ export function bindDirectiveHostProps( DirectiveWrapperExpressions.checkHost( directiveAst.hostProperties, directiveWrapperInstance, o.THIS_EXPR, compileElement.compViewExpr || o.THIS_EXPR, compileElement.renderNode, - DetectChangesVars.throwOnChange, runtimeSecurityCtxExprs)); + runtimeSecurityCtxExprs)); } export function bindDirectiveInputs( @@ -132,17 +132,13 @@ export function bindDirectiveInputs( directiveWrapperInstance .callMethod( `check_${input.directiveName}`, - [ - evalResult.currValExpr, DetectChangesVars.throwOnChange, - evalResult.forceUpdate || o.literal(false) - ]) + [o.THIS_EXPR, evalResult.currValExpr, evalResult.forceUpdate || o.literal(false)]) .toStmt()); }); const isOnPushComp = directiveAst.directive.isComponent && !isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection); const directiveDetectChangesExpr = DirectiveWrapperExpressions.ngDoCheck( - directiveWrapperInstance, o.THIS_EXPR, compileElement.renderNode, - DetectChangesVars.throwOnChange); + directiveWrapperInstance, o.THIS_EXPR, compileElement.renderNode); const directiveDetectChangesStmt = isOnPushComp ? new o.IfStmt( directiveDetectChangesExpr, diff --git a/modules/@angular/compiler/src/view_compiler/view_builder.ts b/modules/@angular/compiler/src/view_compiler/view_builder.ts index 6082f90ea3..894bbab991 100644 --- a/modules/@angular/compiler/src/view_compiler/view_builder.ts +++ b/modules/@angular/compiler/src/view_compiler/view_builder.ts @@ -21,7 +21,7 @@ import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventA import {CompileElement, CompileNode} from './compile_element'; import {CompileView, CompileViewRootNode, CompileViewRootNodeType} from './compile_view'; -import {ChangeDetectorStatusEnum, DetectChangesVars, InjectMethodVars, ViewConstructorVars, ViewEncapsulationEnum, ViewProperties, ViewTypeEnum} from './constants'; +import {ChangeDetectorStatusEnum, InjectMethodVars, ViewConstructorVars, ViewEncapsulationEnum, ViewProperties, ViewTypeEnum} from './constants'; import {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDependency} from './deps'; const IMPLICIT_TEMPLATE_VAR = '\$implicit'; @@ -483,9 +483,7 @@ function createViewClass( ], addReturnValuefNotEmpty(view.injectorGetMethod.finish(), InjectMethodVars.notFoundResult), o.DYNAMIC_TYPE), - new o.ClassMethod( - 'detectChangesInternal', [new o.FnParam(DetectChangesVars.throwOnChange.name, o.BOOL_TYPE)], - generateDetectChangesMethod(view)), + new o.ClassMethod('detectChangesInternal', [], generateDetectChangesMethod(view)), new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()), new o.ClassMethod('destroyInternal', [], generateDestroyMethod(view)), new o.ClassMethod('detachInternal', [], view.detachMethod.finish()), @@ -569,36 +567,26 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] { stmts.push(...view.detectChangesInInputsMethod.finish()); view.viewContainers.forEach((viewContainer) => { stmts.push( - viewContainer.callMethod('detectChangesInNestedViews', [DetectChangesVars.throwOnChange]) + viewContainer.callMethod('detectChangesInNestedViews', [ViewProperties.throwOnChange]) .toStmt()); }); const afterContentStmts = view.updateContentQueriesMethod.finish().concat( view.afterContentLifecycleCallbacksMethod.finish()); if (afterContentStmts.length > 0) { - stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterContentStmts)); + stmts.push(new o.IfStmt(o.not(ViewProperties.throwOnChange), afterContentStmts)); } stmts.push(...view.detectChangesRenderPropertiesMethod.finish()); view.viewChildren.forEach((viewChild) => { stmts.push( - viewChild.callMethod('internalDetectChanges', [DetectChangesVars.throwOnChange]).toStmt()); + viewChild.callMethod('internalDetectChanges', [ViewProperties.throwOnChange]).toStmt()); }); const afterViewStmts = view.updateViewQueriesMethod.finish().concat(view.afterViewLifecycleCallbacksMethod.finish()); if (afterViewStmts.length > 0) { - stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterViewStmts)); + stmts.push(new o.IfStmt(o.not(ViewProperties.throwOnChange), afterViewStmts)); } - const varStmts: any[] = []; - const readVars = o.findReadVarNames(stmts); - if (readVars.has(DetectChangesVars.changed.name)) { - varStmts.push(DetectChangesVars.changed.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)); - } - if (readVars.has(DetectChangesVars.changes.name)) { - varStmts.push( - DetectChangesVars.changes.set(o.NULL_EXPR) - .toDeclStmt(new o.MapType(o.importType(createIdentifier(Identifiers.SimpleChange))))); - } - varStmts.push(...createSharedBindingVariablesIfNeeded(stmts)); + const varStmts = createSharedBindingVariablesIfNeeded(stmts); return varStmts.concat(stmts); } diff --git a/modules/@angular/core/src/change_detection/change_detection.ts b/modules/@angular/core/src/change_detection/change_detection.ts index 14f8c2de3e..506111d38c 100644 --- a/modules/@angular/core/src/change_detection/change_detection.ts +++ b/modules/@angular/core/src/change_detection/change_detection.ts @@ -12,7 +12,7 @@ import {IterableDifferFactory, IterableDiffers} from './differs/iterable_differs import {KeyValueDifferFactory, KeyValueDiffers} from './differs/keyvalue_differs'; export {SimpleChanges} from '../metadata/lifecycle_hooks'; -export {SimpleChange, UNINITIALIZED, ValueUnwrapper, WrappedValue, devModeEqual, looseIdentical} from './change_detection_util'; +export {SimpleChange, ValueUnwrapper, WrappedValue, devModeEqual, looseIdentical} from './change_detection_util'; export {ChangeDetectorRef} from './change_detector_ref'; export {ChangeDetectionStrategy, ChangeDetectorStatus, isDefaultChangeDetectionStrategy} from './constants'; export {CollectionChangeRecord, DefaultIterableDifferFactory} from './differs/default_iterable_differ'; diff --git a/modules/@angular/core/src/change_detection/change_detection_util.ts b/modules/@angular/core/src/change_detection/change_detection_util.ts index 6a3b4f2066..8196c1539c 100644 --- a/modules/@angular/core/src/change_detection/change_detection_util.ts +++ b/modules/@angular/core/src/change_detection/change_detection_util.ts @@ -11,10 +11,6 @@ import {isPrimitive, looseIdentical} from '../facade/lang'; export {looseIdentical} from '../facade/lang'; -export const UNINITIALIZED = { - toString: () => 'CD_INIT_VALUE' -}; - export function devModeEqual(a: any, b: any): boolean { if (isListLikeIterable(a) && isListLikeIterable(b)) { return areIterablesEqual(a, b, devModeEqual); @@ -75,10 +71,15 @@ export class ValueUnwrapper { * @stable */ export class SimpleChange { - constructor(public previousValue: any, public currentValue: any) {} + constructor( + public previousValue: any, public currentValue: any, _isFirstChange: boolean = false) { + // Store this in a non declared field + // to prevent a breaking change (users might have `implement`ed SimpleChange before) + (this)._firstChange = _isFirstChange; + } /** * Check whether the new value is the first value assigned. */ - isFirstChange(): boolean { return this.previousValue === UNINITIALIZED; } + isFirstChange(): boolean { return (this)._firstChange; } } diff --git a/modules/@angular/core/src/core_private_export.ts b/modules/@angular/core/src/core_private_export.ts index 59eed225fe..e88e11c975 100644 --- a/modules/@angular/core/src/core_private_export.ts +++ b/modules/@angular/core/src/core_private_export.ts @@ -72,7 +72,6 @@ export const __core_private__: { StaticNodeDebugInfo: typeof debug_context.StaticNodeDebugInfo, _StaticNodeDebugInfo?: debug_context.StaticNodeDebugInfo, devModeEqual: typeof change_detection_util.devModeEqual, - UNINITIALIZED: typeof change_detection_util.UNINITIALIZED, ValueUnwrapper: typeof change_detection_util.ValueUnwrapper, _ValueUnwrapper?: change_detection_util.ValueUnwrapper, RenderDebugInfo: typeof api.RenderDebugInfo, @@ -130,7 +129,6 @@ export const __core_private__: { DebugContext: debug_context.DebugContext, StaticNodeDebugInfo: debug_context.StaticNodeDebugInfo, devModeEqual: change_detection_util.devModeEqual, - UNINITIALIZED: change_detection_util.UNINITIALIZED, ValueUnwrapper: change_detection_util.ValueUnwrapper, RenderDebugInfo: api.RenderDebugInfo, TemplateRef_: template_ref.TemplateRef_, diff --git a/modules/@angular/core/src/linker/errors.ts b/modules/@angular/core/src/linker/errors.ts index 704bc1addf..27496fb6ca 100644 --- a/modules/@angular/core/src/linker/errors.ts +++ b/modules/@angular/core/src/linker/errors.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {UNINITIALIZED} from '../change_detection/change_detection_util'; import {BaseError, WrappedError} from '../facade/errors'; import {DebugContext} from './debug_context'; @@ -45,10 +44,10 @@ import {DebugContext} from './debug_context'; * @stable */ export class ExpressionChangedAfterItHasBeenCheckedError extends BaseError { - constructor(oldValue: any, currValue: any) { + constructor(oldValue: any, currValue: any, isFirstCheck: boolean) { let msg = `Expression has changed after it was checked. Previous value: '${oldValue}'. Current value: '${currValue}'.`; - if (oldValue === UNINITIALIZED) { + if (isFirstCheck) { msg += ` It seems like the view has been created after its parent and its children have been dirty checked.` + ` Has it been created in a change detection hook ?`; diff --git a/modules/@angular/core/src/linker/view.ts b/modules/@angular/core/src/linker/view.ts index cf2d677b47..024622632f 100644 --- a/modules/@angular/core/src/linker/view.ts +++ b/modules/@angular/core/src/linker/view.ts @@ -47,6 +47,7 @@ export abstract class AppView { appRef: ApplicationRef; numberOfChecks: number = 0; + throwOnChange: boolean = false; renderer: Renderer; @@ -326,7 +327,8 @@ export abstract class AppView { if (this.cdMode === ChangeDetectorStatus.Destroyed) { this.throwDestroyedError('detectChanges'); } - this.detectChangesInternal(throwOnChange); + this.throwOnChange = throwOnChange; + this.detectChangesInternal(); if (this.cdMode === ChangeDetectorStatus.CheckOnce) this.cdMode = ChangeDetectorStatus.Checked; this.numberOfChecks++; @@ -336,7 +338,7 @@ export abstract class AppView { /** * Overwritten by implementations */ - detectChangesInternal(throwOnChange: boolean): void {} + detectChangesInternal(): void {} markAsCheckOnce(): void { this.cdMode = ChangeDetectorStatus.CheckOnce; } diff --git a/modules/@angular/core/src/linker/view_utils.ts b/modules/@angular/core/src/linker/view_utils.ts index 6258704396..33c295cb84 100644 --- a/modules/@angular/core/src/linker/view_utils.ts +++ b/modules/@angular/core/src/linker/view_utils.ts @@ -8,12 +8,11 @@ import {AnimationQueue} from '../animation/animation_queue'; 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, RenderDebugInfo, Renderer, RootRenderer} from '../render/api'; -import {Sanitizer} from '../security'; +import {Sanitizer, SecurityContext} from '../security'; import {Type} from '../type'; import {VERSION} from '../version'; import {NgZone} from '../zone/ng_zone'; @@ -102,14 +101,77 @@ function _toStringWithNull(v: any): string { return v != null ? v.toString() : ''; } -export function checkBinding(throwOnChange: boolean, oldValue: any, newValue: any): boolean { - if (throwOnChange) { - if (!devModeEqual(oldValue, newValue)) { - throw new ExpressionChangedAfterItHasBeenCheckedError(oldValue, newValue); +export function checkBinding( + view: AppView, oldValue: any, newValue: any, forceUpdate: boolean): boolean { + const isFirstCheck = view.numberOfChecks === 0; + if (view.throwOnChange) { + if (isFirstCheck || !devModeEqual(oldValue, newValue)) { + throw new ExpressionChangedAfterItHasBeenCheckedError(oldValue, newValue, isFirstCheck); } return false; } else { - return !looseIdentical(oldValue, newValue); + return isFirstCheck || forceUpdate || !looseIdentical(oldValue, newValue); + } +} + +export function checkBindingChange( + view: AppView, oldValue: any, newValue: any, forceUpdate: boolean): SimpleChange { + if (checkBinding(view, oldValue, newValue, forceUpdate)) { + return new SimpleChange(oldValue, newValue, view.numberOfChecks === 0); + } +} + +export function checkRenderText( + view: AppView, renderElement: any, oldValue: any, newValue: any, forceUpdate: boolean) { + if (checkBinding(view, oldValue, newValue, forceUpdate)) { + view.renderer.setText(renderElement, newValue); + } +} + +export function checkRenderProperty( + view: AppView, renderElement: any, propName: string, oldValue: any, newValue: any, + forceUpdate: boolean, securityContext: SecurityContext) { + if (checkBinding(view, oldValue, newValue, forceUpdate)) { + let renderValue = + securityContext ? view.viewUtils.sanitizer.sanitize(securityContext, newValue) : newValue; + view.renderer.setElementProperty(renderElement, propName, renderValue); + } +} + +export function checkRenderAttribute( + view: AppView, renderElement: any, attrName: string, oldValue: any, newValue: any, + forceUpdate: boolean, securityContext: SecurityContext) { + if (checkBinding(view, oldValue, newValue, forceUpdate)) { + let renderValue = + securityContext ? view.viewUtils.sanitizer.sanitize(securityContext, newValue) : newValue; + renderValue = renderValue != null ? renderValue.toString() : null; + view.renderer.setElementAttribute(renderElement, attrName, renderValue); + } +} + +export function checkRenderClass( + view: AppView, renderElement: any, className: string, oldValue: any, newValue: any, + forceUpdate: boolean) { + if (checkBinding(view, oldValue, newValue, forceUpdate)) { + view.renderer.setElementClass(renderElement, className, newValue); + } +} + +export function checkRenderStyle( + view: AppView, renderElement: any, styleName: string, unit: string, oldValue: any, + newValue: any, forceUpdate: boolean, securityContext: SecurityContext) { + if (checkBinding(view, oldValue, newValue, forceUpdate)) { + let renderValue = + securityContext ? view.viewUtils.sanitizer.sanitize(securityContext, newValue) : newValue; + if (renderValue != null) { + renderValue = renderValue.toString(); + if (unit != null) { + renderValue = renderValue + unit; + } + } else { + renderValue = null; + } + view.renderer.setElementStyle(renderElement, styleName, renderValue); } } @@ -121,11 +183,12 @@ export const EMPTY_ARRAY: any[] = []; export const EMPTY_MAP = {}; export function pureProxy1(fn: (p0: P0) => R): (p0: P0) => R { + let numberOfChecks = 0; let result: R; - let v0: any = UNINITIALIZED; + let v0: any; return (p0) => { - if (!looseIdentical(v0, p0)) { + if (!numberOfChecks++ || !looseIdentical(v0, p0)) { v0 = p0; result = fn(p0); } @@ -134,12 +197,13 @@ export function pureProxy1(fn: (p0: P0) => R): (p0: P0) => R { } export function pureProxy2(fn: (p0: P0, p1: P1) => R): (p0: P0, p1: P1) => R { + let numberOfChecks = 0; let result: R; - let v0: any = UNINITIALIZED; - let v1: any = UNINITIALIZED; + let v0: any; + let v1: any; return (p0, p1) => { - if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1)) { + if (!numberOfChecks++ || !looseIdentical(v0, p0) || !looseIdentical(v1, p1)) { v0 = p0; v1 = p1; result = fn(p0, p1); @@ -150,13 +214,15 @@ export function pureProxy2(fn: (p0: P0, p1: P1) => R): (p0: P0, p1: P export function pureProxy3(fn: (p0: P0, p1: P1, p2: P2) => R): ( p0: P0, p1: P1, p2: P2) => R { + let numberOfChecks = 0; let result: R; - let v0: any = UNINITIALIZED; - let v1: any = UNINITIALIZED; - let v2: any = UNINITIALIZED; + let v0: any; + let v1: any; + let v2: any; return (p0, p1, p2) => { - if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2)) { + if (!numberOfChecks++ || !looseIdentical(v0, p0) || !looseIdentical(v1, p1) || + !looseIdentical(v2, p2)) { v0 = p0; v1 = p1; v2 = p2; @@ -168,12 +234,13 @@ export function pureProxy3(fn: (p0: P0, p1: P1, p2: P2) => R): ( export function pureProxy4(fn: (p0: P0, p1: P1, p2: P2, p3: P3) => R): ( p0: P0, p1: P1, p2: P2, p3: P3) => R { + let numberOfChecks = 0; let result: R; let v0: any, v1: any, v2: any, v3: any; - v0 = v1 = v2 = v3 = UNINITIALIZED; + v0 = v1 = v2 = v3; return (p0, p1, p2, p3) => { - if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) || - !looseIdentical(v3, p3)) { + if (!numberOfChecks++ || !looseIdentical(v0, p0) || !looseIdentical(v1, p1) || + !looseIdentical(v2, p2) || !looseIdentical(v3, p3)) { v0 = p0; v1 = p1; v2 = p2; @@ -187,12 +254,13 @@ export function pureProxy4(fn: (p0: P0, p1: P1, p2: P2, p3: P export function pureProxy5( fn: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4) => R): (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4) => R { + let numberOfChecks = 0; let result: R; let v0: any, v1: any, v2: any, v3: any, v4: any; - v0 = v1 = v2 = v3 = v4 = UNINITIALIZED; + v0 = v1 = v2 = v3 = v4; return (p0, p1, p2, p3, p4) => { - if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) || - !looseIdentical(v3, p3) || !looseIdentical(v4, p4)) { + if (!numberOfChecks++ || !looseIdentical(v0, p0) || !looseIdentical(v1, p1) || + !looseIdentical(v2, p2) || !looseIdentical(v3, p3) || !looseIdentical(v4, p4)) { v0 = p0; v1 = p1; v2 = p2; @@ -208,12 +276,14 @@ export function pureProxy5( export function pureProxy6( fn: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => R): (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => R { + let numberOfChecks = 0; let result: R; let v0: any, v1: any, v2: any, v3: any, v4: any, v5: any; - v0 = v1 = v2 = v3 = v4 = v5 = UNINITIALIZED; + v0 = v1 = v2 = v3 = v4 = v5; return (p0, p1, p2, p3, p4, p5) => { - if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) || - !looseIdentical(v3, p3) || !looseIdentical(v4, p4) || !looseIdentical(v5, p5)) { + if (!numberOfChecks++ || !looseIdentical(v0, p0) || !looseIdentical(v1, p1) || + !looseIdentical(v2, p2) || !looseIdentical(v3, p3) || !looseIdentical(v4, p4) || + !looseIdentical(v5, p5)) { v0 = p0; v1 = p1; v2 = p2; @@ -229,13 +299,14 @@ export function pureProxy6( export function pureProxy7( fn: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) => R): (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) => R { + let numberOfChecks = 0; let result: R; let v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any; - v0 = v1 = v2 = v3 = v4 = v5 = v6 = UNINITIALIZED; + v0 = v1 = v2 = v3 = v4 = v5 = v6; return (p0, p1, p2, p3, p4, p5, p6) => { - if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) || - !looseIdentical(v3, p3) || !looseIdentical(v4, p4) || !looseIdentical(v5, p5) || - !looseIdentical(v6, p6)) { + if (!numberOfChecks++ || !looseIdentical(v0, p0) || !looseIdentical(v1, p1) || + !looseIdentical(v2, p2) || !looseIdentical(v3, p3) || !looseIdentical(v4, p4) || + !looseIdentical(v5, p5) || !looseIdentical(v6, p6)) { v0 = p0; v1 = p1; v2 = p2; @@ -252,13 +323,14 @@ export function pureProxy7( export function pureProxy8( fn: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7) => R): (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7) => R { + let numberOfChecks = 0; let result: R; let v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any; - v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7 = UNINITIALIZED; + v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7; return (p0, p1, p2, p3, p4, p5, p6, p7) => { - if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) || - !looseIdentical(v3, p3) || !looseIdentical(v4, p4) || !looseIdentical(v5, p5) || - !looseIdentical(v6, p6) || !looseIdentical(v7, p7)) { + if (!numberOfChecks++ || !looseIdentical(v0, p0) || !looseIdentical(v1, p1) || + !looseIdentical(v2, p2) || !looseIdentical(v3, p3) || !looseIdentical(v4, p4) || + !looseIdentical(v5, p5) || !looseIdentical(v6, p6) || !looseIdentical(v7, p7)) { v0 = p0; v1 = p1; v2 = p2; @@ -276,13 +348,15 @@ export function pureProxy8( export function pureProxy9( fn: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8) => R): (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8) => R { + let numberOfChecks = 0; let result: R; let v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any; - v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = UNINITIALIZED; + v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8; return (p0, p1, p2, p3, p4, p5, p6, p7, p8) => { - if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) || - !looseIdentical(v3, p3) || !looseIdentical(v4, p4) || !looseIdentical(v5, p5) || - !looseIdentical(v6, p6) || !looseIdentical(v7, p7) || !looseIdentical(v8, p8)) { + if (!numberOfChecks++ || !looseIdentical(v0, p0) || !looseIdentical(v1, p1) || + !looseIdentical(v2, p2) || !looseIdentical(v3, p3) || !looseIdentical(v4, p4) || + !looseIdentical(v5, p5) || !looseIdentical(v6, p6) || !looseIdentical(v7, p7) || + !looseIdentical(v8, p8)) { v0 = p0; v1 = p1; v2 = p2; @@ -301,14 +375,15 @@ export function pureProxy9( export function pureProxy10( fn: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9) => R): (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9) => R { + let numberOfChecks = 0; let result: R; let v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any, v9: any; - v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = v9 = UNINITIALIZED; + v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = v9; return (p0, p1, p2, p3, p4, p5, p6, p7, p8, p9) => { - if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) || - !looseIdentical(v3, p3) || !looseIdentical(v4, p4) || !looseIdentical(v5, p5) || - !looseIdentical(v6, p6) || !looseIdentical(v7, p7) || !looseIdentical(v8, p8) || - !looseIdentical(v9, p9)) { + if (!numberOfChecks++ || !looseIdentical(v0, p0) || !looseIdentical(v1, p1) || + !looseIdentical(v2, p2) || !looseIdentical(v3, p3) || !looseIdentical(v4, p4) || + !looseIdentical(v5, p5) || !looseIdentical(v6, p6) || !looseIdentical(v7, p7) || + !looseIdentical(v8, p8) || !looseIdentical(v9, p9)) { v0 = p0; v1 = p1; v2 = p2; diff --git a/modules/@angular/forms/test/directives_spec.ts b/modules/@angular/forms/test/directives_spec.ts index ec007b728f..317f4d7bb7 100644 --- a/modules/@angular/forms/test/directives_spec.ts +++ b/modules/@angular/forms/test/directives_spec.ts @@ -286,7 +286,7 @@ export function main() { const formValidator = (c: any /** TODO #9100 */) => ({'custom': true}); const f = new FormGroupDirective([formValidator], []); f.form = formModel; - f.ngOnChanges({'form': new SimpleChange(null, null)}); + f.ngOnChanges({'form': new SimpleChange(null, null, false)}); expect(formModel.errors).toEqual({'custom': true}); }); @@ -294,7 +294,7 @@ export function main() { it('should set up an async validator', fakeAsync(() => { const f = new FormGroupDirective([], [asyncValidator('expected')]); f.form = formModel; - f.ngOnChanges({'form': new SimpleChange(null, null)}); + f.ngOnChanges({'form': new SimpleChange(null, null, false)}); tick(); @@ -514,7 +514,7 @@ export function main() { it('should reexport new control properties', () => { const newControl = new FormControl(null); controlDir.form = newControl; - controlDir.ngOnChanges({'form': new SimpleChange(control, newControl)}); + controlDir.ngOnChanges({'form': new SimpleChange(control, newControl, false)}); checkProperties(newControl); }); @@ -523,7 +523,7 @@ export function main() { expect(control.valid).toBe(true); // this will add the required validator and recalculate the validity - controlDir.ngOnChanges({'form': new SimpleChange(null, control)}); + controlDir.ngOnChanges({'form': new SimpleChange(null, control, false)}); expect(control.valid).toBe(false); }); @@ -596,39 +596,39 @@ export function main() { })); it('should mark as disabled properly', fakeAsync(() => { - ngModel.ngOnChanges({isDisabled: new SimpleChange('', undefined)}); + ngModel.ngOnChanges({isDisabled: new SimpleChange('', undefined, false)}); tick(); expect(ngModel.control.disabled).toEqual(false); - ngModel.ngOnChanges({isDisabled: new SimpleChange('', null)}); + ngModel.ngOnChanges({isDisabled: new SimpleChange('', null, false)}); tick(); expect(ngModel.control.disabled).toEqual(false); - ngModel.ngOnChanges({isDisabled: new SimpleChange('', false)}); + ngModel.ngOnChanges({isDisabled: new SimpleChange('', false, false)}); tick(); expect(ngModel.control.disabled).toEqual(false); - ngModel.ngOnChanges({isDisabled: new SimpleChange('', 'false')}); + ngModel.ngOnChanges({isDisabled: new SimpleChange('', 'false', false)}); tick(); expect(ngModel.control.disabled).toEqual(false); - ngModel.ngOnChanges({isDisabled: new SimpleChange('', 0)}); + ngModel.ngOnChanges({isDisabled: new SimpleChange('', 0, false)}); tick(); expect(ngModel.control.disabled).toEqual(false); - ngModel.ngOnChanges({isDisabled: new SimpleChange(null, '')}); + ngModel.ngOnChanges({isDisabled: new SimpleChange(null, '', false)}); tick(); expect(ngModel.control.disabled).toEqual(true); - ngModel.ngOnChanges({isDisabled: new SimpleChange(null, 'true')}); + ngModel.ngOnChanges({isDisabled: new SimpleChange(null, 'true', false)}); tick(); expect(ngModel.control.disabled).toEqual(true); - ngModel.ngOnChanges({isDisabled: new SimpleChange(null, true)}); + ngModel.ngOnChanges({isDisabled: new SimpleChange(null, true, false)}); tick(); expect(ngModel.control.disabled).toEqual(true); - ngModel.ngOnChanges({isDisabled: new SimpleChange(null, 'anything else')}); + ngModel.ngOnChanges({isDisabled: new SimpleChange(null, 'anything else', false)}); tick(); expect(ngModel.control.disabled).toEqual(true); diff --git a/modules/@angular/upgrade/src/aot/downgrade_component_adapter.ts b/modules/@angular/upgrade/src/aot/downgrade_component_adapter.ts index 9f73b81d7a..275a30b01d 100644 --- a/modules/@angular/upgrade/src/aot/downgrade_component_adapter.ts +++ b/modules/@angular/upgrade/src/aot/downgrade_component_adapter.ts @@ -62,8 +62,9 @@ export class DowngradeComponentAdapter { return (value: any /** TODO #9100 */) => { if (this.inputChanges !== null) { this.inputChangeCount++; - this.inputChanges[prop] = - new Ng1Change(value, prevValue === INITIAL_VALUE ? value : prevValue); + this.inputChanges[prop] = new SimpleChange( + value, prevValue === INITIAL_VALUE ? value : prevValue, + prevValue === INITIAL_VALUE); prevValue = value; } this.component[prop] = value; @@ -82,14 +83,14 @@ export class DowngradeComponentAdapter { } if (expr != null) { const watchFn = - ((prop: any /** TODO #9100 */) => - (value: any /** TODO #9100 */, prevValue: any /** TODO #9100 */) => { - if (this.inputChanges != null) { - this.inputChangeCount++; - this.inputChanges[prop] = new Ng1Change(prevValue, value); - } - this.component[prop] = value; - })(input.prop); + ((prop: any /** TODO #9100 */) => ( + value: any /** TODO #9100 */, prevValue: any /** TODO #9100 */) => { + if (this.inputChanges != null) { + this.inputChangeCount++; + this.inputChanges[prop] = new SimpleChange(prevValue, value, prevValue === value); + } + this.component[prop] = value; + })(input.prop); this.componentScope.$watch(expr, watchFn); } } @@ -172,9 +173,3 @@ export class DowngradeComponentAdapter { }); } } - -class Ng1Change implements SimpleChange { - constructor(public previousValue: any, public currentValue: any) {} - - isFirstChange(): boolean { return this.previousValue === this.currentValue; } -} diff --git a/modules/@angular/upgrade/src/downgrade_ng2_adapter.ts b/modules/@angular/upgrade/src/downgrade_ng2_adapter.ts index a991132f9e..bcf286f2e2 100644 --- a/modules/@angular/upgrade/src/downgrade_ng2_adapter.ts +++ b/modules/@angular/upgrade/src/downgrade_ng2_adapter.ts @@ -54,8 +54,9 @@ export class DowngradeNg2ComponentAdapter { return (value: any /** TODO #9100 */) => { if (this.inputChanges !== null) { this.inputChangeCount++; - this.inputChanges[prop] = - new Ng1Change(value, prevValue === INITIAL_VALUE ? value : prevValue); + this.inputChanges[prop] = new SimpleChange( + value, prevValue === INITIAL_VALUE ? value : prevValue, + prevValue === INITIAL_VALUE); prevValue = value; } this.component[prop] = value; @@ -73,14 +74,14 @@ export class DowngradeNg2ComponentAdapter { } if (expr != null) { const watchFn = - ((prop: any /** TODO #9100 */) => - (value: any /** TODO #9100 */, prevValue: any /** TODO #9100 */) => { - if (this.inputChanges != null) { - this.inputChangeCount++; - this.inputChanges[prop] = new Ng1Change(prevValue, value); - } - this.component[prop] = value; - })(input.prop); + ((prop: any /** TODO #9100 */) => ( + value: any /** TODO #9100 */, prevValue: any /** TODO #9100 */) => { + if (this.inputChanges != null) { + this.inputChangeCount++; + this.inputChanges[prop] = new SimpleChange(prevValue, value, prevValue === value); + } + this.component[prop] = value; + })(input.prop); this.componentScope.$watch(expr, watchFn); } } @@ -152,9 +153,3 @@ export class DowngradeNg2ComponentAdapter { }); } } - -class Ng1Change implements SimpleChange { - constructor(public previousValue: any, public currentValue: any) {} - - isFirstChange(): boolean { return this.previousValue === this.currentValue; } -} diff --git a/modules/benchmarks/src/tree/ng2/init.ts b/modules/benchmarks/src/tree/ng2/init.ts index 309db9dd6d..58c0269ce1 100644 --- a/modules/benchmarks/src/tree/ng2/init.ts +++ b/modules/benchmarks/src/tree/ng2/init.ts @@ -29,7 +29,7 @@ export function init(moduleRef: NgModuleRef) { } function detectChanges() { - for (var i = 0; i < 10; i++) { + for (let i = 0; i < 10; i++) { appRef.tick(); } detectChangesRuns += 10; diff --git a/modules/benchmarks/src/tree/ng2_ftl/tree_host.ngfactory.ts b/modules/benchmarks/src/tree/ng2_ftl/tree_host.ngfactory.ts index 638e4ee066..dca417d83d 100644 --- a/modules/benchmarks/src/tree/ng2_ftl/tree_host.ngfactory.ts +++ b/modules/benchmarks/src/tree/ng2_ftl/tree_host.ngfactory.ts @@ -41,8 +41,8 @@ class _View_TreeComponent_Host0 extends import1.AppView { this.init([].concat([this._el_0]), [this._el_0], []); return new import9.ComponentRef_(0, this, this._el_0, this._TreeComponent_0_4.context); } - detectChangesInternal(throwOnChange: boolean): void { - this._TreeComponent_0_4.detectChangesInternal(throwOnChange); + detectChangesInternal(): void { + this._TreeComponent_0_4.detectChangesInternal(this.throwOnChange); } destroyInternal(): void { this._TreeComponent_0_4.destroyInternal(); } injectorGetInternal(token: any, requestNodeIndex: number, notFoundResult: any): any { diff --git a/modules/benchmarks/src/tree/ng2_static_ftl/tree_root.ngfactory.ts b/modules/benchmarks/src/tree/ng2_static_ftl/tree_root.ngfactory.ts index e1833ddef7..7fc0d8d192 100644 --- a/modules/benchmarks/src/tree/ng2_static_ftl/tree_root.ngfactory.ts +++ b/modules/benchmarks/src/tree/ng2_static_ftl/tree_root.ngfactory.ts @@ -49,8 +49,8 @@ class _View_TreeRootComponent_Host0 extends import1.AppView { this.init([].concat([this._el_0]), [this._el_0], []); return new import9.ComponentRef_(0, this, this._el_0, this._TreeRootComponent_0_4); } - detectChangesInternal(throwOnChange: boolean): void { - this._TreeRootComponent_0_4_View.detectChangesInternal(throwOnChange); + detectChangesInternal(): void { + this._TreeRootComponent_0_4_View.detectChangesInternal(this.throwOnChange); } destroyInternal(): void { this._TreeRootComponent_0_4_View.destroyInternal(); } injectorGetInternal(token: any, requestNodeIndex: number, notFoundResult: any): any { @@ -106,13 +106,13 @@ class _View_TreeRootComponent0 extends import1.AppView { @@ -133,9 +133,9 @@ class _View_TreeRootComponent1 extends import1.AppView { return (null as any); } destroyInternal() { this._TreeComponent0_0_4View.destroyInternal(); } - detectChangesInternal(throwOnChange: boolean): void { + detectChangesInternal(): void { this._TreeComponent0_0_4View.updateData(this.parentView.context.data); - this._TreeComponent0_0_4View.detectChangesInternal(throwOnChange); + this._TreeComponent0_0_4View.detectChangesInternal(this.throwOnChange); } visitRootNodesInternal(cb: any, context: any) { cb(this._el_0, context); } } diff --git a/tools/public_api_guard/core/index.d.ts b/tools/public_api_guard/core/index.d.ts index cbe523bdb3..8cf62cabb2 100644 --- a/tools/public_api_guard/core/index.d.ts +++ b/tools/public_api_guard/core/index.d.ts @@ -843,7 +843,7 @@ export declare function setTestabilityGetter(getter: GetTestability): void; export declare class SimpleChange { currentValue: any; previousValue: any; - constructor(previousValue: any, currentValue: any); + constructor(previousValue: any, currentValue: any, _isFirstChange?: boolean); isFirstChange(): boolean; }