From 032f8b934ed0d69bdef0efaf7ff1ace871ce0b05 Mon Sep 17 00:00:00 2001 From: Michael Giambalvo Date: Tue, 14 Mar 2017 16:22:04 -0700 Subject: [PATCH] fix(upgrade): Run digest cycle for upgraded components in a child zone This fixes #6385 by running `$rootScope.$digest` in a child zone. This prevents tasks started in the digest cycle from triggering the digest again, thus preventing the infinite loop. --- packages/upgrade/src/static/upgrade_module.ts | 20 +++++- .../integration/change_detection_spec.ts | 64 ++++++++++--------- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/packages/upgrade/src/static/upgrade_module.ts b/packages/upgrade/src/static/upgrade_module.ts index 5c32a814c9a944..b169c5e9bd00ce 100644 --- a/packages/upgrade/src/static/upgrade_module.ts +++ b/packages/upgrade/src/static/upgrade_module.ts @@ -207,8 +207,24 @@ export class UpgradeModule { // stabilizing setTimeout(() => { const $rootScope = $injector.get('$rootScope'); - const subscription = - this.ngZone.onMicrotaskEmpty.subscribe(() => $rootScope.$digest()); + const subscription = this.ngZone.onMicrotaskEmpty.subscribe(() => { + const zone = (window as any)['Zone']; + if (!zone.current.get('isNgUpgradeZone')) { + zone.current + .fork({ + name: 'ngUpgrade', + properties: {'isNgUpgradeZone': true}, + onInvoke: function( + delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + callback: Function, applyThis: any, applyArgs: any, + source: string) { + delegate.invoke(targetZone, callback, applyThis, applyArgs, source) + $rootScope.$digest() + } + }) + .run(() => {$rootScope.$digest()}) + } + }); $rootScope.$on('$destroy', () => { subscription.unsubscribe(); }); }, 0); } diff --git a/packages/upgrade/test/static/integration/change_detection_spec.ts b/packages/upgrade/test/static/integration/change_detection_spec.ts index 96eae146a4dbde..791f04377d287d 100644 --- a/packages/upgrade/test/static/integration/change_detection_spec.ts +++ b/packages/upgrade/test/static/integration/change_detection_spec.ts @@ -100,10 +100,9 @@ export function main() { ngOnChanges(changes: SimpleChanges) { if (changes['value'].isFirstChange()) return; - this.zone.onMicrotaskEmpty.subscribe( - () => { expect(element.textContent).toEqual('5'); }); + this.zone.onStable.subscribe(() => { expect(element.textContent).toEqual('5'); }); - Promise.resolve().then(() => this.valueFromPromise = changes['value'].currentValue); + Promise.resolve().then(() => {this.valueFromPromise = changes['value'].currentValue}); } } @@ -129,33 +128,36 @@ export function main() { // This test demonstrates https://github.com/angular/angular/issues/6385 // which was invalidly fixed by https://github.com/angular/angular/pull/6386 - // it('should not trigger $digest from an async operation in a watcher', async(() => { - // @Component({selector: 'my-app', template: ''}) - // class AppComponent { - // } - - // @NgModule({declarations: [AppComponent], imports: [BrowserModule]}) - // class Ng2Module { - // } - - // const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); - // const ng1Module = angular.module('ng1', []).directive( - // 'myApp', adapter.downgradeNg2Component(AppComponent)); - - // const element = html(''); - - // adapter.bootstrap(element, ['ng1']).ready((ref) => { - // let doTimeout = false; - // let timeoutId: number; - // ref.ng1RootScope.$watch(() => { - // if (doTimeout && !timeoutId) { - // timeoutId = window.setTimeout(function() { - // timeoutId = null; - // }, 10); - // } - // }); - // doTimeout = true; - // }); - // })); + it('should not trigger $digest from an async operation in a watcher', async(() => { + @Component({selector: 'my-app', template: ''}) + class AppComponent { + } + + @NgModule({ + declarations: [AppComponent], + entryComponents: [AppComponent], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } + + const ng1Module = angular.module('ng1', []).directive( + 'myApp', downgradeComponent({component: AppComponent})); + + const element = html(''); + + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { + let doTimeout = false; + let timeoutId: number; + let rootScope = upgrade.$injector.get('$rootScope'); + rootScope.$watch(() => { + if (doTimeout && !timeoutId) { + timeoutId = window.setTimeout(function() { timeoutId = null; }, 10); + } + }); + doTimeout = true; + }); + })); }); }