New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ComponentRef Change detector doesn't trigger detecting for view #36667
Comments
This is a confirmed bug that still happens in the Angular v9 with ivy enabled (the same bug is present in VE). Here is a simplified reproduce scenario: https://stackblitz.com/edit/angular-i8tus5?file=package.json What is going on here is that While it works as implemented (and as was implemented in VE), this behaviour is very confusing to the users and I would consider this to be broken from a user perspective. To actually understand why it doesn't work one needs to deeply understand inner workings of Angular (concept of the host view, OnPush etc.). I'm not sure what is the best long terms solution here but the fix won't be trivial. The good news is that work-arrounds exists - the simplest one would be to remove |
BTW, this issue will be very visible for people trying to test |
@pkozlowski-opensource thanks for your feedback! 🙂 Is it leads us to 'not to use onPush with dynamic components at all'? yeap, without |
I was researching a bug in our application for 5 days... Now i can confirm that ComponentRef.ChangeDetectorRef .detectChanges doesnt work, but ComponentRef.instance.cdr (injected CDR via construcotr) detectChanges works... |
Workarroud Is to use component's injector to get Real ChangeDetectorRef. ComponentRef.injector.get(ChangeDetectorRef) |
This problem is still happening with v10.0. Repro is here . This problem happens often on using CDK Portal and Overlay. Dynamic view creation is not a corner case. |
Close angular#39462, angular#36667 In `bootstrap` and `dynamic` components, there is a hostview to contain the component view, if the component is OnPush, trigger change detection on the hostview will not check on the OnPush rootComponent, so we need to mark child components for check. The cases are: case 1: AppComponent is OnPush ``` import { Component, NgModule, ViewContainerRef, ComponentFactoryResolver, Injector, ChangeDetectionStrategy } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @component({ selector: 'my-app', template: ` {{name}} `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { name = 'initial value'; ngOnInit() { setTimeout(() => { this.name = 'new value' }, 2000); } } @NgModule({ imports: [ BrowserModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ] }) export class AppModule { } ``` case 2: Dynamic component ``` @component({ selector: 'dynamic', template: `{{name}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic { name = 'initial name'; } @component({ selector: 'my-app', template: ` <b>Expected</b>: "initial name" changes to "changed name" after 2s<br> <b>Actual</b>:<br> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { constructor(private _vcRef: ViewContainerRef, private _cfr: ComponentFactoryResolver, private _injector: Injector) {} ngOnInit() { const cmptRef = this._cfr.resolveComponentFactory(Dynamic).create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = 'changed name'; console.log('changing name'); cmptRef.changeDetectorRef.detectChanges(); }, 2000); } } ``` So in this PR, for `RootViewRef.detectChanges`, we should always markViewDirty for root component in the hostview.
Close angular#39462, angular#36667 In `bootstrap` and `dynamic` components, there is a hostview to contain the component view, if the component is OnPush, trigger change detection on the hostview will not check on the OnPush rootComponent, so we need to mark child components for check. The cases are: case 1: AppComponent is OnPush ``` import { Component, NgModule, ViewContainerRef, ComponentFactoryResolver, Injector, ChangeDetectionStrategy } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @component({ selector: 'my-app', template: ` {{name}} `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { name = 'initial value'; ngOnInit() { setTimeout(() => { this.name = 'new value' }, 2000); } } @NgModule({ imports: [ BrowserModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ] }) export class AppModule { } ``` case 2: Dynamic component ``` @component({ selector: 'dynamic', template: `{{name}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic { name = 'initial name'; } @component({ selector: 'my-app', template: ` <b>Expected</b>: "initial name" changes to "changed name" after 2s<br> <b>Actual</b>:<br> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { constructor(private _vcRef: ViewContainerRef, private _cfr: ComponentFactoryResolver, private _injector: Injector) {} ngOnInit() { const cmptRef = this._cfr.resolveComponentFactory(Dynamic).create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = 'changed name'; console.log('changing name'); cmptRef.changeDetectorRef.detectChanges(); }, 2000); } } ``` So in this PR, for `RootViewRef.detectChanges`, we should always markViewDirty for root component in the hostview.
Close angular#39462, angular#36667 In `bootstrap` and `dynamic` components, there is a hostview to contain the component view, if the component is OnPush, trigger change detection on the hostview will not check on the OnPush rootComponent, so we need to mark child components for check. The cases are: case 1: AppComponent is OnPush ``` import { Component, NgModule, ViewContainerRef, ComponentFactoryResolver, Injector, ChangeDetectionStrategy } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @component({ selector: 'my-app', template: ` {{name}} `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { name = 'initial value'; ngOnInit() { setTimeout(() => { this.name = 'new value' }, 2000); } } @NgModule({ imports: [ BrowserModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ] }) export class AppModule { } ``` case 2: Dynamic component ``` @component({ selector: 'dynamic', template: `{{name}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic { name = 'initial name'; } @component({ selector: 'my-app', template: ` <b>Expected</b>: "initial name" changes to "changed name" after 2s<br> <b>Actual</b>:<br> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { constructor(private _vcRef: ViewContainerRef, private _cfr: ComponentFactoryResolver, private _injector: Injector) {} ngOnInit() { const cmptRef = this._cfr.resolveComponentFactory(Dynamic).create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = 'changed name'; console.log('changing name'); cmptRef.changeDetectorRef.detectChanges(); }, 2000); } } ``` So in this PR, for `RootViewRef.detectChanges`, we should always markViewDirty for root component in the hostview.
Close angular#39462, angular#36667 In `bootstrap` and `dynamic` components, there is a hostview to contain the component view, if the component is OnPush, trigger change detection on the hostview will not check on the OnPush rootComponent, so we need to mark child components for check. The cases are: case 1: AppComponent is OnPush ``` import { Component, NgModule, ViewContainerRef, ComponentFactoryResolver, Injector, ChangeDetectionStrategy } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @component({ selector: 'my-app', template: ` {{name}} `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { name = 'initial value'; ngOnInit() { setTimeout(() => { this.name = 'new value' }, 2000); } } @NgModule({ imports: [ BrowserModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ] }) export class AppModule { } ``` case 2: Dynamic component ``` @component({ selector: 'dynamic', template: `{{name}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic { name = 'initial name'; } @component({ selector: 'my-app', template: ` <b>Expected</b>: "initial name" changes to "changed name" after 2s<br> <b>Actual</b>:<br> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { constructor(private _vcRef: ViewContainerRef, private _cfr: ComponentFactoryResolver, private _injector: Injector) {} ngOnInit() { const cmptRef = this._cfr.resolveComponentFactory(Dynamic).create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = 'changed name'; console.log('changing name'); cmptRef.changeDetectorRef.detectChanges(); }, 2000); } } ``` So in this PR, for `RootViewRef.detectChanges`, we should always markViewDirty for root component in the hostview.
Close angular#39462, angular#36667 In `bootstrap` and `dynamic` components, there is a hostview to contain the component view, if the component is OnPush, trigger change detection on the hostview will not check on the OnPush rootComponent, so we need to mark child components for check. The cases are: case 1: AppComponent is OnPush ``` import { Component, NgModule, ViewContainerRef, ComponentFactoryResolver, Injector, ChangeDetectionStrategy } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @component({ selector: 'my-app', template: ` {{name}} `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { name = 'initial value'; ngOnInit() { setTimeout(() => { this.name = 'new value' }, 2000); } } @NgModule({ imports: [ BrowserModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ] }) export class AppModule { } ``` case 2: Dynamic component ``` @component({ selector: 'dynamic', template: `{{name}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic { name = 'initial name'; } @component({ selector: 'my-app', template: ` <b>Expected</b>: "initial name" changes to "changed name" after 2s<br> <b>Actual</b>:<br> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { constructor(private _vcRef: ViewContainerRef, private _cfr: ComponentFactoryResolver, private _injector: Injector) {} ngOnInit() { const cmptRef = this._cfr.resolveComponentFactory(Dynamic).create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = 'changed name'; console.log('changing name'); cmptRef.changeDetectorRef.detectChanges(); }, 2000); } } ``` So in this PR, for `RootViewRef.detectChanges`, we should always markViewDirty for root component in the hostview.
…orrectly Close angular#36667, angular#39524. `dynamic` and `bootstrap` components has a parent `host view` as the placeholder. And also all the hooks are registered to the tView of the `host view`, not the component view. Running change detection from the `host view` has several issues. 1. componentRef.changeDetectorRef.detectChanges()/markForCheck() will not update the component. ``` @component({ selector: 'dynamic', template: `{{name}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic { name = 'initial name'; } @component({ selector: 'my-app', template: ` <b>Expected</b>: "initial name" changes to "changed name" after 2s<br> <b>Actual</b>:<br> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { constructor(private _vcRef: ViewContainerRef, private _cfr: ComponentFactoryResolver, private _injector: Injector) {} ngOnInit() { const cmptRef = this._cfr.resolveComponentFactory(Dynamic).create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = 'changed name'; console.log('changing name'); // cmptRef.changeDetectorRef is the RootViewRef of the hostView cmptRef.changeDetectorRef.detectChanges(); }, 2000); } } ``` 2. The hooks (such as ngOnCheck) is executed, but the component is not updated. ``` import { Component, NgModule, ViewContainerRef, ComponentFactoryResolver, Injector, ChangeDetectionStrategy, DoCheck } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { timer } from "rxjs"; @component({ selector: "dynamic", template: ` {{ name }} `, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic implements DoCheck { name = "initial name"; ngDoCheck() { console.log("docheck dynamic"); } } @component({ selector: "my-app", template: ` <b>Expected</b>: "initial name" changes to "changed name" after 2s<br /> <b>Actual</b>:<br /> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { constructor( private _vcRef: ViewContainerRef, private _cfr: ComponentFactoryResolver, private _injector: Injector ) {} ngOnInit() { const cmptRef = this._cfr .resolveComponentFactory(Dynamic) .create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = "changed name"; console.log("changing name"); cmptRef.changeDetectorRef.detectChanges(); }, 2000); } } @NgModule({ imports: [BrowserModule], declarations: [AppComponent, Dynamic], entryComponents: [Dynamic], bootstrap: [AppComponent] }) export class AppModule {} ``` This PR update several points. 1. When create a dynamic component, we create a host view and a component view. We consider these 2 views a single unit, if the component is OnPush, we set the Dirty flag to the host view to make it a an OnPush View, and set the CheckAlways flag to the component.
…orrectly Close angular#36667, angular#39524. `dynamic` and `bootstrap` components has a parent `host view` as the placeholder. And also all the hooks are registered to the tView of the `host view`, not the component view. Running change detection from the `host view` has several issues. 1. componentRef.changeDetectorRef.detectChanges()/markForCheck() will not update the component. ``` @component({ selector: 'dynamic', template: `{{name}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic { name = 'initial name'; } @component({ selector: 'my-app', template: ` <b>Expected</b>: "initial name" changes to "changed name" after 2s<br> <b>Actual</b>:<br> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { constructor(private _vcRef: ViewContainerRef, private _cfr: ComponentFactoryResolver, private _injector: Injector) {} ngOnInit() { const cmptRef = this._cfr.resolveComponentFactory(Dynamic).create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = 'changed name'; console.log('changing name'); // cmptRef.changeDetectorRef is the RootViewRef of the hostView cmptRef.changeDetectorRef.detectChanges(); }, 2000); } } ``` 2. The hooks (such as ngOnCheck) is executed, but the component is not updated. ``` import { Component, NgModule, ViewContainerRef, ComponentFactoryResolver, Injector, ChangeDetectionStrategy, DoCheck } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { timer } from "rxjs"; @component({ selector: "dynamic", template: ` {{ name }} `, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic implements DoCheck { name = "initial name"; ngDoCheck() { console.log("docheck dynamic"); } } @component({ selector: "my-app", template: ` <b>Expected</b>: "initial name" changes to "changed name" after 2s<br /> <b>Actual</b>:<br /> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { constructor( private _vcRef: ViewContainerRef, private _cfr: ComponentFactoryResolver, private _injector: Injector ) {} ngOnInit() { const cmptRef = this._cfr .resolveComponentFactory(Dynamic) .create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = "changed name"; console.log("changing name"); cmptRef.changeDetectorRef.detectChanges(); }, 2000); } } @NgModule({ imports: [BrowserModule], declarations: [AppComponent, Dynamic], entryComponents: [Dynamic], bootstrap: [AppComponent] }) export class AppModule {} ``` This PR update several points. 1. When create a dynamic component, we create a host view and a component view. We consider these 2 views a single unit, if the component is OnPush, we set the Dirty flag to the host view to make it a an OnPush View, and set the CheckAlways flag to the component.
…orrectly Close angular#36667, angular#39524. `dynamic` and `bootstrap` components has a parent `host view` as the placeholder. And also all the hooks are registered to the tView of the `host view`, not the component view. Running change detection from the `host view` has several issues. 1. componentRef.changeDetectorRef.detectChanges()/markForCheck() will not update the component. ``` @component({ selector: 'dynamic', template: `{{name}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic { name = 'initial name'; } @component({ selector: 'my-app', template: ` <b>Expected</b>: "initial name" changes to "changed name" after 2s<br> <b>Actual</b>:<br> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { constructor(private _vcRef: ViewContainerRef, private _cfr: ComponentFactoryResolver, private _injector: Injector) {} ngOnInit() { const cmptRef = this._cfr.resolveComponentFactory(Dynamic).create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = 'changed name'; console.log('changing name'); // cmptRef.changeDetectorRef is the RootViewRef of the hostView cmptRef.changeDetectorRef.detectChanges(); }, 2000); } } ``` 2. The hooks (such as ngOnCheck) is executed, but the component is not updated. ``` import { Component, NgModule, ViewContainerRef, ComponentFactoryResolver, Injector, ChangeDetectionStrategy, DoCheck } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { timer } from "rxjs"; @component({ selector: "dynamic", template: ` {{ name }} `, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic implements DoCheck { name = "initial name"; ngDoCheck() { console.log("docheck dynamic"); } } @component({ selector: "my-app", template: ` <b>Expected</b>: "initial name" changes to "changed name" after 2s<br /> <b>Actual</b>:<br /> `, changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent { constructor( private _vcRef: ViewContainerRef, private _cfr: ComponentFactoryResolver, private _injector: Injector ) {} ngOnInit() { const cmptRef = this._cfr .resolveComponentFactory(Dynamic) .create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = "changed name"; console.log("changing name"); cmptRef.changeDetectorRef.detectChanges(); }, 2000); } } @NgModule({ imports: [BrowserModule], declarations: [AppComponent, Dynamic], entryComponents: [Dynamic], bootstrap: [AppComponent] }) export class AppModule {} ``` This PR update several points. 1. When create a dynamic component, we create a host view and a component view. We consider these 2 views a single unit, if the component is OnPush, we set the Dirty flag to the host view to make it a an OnPush View, and set the CheckAlways flag to the component.
…nd onPush not dirty component Close angular#36667 Calling `componentRef.detectChanges()` sometimes not work as expected. ``` @component({ selector: 'dynamic', template: `{{name}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic { name = 'initial name'; } const componetRef = cfr.createComponent(Dynamic); componentRef.changeDetectorRef // host view componentRef.injector.get(ChangeDetectorRef) // component view componentRef.instance.cdRef // componentView componentRef.instance.name = 'something new'; // componentRef.instance.cdRef.markForCheck(); componentRef.detectChanges(); // does not work without `markForCheck` componentRef.changeDetectorRef.detectChanges() // Users expect that Bar gets refreshed. componentRef.injector.get(ChangeDetectorRef).detectChanges() componentRef.instance.cdRef.detectChanges() // Example: https://stackblitz.com/edit/angular-drqzhw?file=src%2Fapp%2Fapp.module.ts ngOnInit() { const cmptRef = this._cfr .resolveComponentFactory(Dynamic) .create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = "changed name"; console.log("changing name"); cmptRef.changeDetectorRef.detectChanges(); // does not work }); setTimeout(() => { cmptRef.instance.name = "second changed name"; console.log("changing name"); cmptRef.injector.get(ChangeDetectorRef).detectChanges(); // works }, 2000); } ``` Since when the component is `boostrap` or `dynamic`, we created a hostView to host the component. And the `componentRef.changeDetectorRef` references to that hostView instead of the componentView itself. And when the component is `OnPush` and not dirty, calling `changeDetectorRef.detectChanges()` will bypass the component which is confusing. So this commit mark the root component dirty when calling `changeDetectorRef.detectChanges()` from the root hostView.
…nd onPush not dirty component Close angular#36667 Calling `componentRef.detectChanges()` sometimes not work as expected. ``` @component({ selector: 'dynamic', template: `{{name}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic { name = 'initial name'; } const componetRef = cfr.createComponent(Dynamic); componentRef.changeDetectorRef // host view componentRef.injector.get(ChangeDetectorRef) // component view componentRef.instance.cdRef // componentView componentRef.instance.name = 'something new'; // componentRef.instance.cdRef.markForCheck(); componentRef.detectChanges(); // does not work without `markForCheck` componentRef.changeDetectorRef.detectChanges() // Users expect that Bar gets refreshed. componentRef.injector.get(ChangeDetectorRef).detectChanges() componentRef.instance.cdRef.detectChanges() // Example: https://stackblitz.com/edit/angular-drqzhw?file=src%2Fapp%2Fapp.module.ts ngOnInit() { const cmptRef = this._cfr .resolveComponentFactory(Dynamic) .create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = "changed name"; console.log("changing name"); cmptRef.changeDetectorRef.detectChanges(); // does not work }); setTimeout(() => { cmptRef.instance.name = "second changed name"; console.log("changing name"); cmptRef.injector.get(ChangeDetectorRef).detectChanges(); // works }, 2000); } ``` Since when the component is `boostrap` or `dynamic`, we created a hostView to host the component. And the `componentRef.changeDetectorRef` references to that hostView instead of the componentView itself. And when the component is `OnPush` and not dirty, calling `changeDetectorRef.detectChanges()` will bypass the component which is confusing. So this commit mark the root component dirty when calling `changeDetectorRef.detectChanges()` from the root hostView. This commit also resolves the following issue. ``` const myComp = TestBed.createComponent(OnPushComponent); myComp.componentInstance.abc = 123; myComp.detectChanges() // Does not work // We could document that users should do this instead // (and/or add a prop to ComponentFixture called `componentInstanceCDR`): myComp.componentRef.injector.get(ChangeDetectorRef).detectChanges(); // myComp.componentInstanceCDR.detectChanges(); ``` Now calling `myComp.detectChanges()` will work as expect and run CD on the OnPushComponent.
…nd onPush not dirty component Close angular#36667 Calling `componentRef.detectChanges()` sometimes not work as expected. ``` @component({ selector: 'dynamic', template: `{{name}}`, changeDetection: ChangeDetectionStrategy.OnPush }) export class Dynamic { name = 'initial name'; } const componetRef = cfr.createComponent(Dynamic); componentRef.changeDetectorRef // host view componentRef.injector.get(ChangeDetectorRef) // component view componentRef.instance.cdRef // componentView componentRef.instance.name = 'something new'; // componentRef.instance.cdRef.markForCheck(); componentRef.detectChanges(); // does not work without `markForCheck` componentRef.changeDetectorRef.detectChanges() // Users expect that Bar gets refreshed. componentRef.injector.get(ChangeDetectorRef).detectChanges() componentRef.instance.cdRef.detectChanges() // Example: https://stackblitz.com/edit/angular-drqzhw?file=src%2Fapp%2Fapp.module.ts ngOnInit() { const cmptRef = this._cfr .resolveComponentFactory(Dynamic) .create(this._injector); this._vcRef.insert(cmptRef.hostView); setTimeout(() => { cmptRef.instance.name = "changed name"; console.log("changing name"); cmptRef.changeDetectorRef.detectChanges(); // does not work }); setTimeout(() => { cmptRef.instance.name = "second changed name"; console.log("changing name"); cmptRef.injector.get(ChangeDetectorRef).detectChanges(); // works }, 2000); } ``` Since when the component is `boostrap` or `dynamic`, we created a hostView to host the component. And the `componentRef.changeDetectorRef` references to that hostView instead of the componentView itself. And when the component is `OnPush` and not dirty, calling `changeDetectorRef.detectChanges()` will bypass the component which is confusing. So this commit mark the root component dirty when calling `changeDetectorRef.detectChanges()` from the root hostView. This commit also resolves the following issue. ``` const myComp = TestBed.createComponent(OnPushComponent); myComp.componentInstance.abc = 123; myComp.detectChanges() // Does not work // We could document that users should do this instead // (and/or add a prop to ComponentFixture called `componentInstanceCDR`): myComp.componentRef.injector.get(ChangeDetectorRef).detectChanges(); // myComp.componentInstanceCDR.detectChanges(); ``` Now calling `myComp.detectChanges()` will work as expect and run CD on the OnPushComponent.
I'm also facing this issue on angular 11.0.8. You can find a minimal reproduction here: Hope it helps! |
In my case the following code helped
|
Hell, this took me some time to find out this is an Angular issue (NG 13), not mine. But at least the workaround to get a fresh CDR from the injector worked well for me! |
@pkozlowski-opensource as far as this bug is not yet fixed - can we atleast add note - warning, for example there: https://angular.io/guide/dynamic-component-loader that component's change detector is often not resolved 100% correctly and its recommended to use components injector to resolve Change detector? |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
🐞 bug report
Affected Package
The issue is caused by package @angular/coreIs this a regression?
Cannot sayDescription
Component created by #ComponentFactoryResolver.
ComponentFactoryResolver#createComponent returns ComponentRef that does have changeDetectorRef.
After any changes to component's input by code (not by template) and invocation of such ComponentRef#changeDetectorRef.detectChanges component's view won't be updated.
example from repro: dynamic-wrapper.component.ts#createComponent.
This is root issue.
To testing purposes ChangeDetectorRef was be added to component's constructor and invoked by
consumer of this ComponentFactoryResolver. CDR from component's injector works as expected
example from repro: dynamic-wrapper.component.ts#createComponent
🔬 Minimal Reproduction
StackBlitz
GitHub repro repository:
https://github.com/RolginRoman/ng-dynamic-components-cdr-repro
🔥 Exception or Error
🌍 Your Environment
Angular Version:
Anything else relevant?
Seems like browser, OS version or anything else doesn't matter
The text was updated successfully, but these errors were encountered: