diff --git a/packages/core/src/change_detection/change_detector_ref.ts b/packages/core/src/change_detection/change_detector_ref.ts index 68a358ba323..46e27beff08 100644 --- a/packages/core/src/change_detection/change_detector_ref.ts +++ b/packages/core/src/change_detection/change_detector_ref.ts @@ -104,7 +104,7 @@ export abstract class ChangeDetectorRef { * Checks the change detector and its children, and throws if any changes are detected. * * Use in development mode to verify that running change detection doesn't introduce - * other changes. + * other changes. Calling it in production mode is a noop. */ abstract checkNoChanges(): void; diff --git a/packages/core/src/render3/instructions/advance.ts b/packages/core/src/render3/instructions/advance.ts index fd06bdfbb7a..8ff38fd1828 100644 --- a/packages/core/src/render3/instructions/advance.ts +++ b/packages/core/src/render3/instructions/advance.ts @@ -37,7 +37,8 @@ import {getLView, getSelectedIndex, getTView, isInCheckNoChangesMode, setSelecte */ export function ɵɵadvance(delta: number): void { ngDevMode && assertGreaterThan(delta, 0, 'Can only advance forward'); - selectIndexInternal(getTView(), getLView(), getSelectedIndex() + delta, isInCheckNoChangesMode()); + selectIndexInternal( + getTView(), getLView(), getSelectedIndex() + delta, !!ngDevMode && isInCheckNoChangesMode()); } export function selectIndexInternal( diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 02852c0f6e9..219a70ff683 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -360,7 +360,7 @@ export function refreshView( enterView(lView); // Check no changes mode is a dev only mode used to verify that bindings have not changed // since they were assigned. We do not want to execute lifecycle hooks in that mode. - const isInCheckNoChangesPass = isInCheckNoChangesMode(); + const isInCheckNoChangesPass = ngDevMode && isInCheckNoChangesMode(); try { resetPreOrderHookFlags(lView); @@ -481,10 +481,14 @@ export function refreshView( export function renderComponentOrTemplate( tView: TView, lView: LView, templateFn: ComponentTemplate<{}>|null, context: T) { const rendererFactory = lView[RENDERER_FACTORY]; - const normalExecutionPath = !isInCheckNoChangesMode(); + + // Check no changes mode is a dev only mode used to verify that bindings have not changed + // since they were assigned. We do not want to invoke renderer factory functions in that mode + // to avoid any possible side-effects. + const checkNoChangesMode = !!ngDevMode && isInCheckNoChangesMode(); const creationModeIsActive = isCreationMode(lView); try { - if (normalExecutionPath && !creationModeIsActive && rendererFactory.begin) { + if (!checkNoChangesMode && !creationModeIsActive && rendererFactory.begin) { rendererFactory.begin(); } if (creationModeIsActive) { @@ -492,7 +496,7 @@ export function renderComponentOrTemplate( } refreshView(tView, lView, templateFn, context); } finally { - if (normalExecutionPath && !creationModeIsActive && rendererFactory.end) { + if (!checkNoChangesMode && !creationModeIsActive && rendererFactory.end) { rendererFactory.end(); } } @@ -507,7 +511,7 @@ function executeTemplate( if (isUpdatePhase && lView.length > HEADER_OFFSET) { // When we're updating, inherently select 0 so we don't // have to generate that instruction for most update blocks. - selectIndexInternal(tView, lView, HEADER_OFFSET, isInCheckNoChangesMode()); + selectIndexInternal(tView, lView, HEADER_OFFSET, !!ngDevMode && isInCheckNoChangesMode()); } const preHookType = diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index c92e325370b..5e2ad1f0b8f 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -7,7 +7,7 @@ */ import {InjectFlags} from '../di/interface/injector'; -import {assertDefined, assertEqual, assertGreaterThanOrEqual, assertLessThan, assertNotEqual} from '../util/assert'; +import {assertDefined, assertEqual, assertGreaterThanOrEqual, assertLessThan, assertNotEqual, throwError} from '../util/assert'; import {assertLViewOrUndefined, assertTNodeForLView, assertTNodeForTView} from './assert'; import {DirectiveDef} from './interfaces/definition'; @@ -172,24 +172,23 @@ interface InstructionState { * ``` */ bindingsEnabled: boolean; - - /** - * In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error. - * - * Necessary to support ChangeDetectorRef.checkNoChanges(). - * - * checkNoChanges Runs only in devmode=true and verifies that no unintended changes exist in - * the change detector or its children. - */ - isInCheckNoChangesMode: boolean; } const instructionState: InstructionState = { lFrame: createLFrame(null), bindingsEnabled: true, - isInCheckNoChangesMode: false, }; +/** + * In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error. + * + * Necessary to support ChangeDetectorRef.checkNoChanges(). + * + * The `checkNoChanges` function is invoked only in ngDevMode=true and verifies that no unintended + * changes exist in the change detector or its children. + */ +let _isInCheckNoChangesMode = false; + /** * Returns true if the instruction state stack is empty. * @@ -336,12 +335,13 @@ export function getContextLView(): LView { } export function isInCheckNoChangesMode(): boolean { - // TODO(misko): remove this from the LView since it is ngDevMode=true mode only. - return instructionState.isInCheckNoChangesMode; + !ngDevMode && throwError('Must never be called in production mode'); + return _isInCheckNoChangesMode; } export function setIsInCheckNoChangesMode(mode: boolean): void { - instructionState.isInCheckNoChangesMode = mode; + !ngDevMode && throwError('Must never be called in production mode'); + _isInCheckNoChangesMode = mode; } // top level variables should not be exported for performance reasons (PERF_NOTES.md) diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts index d1f39334654..2379632cec5 100644 --- a/packages/core/src/render3/view_ref.ts +++ b/packages/core/src/render3/view_ref.ts @@ -281,7 +281,9 @@ export class ViewRef implements viewEngine_EmbeddedViewRef, viewEngine_Int * introduce other changes. */ checkNoChanges(): void { - checkNoChangesInternal(this._lView[TVIEW], this._lView, this.context); + if (ngDevMode) { + checkNoChangesInternal(this._lView[TVIEW], this._lView, this.context); + } } attachToViewContainerRef() { @@ -318,7 +320,9 @@ export class RootViewRef extends ViewRef { } override checkNoChanges(): void { - checkNoChangesInRootView(this._view); + if (ngDevMode) { + checkNoChangesInRootView(this._view); + } } override get context(): T { diff --git a/packages/core/test/bundling/animations/bundle.golden_symbols.json b/packages/core/test/bundling/animations/bundle.golden_symbols.json index ff5b11b69e6..d67b4753f2a 100644 --- a/packages/core/test/bundling/animations/bundle.golden_symbols.json +++ b/packages/core/test/bundling/animations/bundle.golden_symbols.json @@ -716,12 +716,6 @@ { "name": "detachMovedView" }, - { - "name": "detectChangesInRootView" - }, - { - "name": "detectChangesInternal" - }, { "name": "diPublicInInjector" }, @@ -998,9 +992,6 @@ { "name": "isFunction" }, - { - "name": "isInCheckNoChangesMode" - }, { "name": "isInlineTemplate" }, @@ -1286,9 +1277,6 @@ { "name": "setInputsFromAttrs" }, - { - "name": "setIsInCheckNoChangesMode" - }, { "name": "setSelectedIndex" }, @@ -1388,4 +1376,4 @@ { "name": "ɵɵproperty" } -] \ No newline at end of file +] diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index 2d113c8f10c..941d268b56e 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -242,9 +242,6 @@ { "name": "isCurrentTNodeParent" }, - { - "name": "isInCheckNoChangesMode" - }, { "name": "isInlineTemplate" }, diff --git a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json index bdca6549337..b2658c5fc56 100644 --- a/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_reactive/bundle.golden_symbols.json @@ -737,12 +737,6 @@ { "name": "detachView" }, - { - "name": "detectChangesInRootView" - }, - { - "name": "detectChangesInternal" - }, { "name": "diPublicInInjector" }, @@ -1109,9 +1103,6 @@ { "name": "isFunction" }, - { - "name": "isInCheckNoChangesMode" - }, { "name": "isInlineTemplate" }, @@ -1436,9 +1427,6 @@ { "name": "setInputsFromAttrs" }, - { - "name": "setIsInCheckNoChangesMode" - }, { "name": "setSelectedIndex" }, diff --git a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json index 91cdad24201..eb3e8ca10de 100644 --- a/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json +++ b/packages/core/test/bundling/forms_template_driven/bundle.golden_symbols.json @@ -716,12 +716,6 @@ { "name": "detachView" }, - { - "name": "detectChangesInRootView" - }, - { - "name": "detectChangesInternal" - }, { "name": "diPublicInInjector" }, @@ -1073,9 +1067,6 @@ { "name": "isFunction" }, - { - "name": "isInCheckNoChangesMode" - }, { "name": "isInlineTemplate" }, @@ -1418,9 +1409,6 @@ { "name": "setInputsFromAttrs" }, - { - "name": "setIsInCheckNoChangesMode" - }, { "name": "setSelectedIndex" }, @@ -1574,4 +1562,4 @@ { "name": "ɵɵtext" } -] \ No newline at end of file +] diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 3afdaf5e8d3..f2de64339a5 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -173,9 +173,6 @@ { "name": "invertObject" }, - { - "name": "isInCheckNoChangesMode" - }, { "name": "isProceduralRenderer" }, @@ -251,4 +248,4 @@ { "name": "ɵɵdefineComponent" } -] \ No newline at end of file +] diff --git a/packages/core/test/bundling/router/bundle.golden_symbols.json b/packages/core/test/bundling/router/bundle.golden_symbols.json index 3c330205733..b3ca24241d5 100644 --- a/packages/core/test/bundling/router/bundle.golden_symbols.json +++ b/packages/core/test/bundling/router/bundle.golden_symbols.json @@ -992,12 +992,6 @@ { "name": "detachView" }, - { - "name": "detectChangesInRootView" - }, - { - "name": "detectChangesInternal" - }, { "name": "diPublicInInjector" }, @@ -1415,9 +1409,6 @@ { "name": "isImmediateMatch" }, - { - "name": "isInCheckNoChangesMode" - }, { "name": "isInlineTemplate" }, @@ -1760,9 +1751,6 @@ { "name": "setInputsFromAttrs" }, - { - "name": "setIsInCheckNoChangesMode" - }, { "name": "setRouterState" }, @@ -1967,4 +1955,4 @@ { "name": "ɵɵtextInterpolate1" } -] \ No newline at end of file +] diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index f2904bb9b5e..7a1141e14fb 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -305,12 +305,6 @@ { "name": "detachView" }, - { - "name": "detectChangesInRootView" - }, - { - "name": "detectChangesInternal" - }, { "name": "diPublicInInjector" }, @@ -548,9 +542,6 @@ { "name": "isDirectiveHost" }, - { - "name": "isInCheckNoChangesMode" - }, { "name": "isInlineTemplate" }, @@ -746,9 +737,6 @@ { "name": "setInputsFromAttrs" }, - { - "name": "setIsInCheckNoChangesMode" - }, { "name": "setSelectedIndex" },