diff --git a/packages/core/test/acceptance/bootstrap_spec.ts b/packages/core/test/acceptance/bootstrap_spec.ts
index 24c8acc54b8..1e8f1bd599a 100644
--- a/packages/core/test/acceptance/bootstrap_spec.ts
+++ b/packages/core/test/acceptance/bootstrap_spec.ts
@@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ApplicationRef, COMPILER_OPTIONS, Component, destroyPlatform, forwardRef, NgModule, NgZone, TestabilityRegistry, ViewEncapsulation} from '@angular/core';
-import {BrowserModule} from '@angular/platform-browser';
+import {ApplicationRef, COMPILER_OPTIONS, Component, destroyPlatform, forwardRef, NgModule, NgZone, TestabilityRegistry, ViewContainerRef, ViewEncapsulation} from '@angular/core';
+import {bootstrapApplication, BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {withBody} from '@angular/private/testing';
@@ -38,6 +38,30 @@ describe('bootstrap', () => {
}
}));
+
+ it('should allow injecting VCRef into the root (bootstrapped) component',
+ withBody('before||after', async () => {
+ @Component({selector: 'dynamic-cmp', standalone: true, template: 'dynamic'})
+ class DynamicCmp {
+ }
+
+ @Component({selector: 'test-cmp', standalone: true, template: '(test)'})
+ class TestCmp {
+ constructor(public vcRef: ViewContainerRef) {}
+ }
+
+ expect(document.body.textContent).toEqual('before||after');
+
+ const appRef = await bootstrapApplication(TestCmp);
+ expect(document.body.textContent).toEqual('before|(test)|after');
+
+ appRef.components[0].instance.vcRef.createComponent(DynamicCmp);
+ expect(document.body.textContent).toEqual('before|(test)dynamic|after');
+
+ appRef.destroy();
+ expect(document.body.textContent).toEqual('before||after');
+ }));
+
describe('options', () => {
function createComponentAndModule(
options:
diff --git a/packages/core/test/acceptance/view_container_ref_spec.ts b/packages/core/test/acceptance/view_container_ref_spec.ts
index c12638115da..fcac7467774 100644
--- a/packages/core/test/acceptance/view_container_ref_spec.ts
+++ b/packages/core/test/acceptance/view_container_ref_spec.ts
@@ -8,7 +8,7 @@
import {CommonModule, DOCUMENT} from '@angular/common';
import {computeMsgId} from '@angular/compiler';
-import {Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, InjectionToken, Injector, Input, NgModule, NgModuleRef, NO_ERRORS_SCHEMA, OnDestroy, OnInit, Pipe, PipeTransform, QueryList, Renderer2, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵsetDocument} from '@angular/core';
+import {ChangeDetectorRef, Compiler, Component, ComponentFactoryResolver, Directive, DoCheck, ElementRef, EmbeddedViewRef, ErrorHandler, InjectionToken, Injector, Input, NgModule, NgModuleRef, NO_ERRORS_SCHEMA, OnDestroy, OnInit, Pipe, PipeTransform, QueryList, Renderer2, RendererFactory2, RendererType2, Sanitizer, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, ɵsetDocument} from '@angular/core';
import {isProceduralRenderer} from '@angular/core/src/render3/interfaces/renderer';
import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
import {ComponentFixture, TestBed, TestComponentRenderer} from '@angular/core/testing';
@@ -150,6 +150,31 @@ describe('ViewContainerRef', () => {
expect(fixture.debugElement.nativeElement.innerHTML).toContain('Hello');
});
+ it('should view queries in dynamically created components', () => {
+ @Component({
+ selector: 'dynamic-cmpt-with-view-queries',
+ template: `
`,
+ })
+ class DynamicCompWithViewQueries {
+ @ViewChildren('foo') fooList!: QueryList;
+ }
+
+ @Component({
+ selector: 'test-cmp',
+ template: ``,
+ })
+ class TestCmp {
+ constructor(readonly vcRf: ViewContainerRef) {}
+ }
+
+ const fixture = TestBed.createComponent(TestCmp);
+ const cmpRef = fixture.componentInstance.vcRf.createComponent(DynamicCompWithViewQueries);
+ fixture.detectChanges();
+
+ expect(cmpRef.instance.fooList.length).toBe(1);
+ expect(cmpRef.instance.fooList.first).toBeAnInstanceOf(ElementRef);
+ });
+
describe('element namespaces', () => {
function runTestWithSelectors(svgSelector: string, mathMLSelector: string) {
it('should be set correctly for host elements of dynamically created components', () => {
@@ -374,6 +399,33 @@ describe('ViewContainerRef', () => {
expect(cmpt.c1.indexOf(viewRef)).toBe(-1);
expect(cmpt.c2.indexOf(viewRef)).toBe(0);
});
+
+ it('should add embedded views at the right position in the DOM tree (ng-template next to other ng-template)',
+ () => {
+ @Component({
+ template: `before|AB|after`
+ })
+ class TestCmp {
+ @ViewChild('a', {static: true}) ta!: TemplateRef<{}>;
+ @ViewChild('b', {static: true}) tb!: TemplateRef<{}>;
+ @ViewChild('a', {static: true, read: ViewContainerRef}) ca!: ViewContainerRef;
+ @ViewChild('b', {static: true, read: ViewContainerRef}) cb!: ViewContainerRef;
+ }
+
+ const fixture = TestBed.createComponent(TestCmp);
+ const testCmpInstance = fixture.componentInstance;
+
+ fixture.detectChanges();
+ expect(fixture.nativeElement.textContent).toBe('before||after');
+
+ testCmpInstance.cb.createEmbeddedView(testCmpInstance.tb);
+ fixture.detectChanges();
+ expect(fixture.nativeElement.textContent).toBe('before|B|after');
+
+ testCmpInstance.ca.createEmbeddedView(testCmpInstance.ta);
+ fixture.detectChanges();
+ expect(fixture.nativeElement.textContent).toBe('before|AB|after');
+ });
});
describe('move', () => {
@@ -685,7 +737,7 @@ describe('ViewContainerRef', () => {
});
});
- describe('getters', () => {
+ describe('getters for the anchor node', () => {
it('should work on templates', () => {
@Component({
template: `
@@ -707,10 +759,56 @@ describe('ViewContainerRef', () => {
// that the comment is a placeholder for a container.
expect(vcRefDir.vcref.element.nativeElement.textContent).toEqual('container');
- expect(vcRefDir.vcref.injector.get(ElementRef).nativeElement.textContent);
+ expect(vcRefDir.vcref.injector.get(ElementRef).nativeElement.textContent)
+ .toEqual('container');
expect(getElementHtml(vcRefDir.vcref.parentInjector.get(ElementRef).nativeElement))
.toBe('');
});
+
+ it('should work on elements', () => {
+ @Component({
+ template: `
+
+
+ `
+ })
+ class TestComponent {
+ @ViewChild(VCRefDirective, {static: true}) vcRefDir!: VCRefDirective;
+ }
+
+ TestBed.configureTestingModule({declarations: [VCRefDirective, TestComponent]});
+ const fixture = TestBed.createComponent(TestComponent);
+ fixture.detectChanges();
+ const vcref = fixture.componentInstance.vcRefDir.vcref;
+
+ expect(vcref.element.nativeElement.tagName.toLowerCase()).toEqual('header');
+ expect(vcref.injector.get(ElementRef).nativeElement.tagName.toLowerCase()).toEqual('header');
+ });
+
+ it('should work on components', () => {
+ @Component({selector: 'header-cmp', template: ``})
+ class HeaderCmp {
+ }
+
+ @Component({
+ template: `
+
+
+ `
+ })
+ class TestComponent {
+ @ViewChild(VCRefDirective, {static: true}) vcRefDir!: VCRefDirective;
+ }
+
+ TestBed.configureTestingModule({declarations: [HeaderCmp, VCRefDirective, TestComponent]});
+ const fixture = TestBed.createComponent(TestComponent);
+ fixture.detectChanges();
+ const vcref = fixture.componentInstance.vcRefDir.vcref;
+
+ expect(vcref.element.nativeElement.tagName.toLowerCase()).toEqual('header-cmp');
+ expect(vcref.injector.get(ElementRef).nativeElement.tagName.toLowerCase())
+ .toEqual('header-cmp');
+ });
});
describe('detach', () => {
@@ -771,6 +869,30 @@ describe('ViewContainerRef', () => {
expect(viewE.destroyed).toBeFalsy();
expect(ngDevMode!.rendererDestroyNode).toBe(0);
});
+
+ it('should not throw when destroying a detached component view', () => {
+ @Component({selector: 'dynamic-cmp'})
+ class DynamicCmp {
+ }
+
+ @Component({selector: 'test-cmp'})
+ class TestCmp {
+ constructor(public vcRef: ViewContainerRef) {}
+ }
+
+ const fixture = TestBed.createComponent(TestCmp);
+ fixture.detectChanges();
+
+ const vcRef = fixture.componentInstance.vcRef;
+ const cmpRef = vcRef.createComponent(DynamicCmp);
+ fixture.detectChanges();
+
+ vcRef.detach(vcRef.indexOf(cmpRef.hostView));
+
+ expect(() => {
+ cmpRef.destroy();
+ }).not.toThrow();
+ });
});
describe('remove', () => {
@@ -1391,6 +1513,47 @@ describe('ViewContainerRef', () => {
expect(fixture.debugElement.nativeElement.innerHTML).toContain('Child Component');
});
+ it('should return ComponentRef with ChangeDetectorRef attached to root view', () => {
+ @Component({selector: 'dynamic-cmp', template: ``})
+ class DynamicCmp {
+ doCheckCount = 0;
+
+ ngDoCheck() {
+ this.doCheckCount++;
+ }
+ }
+
+ @Component({template: ``})
+ class TestCmp {
+ constructor(
+ public viewContainerRef: ViewContainerRef,
+ public componentFactoryResolver: ComponentFactoryResolver) {}
+ }
+
+ const fixture = TestBed.createComponent(TestCmp);
+ const testCmpInstance = fixture.componentInstance;
+ const cmpFactory =
+ testCmpInstance.componentFactoryResolver.resolveComponentFactory(DynamicCmp);
+ const dynamicCmpRef = testCmpInstance.viewContainerRef.createComponent(cmpFactory);
+
+ // change detection didn't run at all
+ expect(dynamicCmpRef.instance.doCheckCount).toBe(0);
+
+ // running change detection on the dynamicCmpRef level
+ dynamicCmpRef.changeDetectorRef.detectChanges();
+ expect(dynamicCmpRef.instance.doCheckCount).toBe(1);
+
+ // running change detection on the TestBed fixture level
+ fixture.changeDetectorRef.detectChanges();
+ expect(dynamicCmpRef.instance.doCheckCount).toBe(2);
+
+ // The injector should retrieve the change detector ref for DynamicComp. As such,
+ // the doCheck hook for DynamicComp should NOT run upon ref.detectChanges().
+ const changeDetector = dynamicCmpRef.injector.get(ChangeDetectorRef);
+ changeDetector.detectChanges();
+ expect(dynamicCmpRef.instance.doCheckCount).toBe(2);
+ });
+
describe('createComponent using Type', () => {
const TOKEN_A = new InjectionToken('A');
const TOKEN_B = new InjectionToken('B');
diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts
deleted file mode 100644
index 9dfd64b2914..00000000000
--- a/packages/core/test/render3/view_container_ref_spec.ts
+++ /dev/null
@@ -1,451 +0,0 @@
-/**
- * @license
- * Copyright Google LLC All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.io/license
- */
-
-import {QueryFlags} from '@angular/core/src/render3/interfaces/query';
-import {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view';
-
-import {ChangeDetectorRef, Component as _Component, ComponentFactoryResolver, ElementRef, QueryList, TemplateRef, ViewContainerRef, ViewRef} from '../../src/core';
-import {ViewEncapsulation} from '../../src/metadata';
-import {injectComponentFactoryResolver, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵlistener, ɵɵloadQuery, ɵɵqueryRefresh, ɵɵviewQuery} from '../../src/render3/index';
-import {ɵɵdirectiveInject, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵtemplate, ɵɵtext} from '../../src/render3/instructions/all';
-import {RenderFlags} from '../../src/render3/interfaces/definition';
-import {RElement} from '../../src/render3/interfaces/renderer_dom';
-import {getLView} from '../../src/render3/state';
-import {getNativeByIndex} from '../../src/render3/util/view_utils';
-
-import {ComponentFixture, createComponent, TemplateFixture} from './render_util';
-
-
-const Component: typeof _Component = function(...args: any[]): any {
- // In test we use @Component for documentation only so it's safe to mock out the implementation.
- return () => undefined;
-} as any;
-
-
-describe('ViewContainerRef', () => {
- let directiveInstance: DirectiveWithVCRef|null;
-
- beforeEach(() => directiveInstance = null);
-
- class DirectiveWithVCRef {
- static ɵfac = () => directiveInstance = new DirectiveWithVCRef(
- ɵɵdirectiveInject(ViewContainerRef as any), injectComponentFactoryResolver())
-
- static ɵdir = ɵɵdefineDirective({
- type: DirectiveWithVCRef,
- selectors: [['', 'vcref', '']],
- inputs: {tplRef: 'tplRef', name: 'name'}
- });
-
- // TODO(issue/24571): remove '!'.
- tplRef!: TemplateRef<{}>;
-
- name: string = '';
-
- // injecting a ViewContainerRef to create a dynamic container in which embedded views will be
- // created
- constructor(public vcref: ViewContainerRef, public cfr: ComponentFactoryResolver) {}
- }
-
- describe('API', () => {
- describe('createEmbeddedView (incl. insert)', () => {
- it('should add embedded views at the right position in the DOM tree (ng-template next to other ng-template)',
- () => {
- let directiveInstances: TestDirective[] = [];
-
- class TestDirective {
- static ɵfac =
- () => {
- const instance = new TestDirective(
- ɵɵdirectiveInject(ViewContainerRef as any),
- ɵɵdirectiveInject(TemplateRef as any));
-
- directiveInstances.push(instance);
-
- return instance;
- }
-
- static ɵdir = ɵɵdefineDirective({
- type: TestDirective,
- selectors: [['', 'testdir', '']],
- });
-
- constructor(private _vcRef: ViewContainerRef, private _tplRef: TemplateRef<{}>) {}
-
- insertTpl(ctx: {}) {
- this._vcRef.createEmbeddedView(this._tplRef, ctx);
- }
-
- remove(index?: number) {
- this._vcRef.remove(index);
- }
- }
-
- function EmbeddedTemplateA(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵtext(0, 'A');
- }
- }
-
- function EmbeddedTemplateB(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵtext(0, 'B');
- }
- }
-
- /**
- * before|
- * A
- * B
- * |after
- */
- class TestComponent {
- // TODO(issue/24571): remove '!'.
- testDir!: TestDirective;
- static ɵfac = () => new TestComponent();
- static ɵcmp = ɵɵdefineComponent({
- type: TestComponent,
- encapsulation: ViewEncapsulation.None,
- selectors: [['test-cmp']],
- decls: 4,
- vars: 0,
- consts: [['testdir', '']],
- template:
- (rf: RenderFlags, cmp: TestComponent) => {
- if (rf & RenderFlags.Create) {
- ɵɵtext(0, 'before|');
- ɵɵtemplate(1, EmbeddedTemplateA, 1, 0, 'ng-template', 0);
- ɵɵtemplate(2, EmbeddedTemplateB, 1, 0, 'ng-template', 0);
- ɵɵtext(3, '|after');
- }
- },
- dependencies: [TestDirective]
- });
- }
-
- const fixture = new ComponentFixture(TestComponent);
- expect(fixture.html).toEqual('before||after');
-
- directiveInstances![1].insertTpl({});
- expect(fixture.html).toEqual('before|B|after');
-
- directiveInstances![0].insertTpl({});
- expect(fixture.html).toEqual('before|AB|after');
- });
- });
-
- describe('createComponent', () => {
- let templateExecutionCounter = 0;
-
- describe('ComponentRef', () => {
- let dynamicComp!: DynamicComp;
-
- class AppComp {
- constructor(public vcr: ViewContainerRef, public cfr: ComponentFactoryResolver) {}
-
- static ɵfac =
- () => {
- return new AppComp(
- ɵɵdirectiveInject(ViewContainerRef as any), injectComponentFactoryResolver());
- }
-
- static ɵcmp = ɵɵdefineComponent({
- type: AppComp,
- selectors: [['app-comp']],
- decls: 0,
- vars: 0,
- template: (rf: RenderFlags, cmp: AppComp) => {}
- });
- }
-
- class DynamicComp {
- doCheckCount = 0;
-
- ngDoCheck() {
- this.doCheckCount++;
- }
-
- static ɵfac = () => dynamicComp = new DynamicComp();
-
- static ɵcmp = ɵɵdefineComponent({
- type: DynamicComp,
- selectors: [['dynamic-comp']],
- decls: 0,
- vars: 0,
- template: (rf: RenderFlags, cmp: DynamicComp) => {}
- });
- }
-
- it('should return ComponentRef with ChangeDetectorRef attached to root view', () => {
- const fixture = new ComponentFixture(AppComp);
-
- const dynamicCompFactory = fixture.component.cfr.resolveComponentFactory(DynamicComp);
- const ref = fixture.component.vcr.createComponent(dynamicCompFactory);
- fixture.update();
- expect(dynamicComp.doCheckCount).toEqual(1);
-
- // The change detector ref should be attached to the root view that contains
- // DynamicComp, so the doCheck hook for DynamicComp should run upon ref.detectChanges().
- ref.changeDetectorRef.detectChanges();
- expect(dynamicComp.doCheckCount).toEqual(2);
- expect((ref.changeDetectorRef as any).context).toBeNull();
- });
-
- it('should return ComponentRef that can retrieve component ChangeDetectorRef through its injector',
- () => {
- const fixture = new ComponentFixture(AppComp);
-
- const dynamicCompFactory = fixture.component.cfr.resolveComponentFactory(DynamicComp);
- const ref = fixture.component.vcr.createComponent(dynamicCompFactory);
- fixture.update();
- expect(dynamicComp.doCheckCount).toEqual(1);
-
- // The injector should retrieve the change detector ref for DynamicComp. As such,
- // the doCheck hook for DynamicComp should NOT run upon ref.detectChanges().
- const changeDetector = ref.injector.get(ChangeDetectorRef);
- changeDetector.detectChanges();
- expect(dynamicComp.doCheckCount).toEqual(1);
- expect((changeDetector as any).context).toEqual(dynamicComp);
- });
-
- it('should not throw when destroying a reattached component', () => {
- const fixture = new ComponentFixture(AppComp);
-
- const dynamicCompFactory = fixture.component.cfr.resolveComponentFactory(DynamicComp);
- const ref = fixture.component.vcr.createComponent(dynamicCompFactory);
- fixture.update();
-
- fixture.component.vcr.detach(fixture.component.vcr.indexOf(ref.hostView));
-
- expect(() => {
- ref.destroy();
- }).not.toThrow();
- });
- });
- });
-
- describe('getters', () => {
- it('should work on elements', () => {
- function createTemplate() {
- ɵɵelement(0, 'header', 0);
- ɵɵelement(1, 'footer');
- }
-
- new TemplateFixture({
- create: createTemplate,
- decls: 2,
- directives: [DirectiveWithVCRef],
- consts: [['vcref', '']]
- });
-
- expect(directiveInstance!.vcref.element.nativeElement.tagName.toLowerCase())
- .toEqual('header');
- expect(
- directiveInstance!.vcref.injector.get(ElementRef).nativeElement.tagName.toLowerCase())
- .toEqual('header');
- expect(() => directiveInstance!.vcref.parentInjector.get(ElementRef)).toThrow();
- });
-
- it('should work on components', () => {
- const HeaderComponent =
- createComponent('header-cmp', function(rf: RenderFlags, ctx: any) {});
-
- function createTemplate() {
- ɵɵelement(0, 'header-cmp', 0);
- ɵɵelement(1, 'footer');
- }
-
- new TemplateFixture({
- create: createTemplate,
- decls: 2,
- directives: [HeaderComponent, DirectiveWithVCRef],
- consts: [['vcref', '']]
- });
-
- expect(directiveInstance!.vcref.element.nativeElement.tagName.toLowerCase())
- .toEqual('header-cmp');
- expect(
- directiveInstance!.vcref.injector.get(ElementRef).nativeElement.tagName.toLowerCase())
- .toEqual('header-cmp');
- expect(() => directiveInstance!.vcref.parentInjector.get(ElementRef)).toThrow();
- });
- });
- });
-
- describe('view engine compatibility', () => {
- @Component({selector: 'app', template: ''})
- class AppCmpt {
- static ɵfac = () =>
- new AppCmpt(ɵɵdirectiveInject(ViewContainerRef as any), injectComponentFactoryResolver())
-
- static ɵcmp = ɵɵdefineComponent({
- type: AppCmpt,
- selectors: [['app']],
- decls: 0,
- vars: 0,
- template: (rf: RenderFlags, cmp: AppCmpt) => {}
- });
-
- constructor(private _vcRef: ViewContainerRef, private _cfResolver: ComponentFactoryResolver) {
- }
-
- insert(comp: any) {
- this._vcRef.createComponent(this._cfResolver.resolveComponentFactory(comp));
- }
-
- clear() {
- this._vcRef.clear();
- }
-
- getVCRefParentInjector() {
- return this._vcRef.parentInjector;
- }
- }
-
- // https://stackblitz.com/edit/angular-xxpffd?file=src%2Findex.html
- it('should allow injecting VCRef into the root (bootstrapped) component', () => {
- const DynamicComponent =
- createComponent('dynamic-cmpt', function(rf: RenderFlags, parent: any) {
- if (rf & RenderFlags.Create) {
- ɵɵtext(0, 'inserted dynamically');
- }
- }, 1, 0);
-
-
- const fixture = new ComponentFixture(AppCmpt);
- expect(fixture.outerHtml).toBe('');
-
- fixture.component.insert(DynamicComponent);
- fixture.update();
- expect(fixture.outerHtml)
- .toBe('inserted dynamically');
-
- fixture.component.clear();
- fixture.update();
- expect(fixture.outerHtml).toBe('');
- });
-
- it('should allow getting the parentInjector of the VCRef which was injected into the root (bootstrapped) component',
- () => {
- const fixture = new ComponentFixture(AppCmpt, {
- injector: {
- get: (token: any) => {
- if (token === 'foo') return 'bar';
- }
- }
- });
- expect(fixture.outerHtml).toBe('');
-
- const parentInjector = fixture.component.getVCRefParentInjector();
- expect(parentInjector.get('foo')).toEqual('bar');
- });
-
- it('should support view queries for dynamically created components', () => {
- let dynamicComp!: DynamicCompWithViewQueries;
- let fooEl!: RElement;
-
- class DynamicCompWithViewQueries {
- // @ViewChildren('foo')
- foo!: QueryList;
-
- static ɵfac = () => dynamicComp = new DynamicCompWithViewQueries();
- static ɵcmp = ɵɵdefineComponent({
- type: DynamicCompWithViewQueries,
- selectors: [['dynamic-cmpt-with-view-queries']],
- decls: 2,
- vars: 0,
- consts: [['foo', ''], ['bar', '']],
- template:
- (rf: RenderFlags, ctx: DynamicCompWithViewQueries) => {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', 1, 0);
- }
- // testing only
- fooEl = getNativeByIndex(HEADER_OFFSET, getLView()) as RElement;
- },
- viewQuery:
- function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵviewQuery(['foo'], QueryFlags.descendants);
- }
- if (rf & RenderFlags.Update) {
- let tmp: any;
- ɵɵqueryRefresh(tmp = ɵɵloadQuery>()) &&
- (ctx.foo = tmp as QueryList);
- }
- }
- });
- }
-
- const fixture = new ComponentFixture(AppCmpt);
-
- fixture.component.insert(DynamicCompWithViewQueries);
- fixture.update();
-
- expect(dynamicComp.foo.first.nativeElement).toEqual(fooEl as any);
- });
- });
-
- describe('view destruction', () => {
- class CompWithListenerThatDestroysItself {
- constructor(private viewRef: ViewRef) {}
-
- onClick() {}
-
- ngOnDestroy() {
- this.viewRef.destroy();
- }
-
- // We want the ViewRef, so we rely on the knowledge that `ViewRef` is actually given
- // when injecting `ChangeDetectorRef`.
- static ɵfac = () =>
- new CompWithListenerThatDestroysItself(ɵɵdirectiveInject(ChangeDetectorRef as any))
-
- static ɵcmp = ɵɵdefineComponent({
- type: CompWithListenerThatDestroysItself,
- selectors: [['comp-with-listener-and-on-destroy']],
- decls: 2,
- vars: 0,
- /** */
- template:
- function CompTemplate(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'button');
- {
- ɵɵlistener('click', function() {
- return ctx.onClick();
- });
- ɵɵtext(1, 'Click me');
- }
- ɵɵelementEnd();
- }
- },
- });
- }
-
-
- it('should not error when destroying a view with listeners twice', () => {
- const CompWithChildListener = createComponent('test-app', (rf: RenderFlags, ctx: any) => {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'comp-with-listener-and-on-destroy');
- }
- }, 1, 0, [CompWithListenerThatDestroysItself]);
-
- const fixture = new ComponentFixture(CompWithChildListener);
- fixture.update();
-
- // Destroying the parent view will also destroy all of its children views and call their
- // onDestroy hooks. Here, our child view attempts to destroy itself *again* in its
- // onDestroy. This test exists to verify that no errors are thrown when doing this. We want
- // the test component to destroy its own view in onDestroy because the destroy hooks happen
- // as a *part of* view destruction. We also ensure that the test component has at least one
- // listener so that it runs the event listener cleanup code path.
- fixture.destroy();
- });
- });
-});