angular: [2.2.1] Change Detection Not Working Correctly with Upgrade Adapter and Propagating Changes to Descendent Components

I’m submitting a … (check one with “x”)

[*] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior When using the UpgradeAdapter, change detection does not propagate properties to descendent components in the 'angular' Zone (either into setters, or ngOnChanges()).

Instead, it propagates changes in the '<root>' zone, which causes problems when the child component performs some asynchronous operation. At the end of the async operation, change detection does not trigger again, so those async changes are not seen in the DOM.

Note: the problem does not exist when using platformBrowserDynamic in place of the UpgradeAdapter.

Expected behavior When using platformBrowserDynamic instead of the UpgradeAdapter, changes propagate to child components in the 'angular' zone, which is correct, and there is no bug. Same should happen with UpgradeAdapter.

Minimal reproduction of the problem with instructions

Reproduction Plnkr: http://plnkr.co/edit/DNF8BapmfaDIhIM7fvkS?p=preview

Explanation of Plnkr: When loaded, click the button. You will see that the parent component (AppComponent) updates the numClicks, as well as the child component’s synchronous numClicks value. However, you will see that a value changed from an async promise resolution has not been updated in the DOM.

Note: Inside main.ts, you can comment out the UpgradeAdapter, and uncomment platformBrowserDynamic() to see the correct behavior.

General Steps to reproduce:

  1. Create a parent component, which passes an input attribute to a child component.
  2. On click of a button (or anything) in the parent component, modify the value of the property/attribute.
  3. Create a setter on the child component for that @Input attribute.
  4. Resolve a promise from the setter in the child component which updates another child component property. You will not see that property change in the DOM.

What is the motivation / use case for changing the behavior? We have an Angular 1 app which is now in the process of being upgraded to Angular 2.

We have one of our Angular 1 components trigger a change in an Angular 2 component, but then change detection breaks inside the Angular 2 component tree when children try to asynchronously retrieve data from the server. We essentially don’t see that data populated on the Angular 2 components when the server requests are complete, because the '<root>' zone doesn’t trigger change detection.

Please tell us about your environment: OS: Mac OSX 10.11.1 (El Capitan) / Windows 7. IDE: IntelliJ 2016.2 / Visual Studio Code / Plnkr Package Manager: npm

  • Angular version: 2.1.0
  • Browser: OSX Chrome 53.0.2785.143 (64-bit) OSX Safari 9.0.3 Win7 Internet Explorer 11.0.9600.18449
  • Language: TypeScript 2.0.2
  • Node (for AoT issues): node --version = 6.7.0

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 9
  • Comments: 25 (13 by maintainers)

Commits related to this issue

Most upvoted comments

This is still a problem for me, version 4.2.4. Will try to make a quick example if I can. Does anyone else still have this issue?

I have the same issue on the hybrid app with Angular 6.1.0. Any idea how to fix it?

Yes, I do believe it is upgradeAdapter specific. When bootstrapping with platformBrowserDynamic() instead, it works as expected.

For everyone’s FYI by the way, anything you run asynchronously where you notice the view is not updating, try the following workaround as mentioned by @mbenna:

Change something like this code:

ngOnChanges() {
    this.loadMoreData().subscribe( result => this.message = result.message );
}

to:

constructor( private ngZone: NgZone ) {}

ngOnChanges() {
    this.ngZone.run( () =>
        this.loadMoreData().subscribe( result => this.message = result.message );
    } );
}

I spent the morning chasing down my own repro case of this using angular 2.2.1 and zone.js 0.6.26 and came to exactly the same conclusion… with upgradeAdapter the child component is called in <root> zone, and without upgradeAdapter the child component is called in angular zone as expected. For now I’m working around it by wrapping constructor code and ngOnChanges code with return this._ngZone.run(() => { desired code })

The plnkr above is exactly what I was going to create, so thanks for saving me the time 😃

@petebacondarwin left comments

I think using broadcast is not right because:

  1. It is slow
  2. It does not guarantee that the ng1 and ng2 change detection is interleaved in the right order.

Can we just autodect that we are in infinite loop and break out of it?

The problem with the infinite digests is that triggering a change detection was causing a digest which was then triggering a change detection, etc. By using broadcast, we have two separate mechanisms that are not going to keep triggering each other. I am happy to proven wrong - this might break everything. I have a branch that is almost working; I’ll publish it within the next hour for your review.