fix(core): unsubscribe from the `onError` when the root view is removed (#39940)
At the moment, when creating a root module, a subscription to the `onError` subject is also created. It captures the scope where `NgModuleRef` is created and prevents it from being garbage collected. Also note that this `NgModuleRef` has a reference to the root module instance (e.g. `AppModule`), which also prevents it from being GC'd. PR Close #39940
This commit is contained in:
parent
f01c713ee2
commit
5a3a154cd8
|
@ -350,12 +350,17 @@ export class PlatformRef {
|
||||||
if (!exceptionHandler) {
|
if (!exceptionHandler) {
|
||||||
throw new Error('No ErrorHandler. Is platform module (BrowserModule) included?');
|
throw new Error('No ErrorHandler. Is platform module (BrowserModule) included?');
|
||||||
}
|
}
|
||||||
moduleRef.onDestroy(() => remove(this._modules, moduleRef));
|
ngZone!.runOutsideAngular(() => {
|
||||||
ngZone!.runOutsideAngular(() => ngZone!.onError.subscribe({
|
const subscription = ngZone!.onError.subscribe({
|
||||||
next: (error: any) => {
|
next: (error: any) => {
|
||||||
exceptionHandler.handleError(error);
|
exceptionHandler.handleError(error);
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
moduleRef.onDestroy(() => {
|
||||||
|
remove(this._modules, moduleRef);
|
||||||
|
subscription.unsubscribe();
|
||||||
|
});
|
||||||
|
});
|
||||||
return _callAndReportToErrorHandler(exceptionHandler, ngZone!, () => {
|
return _callAndReportToErrorHandler(exceptionHandler, ngZone!, () => {
|
||||||
const initStatus: ApplicationInitStatus = moduleRef.injector.get(ApplicationInitStatus);
|
const initStatus: ApplicationInitStatus = moduleRef.injector.get(ApplicationInitStatus);
|
||||||
initStatus.runInitializers();
|
initStatus.runInitializers();
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ApplicationRef, COMPILER_OPTIONS, Component, destroyPlatform, NgModule, TestabilityRegistry, ViewEncapsulation} from '@angular/core';
|
import {ApplicationRef, COMPILER_OPTIONS, Component, destroyPlatform, NgModule, NgZone, TestabilityRegistry, ViewEncapsulation} from '@angular/core';
|
||||||
import {expect} from '@angular/core/testing/src/testing_internal';
|
import {expect} from '@angular/core/testing/src/testing_internal';
|
||||||
import {BrowserModule} from '@angular/platform-browser';
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||||
|
@ -227,6 +227,22 @@ describe('bootstrap', () => {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('PlatformRef cleanup', () => {
|
||||||
|
it('should unsubscribe from `onError` when Injector is destroyed',
|
||||||
|
withBody('<my-app></my-app>', async () => {
|
||||||
|
const TestModule = createComponentAndModule();
|
||||||
|
|
||||||
|
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(TestModule);
|
||||||
|
const ngZone = ngModuleRef.injector.get(NgZone);
|
||||||
|
|
||||||
|
expect(ngZone.onError.observers.length).toBe(1);
|
||||||
|
|
||||||
|
ngModuleRef.destroy();
|
||||||
|
|
||||||
|
expect(ngZone.onError.observers.length).toBe(0);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
onlyInIvy('options cannot be changed in Ivy').describe('changing bootstrap options', () => {
|
onlyInIvy('options cannot be changed in Ivy').describe('changing bootstrap options', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(console, 'error');
|
spyOn(console, 'error');
|
||||||
|
@ -365,4 +381,4 @@ export class MultipleSelectorsAppComponent {
|
||||||
bootstrap: [MultipleSelectorsAppComponent],
|
bootstrap: [MultipleSelectorsAppComponent],
|
||||||
})
|
})
|
||||||
export class MultipleSelectorsAppModule {
|
export class MultipleSelectorsAppModule {
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue