diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts
index 35ce7660982b5..663b682fff02d 100644
--- a/packages/core/src/render3/node_manipulation.ts
+++ b/packages/core/src/render3/node_manipulation.ts
@@ -396,7 +396,6 @@ export function destroyLView(tView: TView, lView: LView) {
}
destroyViewTree(lView);
- unregisterLView(lView);
}
}
@@ -443,6 +442,9 @@ function cleanUpView(tView: TView, lView: LView): void {
lQueries.detachView(tView);
}
}
+
+ // Deregister the view once everything else has been cleaned up.
+ unregisterLView(lView);
}
}
diff --git a/packages/core/test/acceptance/integration_spec.ts b/packages/core/test/acceptance/integration_spec.ts
index e01aa0ab36859..07e7fd8cd4e00 100644
--- a/packages/core/test/acceptance/integration_spec.ts
+++ b/packages/core/test/acceptance/integration_spec.ts
@@ -11,7 +11,11 @@ import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/brow
import {CommonModule} from '@angular/common';
import {Component, ContentChild, Directive, ElementRef, EventEmitter, HostBinding, HostListener, Input, NgModule, OnInit, Output, Pipe, QueryList, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {Inject} from '@angular/core/src/di';
-import {TVIEW} from '@angular/core/src/render3/interfaces/view';
+import {readPatchedLView} from '@angular/core/src/render3/context_discovery';
+import {LContainer} from '@angular/core/src/render3/interfaces/container';
+import {getLViewById} from '@angular/core/src/render3/interfaces/lview_tracking';
+import {isLView} from '@angular/core/src/render3/interfaces/type_checks';
+import {ID, LView, PARENT, TVIEW} from '@angular/core/src/render3/interfaces/view';
import {getLView} from '@angular/core/src/render3/state';
import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
import {fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
@@ -2011,6 +2015,43 @@ describe('acceptance integration tests', () => {
expect(fixture.nativeElement.innerHTML).toContain('Hello');
});
+ onlyInIvy('The test is checking Ivy-specific logic')
+ .it('should remove child LView from the registry when the root view is destroyed', () => {
+ @Component({template: ''})
+ class App {
+ }
+
+ @Component({selector: 'child', template: ''})
+ class Child {
+ }
+
+ @Component({selector: 'grand-child', template: ''})
+ class GrandChild {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, Child, GrandChild]});
+ const fixture = TestBed.createComponent(App);
+ const grandChild = fixture.debugElement.query(By.directive(GrandChild)).componentInstance;
+ fixture.detectChanges();
+ const leafLView = readPatchedLView(grandChild)!;
+ const lViewIds: number[] = [];
+ let current: LView|LContainer|null = leafLView;
+
+ while (current) {
+ isLView(current) && lViewIds.push(current[ID]);
+ current = current[PARENT];
+ }
+
+ // We expect 3 views: `GrandChild`, `Child` and `App`.
+ expect(lViewIds).toEqual([leafLView[ID], leafLView[ID] - 1, leafLView[ID] - 2]);
+ expect(lViewIds.every(id => getLViewById(id) !== null)).toBe(true);
+
+ fixture.destroy();
+
+ // Expect all 3 views to be removed from the registry once the root is destroyed.
+ expect(lViewIds.map(getLViewById)).toEqual([null, null, null]);
+ });
+
describe('tView.firstUpdatePass', () => {
function isFirstUpdatePass() {
const lView = getLView();