|
6 | 6 | * found in the LICENSE file at https://angular.io/license
|
7 | 7 | */
|
8 | 8 |
|
9 |
| -import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, QueryList, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core'; |
| 9 | +import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, QueryList, RendererFactory2, TemplateRef, ViewContainerRef, ViewRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core'; |
10 | 10 | import {ViewEncapsulation} from '../../src/metadata';
|
11 |
| -import {AttributeMarker, NO_CHANGE, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, loadViewQuery, queryRefresh, viewQuery} from '../../src/render3/index'; |
12 | 11 |
|
13 |
| -import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, nextContext, projection, projectionDef, reference, template, text, textBinding, elementHostAttrs} from '../../src/render3/instructions'; |
| 12 | +import {AttributeMarker, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, listener, loadViewQuery, NgOnChangesFeature, queryRefresh, viewQuery,} from '../../src/render3/index'; |
| 13 | + |
| 14 | +import {allocHostVars, bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementEnd, elementHostAttrs, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, nextContext, projection, projectionDef, reference, template, text, textBinding,} from '../../src/render3/instructions'; |
14 | 15 | import {RenderFlags} from '../../src/render3/interfaces/definition';
|
15 | 16 | import {RElement} from '../../src/render3/interfaces/renderer';
|
16 |
| -import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound'; |
17 | 17 | import {NgModuleFactory} from '../../src/render3/ng_module_ref';
|
18 | 18 | import {pipe, pipeBind1} from '../../src/render3/pipe';
|
19 | 19 | import {getLView} from '../../src/render3/state';
|
20 | 20 | import {getNativeByIndex} from '../../src/render3/util';
|
| 21 | +import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound'; |
21 | 22 | import {NgForOf} from '../../test/render3/common_with_def';
|
22 | 23 |
|
23 | 24 | import {getRendererFactory2} from './imported_renderer2';
|
24 |
| -import {ComponentFixture, TemplateFixture, createComponent, getDirectiveOnNode} from './render_util'; |
| 25 | +import {ComponentFixture, createComponent, getDirectiveOnNode, TemplateFixture,} from './render_util'; |
25 | 26 |
|
26 | 27 | const Component: typeof _Component = function(...args: any[]): any {
|
27 | 28 | // In test we use @Component for documentation only so it's safe to mock out the implementation.
|
@@ -2087,4 +2088,56 @@ describe('ViewContainerRef', () => {
|
2087 | 2088 | });
|
2088 | 2089 |
|
2089 | 2090 | });
|
| 2091 | + |
| 2092 | + describe('view destruction', () => { |
| 2093 | + class CompWithListenerThatDestroysItself { |
| 2094 | + constructor(private viewRef: ViewRef) {} |
| 2095 | + |
| 2096 | + onClick() {} |
| 2097 | + |
| 2098 | + ngOnDestroy() { this.viewRef.destroy(); } |
| 2099 | + |
| 2100 | + static ngComponentDef = defineComponent({ |
| 2101 | + type: CompWithListenerThatDestroysItself, |
| 2102 | + selectors: [['comp-with-listener-and-on-destroy']], |
| 2103 | + consts: 2, |
| 2104 | + vars: 0, |
| 2105 | + /** <button (click)="onClick()"> Click me </button> */ |
| 2106 | + template: function CompTemplate(rf: RenderFlags, ctx: any) { |
| 2107 | + if (rf & RenderFlags.Create) { |
| 2108 | + elementStart(0, 'button'); |
| 2109 | + { |
| 2110 | + listener('click', function() { return ctx.onClick(); }); |
| 2111 | + text(1, 'Click me'); |
| 2112 | + } |
| 2113 | + elementEnd(); |
| 2114 | + } |
| 2115 | + }, |
| 2116 | + // We want the ViewRef, so we rely on the knowledge that `ViewRef` is actually given |
| 2117 | + // when injecting `ChangeDetectorRef`. |
| 2118 | + factory: |
| 2119 | + () => new CompWithListenerThatDestroysItself(directiveInject(ChangeDetectorRef as any)), |
| 2120 | + }); |
| 2121 | + } |
| 2122 | + |
| 2123 | + |
| 2124 | + it('should not error when destroying a view with listeners twice', () => { |
| 2125 | + const CompWithChildListener = createComponent('test-app', (rf: RenderFlags, ctx: any) => { |
| 2126 | + if (rf & RenderFlags.Create) { |
| 2127 | + element(0, 'comp-with-listener-and-on-destroy'); |
| 2128 | + } |
| 2129 | + }, 1, 0, [CompWithListenerThatDestroysItself]); |
| 2130 | + |
| 2131 | + const fixture = new ComponentFixture(CompWithChildListener); |
| 2132 | + fixture.update(); |
| 2133 | + |
| 2134 | + // Destroying the parent view will also destroy all of its children views and call their |
| 2135 | + // onDestroy hooks. Here, our child view attempts to destroy itself *again* in its onDestroy. |
| 2136 | + // This test exists to verify that no errors are thrown when doing this. We want the test |
| 2137 | + // component to destroy its own view in onDestroy because the destroy hooks happen as a |
| 2138 | + // *part of* view destruction. We also ensure that the test component has at least one |
| 2139 | + // listener so that it runs the event listener cleanup code path. |
| 2140 | + fixture.destroy(); |
| 2141 | + }); |
| 2142 | + }); |
2090 | 2143 | });
|
0 commit comments