diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts
index 4e84daea32..fdb2291800 100644
--- a/packages/core/src/render3/instructions.ts
+++ b/packages/core/src/render3/instructions.ts
@@ -30,7 +30,7 @@ import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, Pro
import {PlayerFactory} from './interfaces/player';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {LQueries} from './interfaces/query';
-import {GlobalTargetResolver, ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
+import {GlobalTargetResolver, ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {SanitizerFn} from './interfaces/sanitization';
import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TAIL, TData, TVIEW, TView, T_HOST} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
@@ -41,7 +41,7 @@ import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticCo
import {BoundPlayerFactory} from './styling/player_factory';
import {ANIMATION_PROP_PREFIX, allocateDirectiveIntoContext, createEmptyStylingContext, forceClassesAsString, forceStylesAsString, getStylingContext, hasClassInput, hasStyleInput, hasStyling, isAnimationProp} from './styling/util';
import {NO_CHANGE} from './tokens';
-import {INTERPOLATION_DELIMITER, findComponentView, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isContentQueryHost, loadInternal, readElementValue, readPatchedLView, renderStringify} from './util';
+import {INTERPOLATION_DELIMITER, findComponentView, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readElementValue, readPatchedLView, renderStringify} from './util';
@@ -2729,15 +2729,16 @@ function wrapListener(
* @returns the root LView
*/
export function markViewDirty(lView: LView): LView|null {
- while (lView && !(lView[FLAGS] & LViewFlags.IsRoot)) {
+ while (lView) {
lView[FLAGS] |= LViewFlags.Dirty;
+ // Stop traversing up as soon as you find a root view that wasn't attached to any container
+ if (isRootView(lView) && lView[CONTAINER_INDEX] === -1) {
+ return lView;
+ }
+ // continue otherwise
lView = lView[PARENT] !;
}
- // Detached views do not have a PARENT and also aren't root views
- if (lView) {
- lView[FLAGS] |= LViewFlags.Dirty;
- }
- return lView;
+ return null;
}
/**
diff --git a/packages/core/src/render3/util.ts b/packages/core/src/render3/util.ts
index e07469bf69..4f7b2991be 100644
--- a/packages/core/src/render3/util.ts
+++ b/packages/core/src/render3/util.ts
@@ -16,7 +16,7 @@ import {NO_PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFl
import {TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {RComment, RElement, RText} from './interfaces/renderer';
import {StylingContext} from './interfaces/styling';
-import {CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, RootContext, TData, TVIEW, TView, T_HOST} from './interfaces/view';
+import {CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, RootContext, TData, TVIEW, T_HOST} from './interfaces/view';
diff --git a/packages/core/test/acceptance/change_detection_spec.ts b/packages/core/test/acceptance/change_detection_spec.ts
index 8978ca4b17..340a224321 100644
--- a/packages/core/test/acceptance/change_detection_spec.ts
+++ b/packages/core/test/acceptance/change_detection_spec.ts
@@ -7,7 +7,7 @@
*/
-import {ApplicationRef, Component, Directive, EmbeddedViewRef, TemplateRef, ViewContainerRef} from '@angular/core';
+import {ApplicationRef, ChangeDetectionStrategy, Component, ComponentFactoryResolver, ComponentRef, Directive, EmbeddedViewRef, NgModule, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -68,4 +68,63 @@ describe('change detection', () => {
});
+ describe('markForCheck', () => {
+
+ it('should mark OnPush ancestor of dynamically created component views as dirty', () => {
+
+ @Component({
+ selector: `test-cmpt`,
+ template: `{{counter}}|`,
+ changeDetection: ChangeDetectionStrategy.OnPush
+ })
+ class TestCmpt {
+ counter = 0;
+ @ViewChild('vc', {read: ViewContainerRef}) vcRef !: ViewContainerRef;
+
+ constructor(private _cfr: ComponentFactoryResolver) {}
+
+ createComponentView(cmptType: Type): ComponentRef {
+ const cf = this._cfr.resolveComponentFactory(cmptType);
+ return this.vcRef.createComponent(cf);
+ }
+ }
+
+ @Component({
+ selector: 'dynamic-cmpt',
+ template: `dynamic`,
+ changeDetection: ChangeDetectionStrategy.OnPush
+ })
+ class DynamicCmpt {
+ }
+
+ @NgModule({declarations: [DynamicCmpt], entryComponents: [DynamicCmpt]})
+ class DynamicModule {
+ }
+
+ TestBed.configureTestingModule({imports: [DynamicModule], declarations: [TestCmpt]});
+
+ const fixture = TestBed.createComponent(TestCmpt);
+
+ // initial CD to have query results
+ // NOTE: we call change detection without checkNoChanges to have clearer picture
+ fixture.detectChanges(false);
+ expect(fixture.nativeElement).toHaveText('0|');
+
+ // insert a dynamic component
+ const dynamicCmptRef = fixture.componentInstance.createComponentView(DynamicCmpt);
+ fixture.detectChanges(false);
+ expect(fixture.nativeElement).toHaveText('0|dynamic');
+
+ // update model in the OnPush component - should not update UI
+ fixture.componentInstance.counter = 1;
+ fixture.detectChanges(false);
+ expect(fixture.nativeElement).toHaveText('0|dynamic');
+
+ // now mark the dynamically inserted component as dirty
+ dynamicCmptRef.changeDetectorRef.markForCheck();
+ fixture.detectChanges(false);
+ expect(fixture.nativeElement).toHaveText('1|dynamic');
+ });
+ });
+
});
\ No newline at end of file