/** * @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 {CommonModule} from '@angular/common'; import {AfterViewInit, Component, ContentChild, ContentChildren, Directive, ElementRef, EventEmitter, forwardRef, InjectionToken, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, ViewRef} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {expect} from '@angular/platform-browser/testing/src/matchers'; import {onlyInIvy} from '@angular/private/testing'; describe('query logic', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [ AppComp, QueryComp, SimpleCompA, SimpleCompB, StaticViewQueryComp, TextDirective, SubclassStaticViewQueryComp, StaticContentQueryComp, SubclassStaticContentQueryComp, QueryCompWithChanges, StaticContentQueryDir, SuperDirectiveQueryTarget, SuperDirective, SubComponent, TestComponentWithToken, TestInjectionTokenContentQueries, TestInjectionTokenQueries, ] }); }); describe('view queries', () => { it('should return Component instances when Components are labeled and retrieved', () => { const template = `
`; const fixture = initWithTemplate(QueryComp, template); const comp = fixture.componentInstance; expect(comp.viewChild).toBeAnInstanceOf(SimpleCompA); expect(comp.viewChildren.first).toBeAnInstanceOf(SimpleCompA); expect(comp.viewChildren.last).toBeAnInstanceOf(SimpleCompB); }); it('should return ElementRef when HTML element is labeled and retrieved', () => { const template = `
`; const fixture = initWithTemplate(QueryComp, template); const comp = fixture.componentInstance; expect(comp.viewChild).toBeAnInstanceOf(ElementRef); expect(comp.viewChildren.first).toBeAnInstanceOf(ElementRef); }); onlyInIvy('multiple local refs are supported in Ivy') .it('should return ElementRefs when HTML elements are labeled and retrieved', () => { const template = `
A
B
`; const fixture = initWithTemplate(QueryComp, template); const comp = fixture.componentInstance; expect(comp.viewChild).toBeAnInstanceOf(ElementRef); expect(comp.viewChild.nativeElement).toBe(fixture.debugElement.children[0].nativeElement); expect(comp.viewChildren.first).toBeAnInstanceOf(ElementRef); expect(comp.viewChildren.last).toBeAnInstanceOf(ElementRef); expect(comp.viewChildren.length).toBe(2); }); it('should return TemplateRef when template is labeled and retrieved', () => { const template = ` `; const fixture = initWithTemplate(QueryComp, template); const comp = fixture.componentInstance; expect(comp.viewChildren.first).toBeAnInstanceOf(TemplateRef); }); it('should support selecting InjectionToken', () => { const fixture = TestBed.createComponent(TestInjectionTokenQueries); const instance = fixture.componentInstance; fixture.detectChanges(); expect(instance.viewFirstOption).toBeDefined(); expect(instance.viewFirstOption instanceof TestComponentWithToken).toBe(true); expect(instance.viewOptions).toBeDefined(); expect(instance.viewOptions.length).toBe(2); expect(instance.contentFirstOption).toBeUndefined(); expect(instance.contentOptions).toBeDefined(); expect(instance.contentOptions.length).toBe(0); }); onlyInIvy('multiple local refs are supported in Ivy') .it('should return TemplateRefs when templates are labeled and retrieved', () => { const template = ` `; const fixture = initWithTemplate(QueryComp, template); const comp = fixture.componentInstance; expect(comp.viewChild).toBeAnInstanceOf(TemplateRef); expect(comp.viewChild.elementRef.nativeElement) .toBe(fixture.debugElement.childNodes[0].nativeNode); expect(comp.viewChildren.first).toBeAnInstanceOf(TemplateRef); expect(comp.viewChildren.last).toBeAnInstanceOf(TemplateRef); expect(comp.viewChildren.length).toBe(2); }); it('should set static view child queries in creation mode (and just in creation mode)', () => { const fixture = TestBed.createComponent(StaticViewQueryComp); const component = fixture.componentInstance; // static ViewChild query should be set in creation mode, before CD runs expect(component.textDir).toBeAnInstanceOf(TextDirective); expect(component.textDir.text).toEqual(''); expect(component.setEvents).toEqual(['textDir set']); // dynamic ViewChild query should not have been resolved yet expect(component.foo).not.toBeDefined(); const span = fixture.nativeElement.querySelector('span'); fixture.detectChanges(); expect(component.textDir.text).toEqual('some text'); expect(component.foo.nativeElement).toBe(span); expect(component.setEvents).toEqual(['textDir set', 'foo set']); }); it('should support static view child queries inherited from superclasses', () => { const fixture = TestBed.createComponent(SubclassStaticViewQueryComp); const component = fixture.componentInstance; const divs = fixture.nativeElement.querySelectorAll('div'); const spans = fixture.nativeElement.querySelectorAll('span'); // static ViewChild queries should be set in creation mode, before CD runs expect(component.textDir).toBeAnInstanceOf(TextDirective); expect(component.textDir.text).toEqual(''); expect(component.bar.nativeElement).toEqual(divs[1]); // dynamic ViewChild queries should not have been resolved yet expect(component.foo).not.toBeDefined(); expect(component.baz).not.toBeDefined(); fixture.detectChanges(); expect(component.textDir.text).toEqual('some text'); expect(component.foo.nativeElement).toBe(spans[0]); expect(component.baz.nativeElement).toBe(spans[1]); }); it('should support multiple static view queries (multiple template passes)', () => { const template = ` `; TestBed.overrideComponent(AppComp, {set: new Component({template})}); const fixture = TestBed.createComponent(AppComp); const firstComponent = fixture.debugElement.children[0].injector.get(StaticViewQueryComp); const secondComponent = fixture.debugElement.children[1].injector.get(StaticViewQueryComp); // static ViewChild query should be set in creation mode, before CD runs expect(firstComponent.textDir).toBeAnInstanceOf(TextDirective); expect(secondComponent.textDir).toBeAnInstanceOf(TextDirective); expect(firstComponent.textDir.text).toEqual(''); expect(secondComponent.textDir.text).toEqual(''); expect(firstComponent.setEvents).toEqual(['textDir set']); expect(secondComponent.setEvents).toEqual(['textDir set']); // dynamic ViewChild query should not have been resolved yet expect(firstComponent.foo).not.toBeDefined(); expect(secondComponent.foo).not.toBeDefined(); const spans = fixture.nativeElement.querySelectorAll('span'); fixture.detectChanges(); expect(firstComponent.textDir.text).toEqual('some text'); expect(secondComponent.textDir.text).toEqual('some text'); expect(firstComponent.foo.nativeElement).toBe(spans[0]); expect(secondComponent.foo.nativeElement).toBe(spans[1]); expect(firstComponent.setEvents).toEqual(['textDir set', 'foo set']); expect(secondComponent.setEvents).toEqual(['textDir set', 'foo set']); }); it('should allow for view queries to be inherited from a directive', () => { const fixture = TestBed.createComponent(SubComponent); const comp = fixture.componentInstance; fixture.detectChanges(); expect(comp.headers).toBeTruthy(); expect(comp.headers.length).toBe(2); expect(comp.headers.toArray().every(result => result instanceof SuperDirectiveQueryTarget)) .toBe(true); }); it('should support ViewChild query inherited from undecorated superclasses', () => { class MyComp { @ViewChild('foo') foo: any; } @Component({selector: 'sub-comp', template: '
'}) class SubComp extends MyComp { } TestBed.configureTestingModule({declarations: [SubComp]}); const fixture = TestBed.createComponent(SubComp); fixture.detectChanges(); expect(fixture.componentInstance.foo).toBeAnInstanceOf(ElementRef); }); it('should support ViewChild query inherited from undecorated grand superclasses', () => { class MySuperComp { @ViewChild('foo') foo: any; } class MyComp extends MySuperComp {} @Component({selector: 'sub-comp', template: '
'}) class SubComp extends MyComp { } TestBed.configureTestingModule({declarations: [SubComp]}); const fixture = TestBed.createComponent(SubComp); fixture.detectChanges(); expect(fixture.componentInstance.foo).toBeAnInstanceOf(ElementRef); }); it('should support ViewChildren query inherited from undecorated superclasses', () => { @Directive({selector: '[some-dir]'}) class SomeDir { } class MyComp { @ViewChildren(SomeDir) foo!: QueryList; } @Component({ selector: 'sub-comp', template: `
` }) class SubComp extends MyComp { } TestBed.configureTestingModule({declarations: [SubComp, SomeDir]}); const fixture = TestBed.createComponent(SubComp); fixture.detectChanges(); expect(fixture.componentInstance.foo).toBeAnInstanceOf(QueryList); expect(fixture.componentInstance.foo.length).toBe(2); }); it('should support ViewChildren query inherited from undecorated grand superclasses', () => { @Directive({selector: '[some-dir]'}) class SomeDir { } class MySuperComp { @ViewChildren(SomeDir) foo!: QueryList; } class MyComp extends MySuperComp {} @Component({ selector: 'sub-comp', template: `
` }) class SubComp extends MyComp { } TestBed.configureTestingModule({declarations: [SubComp, SomeDir]}); const fixture = TestBed.createComponent(SubComp); fixture.detectChanges(); expect(fixture.componentInstance.foo).toBeAnInstanceOf(QueryList); expect(fixture.componentInstance.foo.length).toBe(2); }); it('should support ViewChild query where template is inserted in child component', () => { @Component({selector: 'required', template: ''}) class Required { } @Component({ selector: 'insertion', template: `` }) class Insertion { @Input() content!: TemplateRef<{}>; } @Component({ template: ` ` }) class App { @ViewChild(Required) requiredEl!: Required; viewChildAvailableInAfterViewInit?: boolean; ngAfterViewInit() { this.viewChildAvailableInAfterViewInit = this.requiredEl !== undefined; } } const fixture = TestBed.configureTestingModule({declarations: [App, Insertion, Required]}) .createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.viewChildAvailableInAfterViewInit).toBe(true); }); it('should destroy QueryList when the containing view is destroyed', () => { let queryInstance: QueryList; @Component({ selector: 'comp-with-view-query', template: '
Content
', }) class ComponentWithViewQuery { @ViewChildren('foo') set foo(value: any) { queryInstance = value; } get foo() { return queryInstance; } } @Component({ selector: 'root', template: ` ` }) class Root { condition = true; } TestBed.configureTestingModule({ declarations: [Root, ComponentWithViewQuery], imports: [CommonModule], }); const fixture = TestBed.createComponent(Root); fixture.detectChanges(); expect((queryInstance!.changes as EventEmitter).closed).toBeFalsy(); fixture.componentInstance.condition = false; fixture.detectChanges(); expect((queryInstance!.changes as EventEmitter).closed).toBeTruthy(); }); }); describe('content queries', () => { it('should return Component instance when Component is labeled and retrieved', () => { const template = ` `; const fixture = initWithTemplate(AppComp, template); const comp = fixture.debugElement.children[0].references['q']; expect(comp.contentChild).toBeAnInstanceOf(SimpleCompA); expect(comp.contentChildren.first).toBeAnInstanceOf(SimpleCompA); }); it('should support selecting InjectionToken', () => { const fixture = TestBed.createComponent(TestInjectionTokenContentQueries); const instance = fixture.debugElement.query(By.directive(TestInjectionTokenQueries)).componentInstance; fixture.detectChanges(); expect(instance.contentFirstOption).toBeDefined(); expect(instance.contentFirstOption instanceof TestComponentWithToken).toBe(true); expect(instance.contentOptions).toBeDefined(); expect(instance.contentOptions.length).toBe(2); }); onlyInIvy('multiple local refs are supported in Ivy') .it('should return Component instances when Components are labeled and retrieved', () => { const template = ` `; const fixture = initWithTemplate(AppComp, template); const comp = fixture.debugElement.children[0].references['q']; expect(comp.contentChild).toBeAnInstanceOf(SimpleCompA); expect(comp.contentChildren.first).toBeAnInstanceOf(SimpleCompA); expect(comp.contentChildren.last).toBeAnInstanceOf(SimpleCompB); expect(comp.contentChildren.length).toBe(2); }); it('should return ElementRef when HTML element is labeled and retrieved', () => { const template = `
`; const fixture = initWithTemplate(AppComp, template); const comp = fixture.debugElement.children[0].references['q']; expect(comp.contentChildren.first).toBeAnInstanceOf(ElementRef); }); onlyInIvy('multiple local refs are supported in Ivy') .it('should return ElementRefs when HTML elements are labeled and retrieved', () => { const template = `
`; const fixture = initWithTemplate(AppComp, template); const firstChild = fixture.debugElement.children[0]; const comp = firstChild.references['q']; expect(comp.contentChild).toBeAnInstanceOf(ElementRef); expect(comp.contentChild.nativeElement).toBe(firstChild.children[0].nativeElement); expect(comp.contentChildren.first).toBeAnInstanceOf(ElementRef); expect(comp.contentChildren.last).toBeAnInstanceOf(ElementRef); expect(comp.contentChildren.length).toBe(2); }); it('should return TemplateRef when template is labeled and retrieved', () => { const template = ` `; const fixture = initWithTemplate(AppComp, template); const comp = fixture.debugElement.children[0].references['q']; expect(comp.contentChildren.first).toBeAnInstanceOf(TemplateRef); }); onlyInIvy('multiple local refs are supported in Ivy') .it('should return TemplateRefs when templates are labeled and retrieved', () => { const template = ` `; const fixture = initWithTemplate(AppComp, template); const firstChild = fixture.debugElement.children[0]; const comp = firstChild.references['q']; expect(comp.contentChild).toBeAnInstanceOf(TemplateRef); expect(comp.contentChild.elementRef.nativeElement) .toBe(firstChild.childNodes[0].nativeNode); expect(comp.contentChildren.first).toBeAnInstanceOf(TemplateRef); expect(comp.contentChildren.last).toBeAnInstanceOf(TemplateRef); expect(comp.contentChildren.length).toBe(2); }); it('should set static content child queries in creation mode (and just in creation mode)', () => { const template = `
`; TestBed.overrideComponent(AppComp, {set: new Component({template})}); const fixture = TestBed.createComponent(AppComp); const component = fixture.debugElement.children[0].injector.get(StaticContentQueryComp); // static ContentChild query should be set in creation mode, before CD runs expect(component.textDir).toBeAnInstanceOf(TextDirective); expect(component.textDir.text).toEqual(''); expect(component.setEvents).toEqual(['textDir set']); // dynamic ContentChild query should not have been resolved yet expect(component.foo).not.toBeDefined(); const span = fixture.nativeElement.querySelector('span'); (fixture.componentInstance as any).text = 'some text'; fixture.detectChanges(); expect(component.textDir.text).toEqual('some text'); expect(component.foo.nativeElement).toBe(span); expect(component.setEvents).toEqual(['textDir set', 'foo set']); }); it('should support static content child queries inherited from superclasses', () => { const template = `
`; TestBed.overrideComponent(AppComp, {set: new Component({template})}); const fixture = TestBed.createComponent(AppComp); const component = fixture.debugElement.children[0].injector.get(SubclassStaticContentQueryComp); const divs = fixture.nativeElement.querySelectorAll('div'); const spans = fixture.nativeElement.querySelectorAll('span'); // static ContentChild queries should be set in creation mode, before CD runs expect(component.textDir).toBeAnInstanceOf(TextDirective); expect(component.textDir.text).toEqual(''); expect(component.bar.nativeElement).toEqual(divs[1]); // dynamic ContentChild queries should not have been resolved yet expect(component.foo).not.toBeDefined(); expect(component.baz).not.toBeDefined(); (fixture.componentInstance as any).text = 'some text'; fixture.detectChanges(); expect(component.textDir.text).toEqual('some text'); expect(component.foo.nativeElement).toBe(spans[0]); expect(component.baz.nativeElement).toBe(spans[1]); }); it('should set static content child queries on directives', () => { const template = `
`; TestBed.overrideComponent(AppComp, {set: new Component({template})}); const fixture = TestBed.createComponent(AppComp); const component = fixture.debugElement.children[0].injector.get(StaticContentQueryDir); // static ContentChild query should be set in creation mode, before CD runs expect(component.textDir).toBeAnInstanceOf(TextDirective); expect(component.textDir.text).toEqual(''); expect(component.setEvents).toEqual(['textDir set']); // dynamic ContentChild query should not have been resolved yet expect(component.foo).not.toBeDefined(); const span = fixture.nativeElement.querySelector('span'); (fixture.componentInstance as any).text = 'some text'; fixture.detectChanges(); expect(component.textDir.text).toEqual('some text'); expect(component.foo.nativeElement).toBe(span); expect(component.setEvents).toEqual(['textDir set', 'foo set']); }); it('should support multiple content query components (multiple template passes)', () => { const template = `
`; TestBed.overrideComponent(AppComp, {set: new Component({template})}); const fixture = TestBed.createComponent(AppComp); const firstComponent = fixture.debugElement.children[0].injector.get(StaticContentQueryComp); const secondComponent = fixture.debugElement.children[1].injector.get(StaticContentQueryComp); // static ContentChild query should be set in creation mode, before CD runs expect(firstComponent.textDir).toBeAnInstanceOf(TextDirective); expect(secondComponent.textDir).toBeAnInstanceOf(TextDirective); expect(firstComponent.textDir.text).toEqual(''); expect(secondComponent.textDir.text).toEqual(''); expect(firstComponent.setEvents).toEqual(['textDir set']); expect(secondComponent.setEvents).toEqual(['textDir set']); // dynamic ContentChild query should not have been resolved yet expect(firstComponent.foo).not.toBeDefined(); expect(secondComponent.foo).not.toBeDefined(); const spans = fixture.nativeElement.querySelectorAll('span'); (fixture.componentInstance as any).text = 'some text'; fixture.detectChanges(); expect(firstComponent.textDir.text).toEqual('some text'); expect(secondComponent.textDir.text).toEqual('some text'); expect(firstComponent.foo.nativeElement).toBe(spans[0]); expect(secondComponent.foo.nativeElement).toBe(spans[1]); expect(firstComponent.setEvents).toEqual(['textDir set', 'foo set']); expect(secondComponent.setEvents).toEqual(['textDir set', 'foo set']); }); it('should support ContentChild query inherited from undecorated superclasses', () => { class MyComp { @ContentChild('foo') foo: any; } @Component({selector: 'sub-comp', template: ''}) class SubComp extends MyComp { } @Component({template: '
'}) class App { @ViewChild(SubComp) subComp!: SubComp; } TestBed.configureTestingModule({declarations: [App, SubComp]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.subComp.foo).toBeAnInstanceOf(ElementRef); }); it('should support ContentChild query inherited from undecorated grand superclasses', () => { class MySuperComp { @ContentChild('foo') foo: any; } class MyComp extends MySuperComp {} @Component({selector: 'sub-comp', template: ''}) class SubComp extends MyComp { } @Component({template: '
'}) class App { @ViewChild(SubComp) subComp!: SubComp; } TestBed.configureTestingModule({declarations: [App, SubComp]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.subComp.foo).toBeAnInstanceOf(ElementRef); }); it('should support ContentChildren query inherited from undecorated superclasses', () => { @Directive({selector: '[some-dir]'}) class SomeDir { } class MyComp { @ContentChildren(SomeDir) foo!: QueryList; } @Component({selector: 'sub-comp', template: ''}) class SubComp extends MyComp { } @Component({ template: `
` }) class App { @ViewChild(SubComp) subComp!: SubComp; } TestBed.configureTestingModule({declarations: [App, SubComp, SomeDir]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.subComp.foo).toBeAnInstanceOf(QueryList); expect(fixture.componentInstance.subComp.foo.length).toBe(2); }); it('should support ContentChildren query inherited from undecorated grand superclasses', () => { @Directive({selector: '[some-dir]'}) class SomeDir { } class MySuperComp { @ContentChildren(SomeDir) foo!: QueryList; } class MyComp extends MySuperComp {} @Component({selector: 'sub-comp', template: ''}) class SubComp extends MyComp { } @Component({ template: `
` }) class App { @ViewChild(SubComp) subComp!: SubComp; } TestBed.configureTestingModule({declarations: [App, SubComp, SomeDir]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.subComp.foo).toBeAnInstanceOf(QueryList); expect(fixture.componentInstance.subComp.foo.length).toBe(2); }); it('should match shallow content queries in views inserted / removed by ngIf', () => { @Component({ selector: 'test-comp', template: `
` }) class TestComponent { showing = false; } @Component({ selector: 'shallow-comp', template: '', }) class ShallowComp { @ContentChildren('foo', {descendants: false}) foos!: QueryList; } TestBed.configureTestingModule( {declarations: [TestComponent, ShallowComp], imports: [CommonModule]}); const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); const shallowComp = fixture.debugElement.query(By.directive(ShallowComp)).componentInstance; const queryList = shallowComp!.foos; expect(queryList.length).toBe(0); fixture.componentInstance.showing = true; fixture.detectChanges(); expect(queryList.length).toBe(1); fixture.componentInstance.showing = false; fixture.detectChanges(); expect(queryList.length).toBe(0); }); it('should support content queries for directives within repeated embedded views', () => { const withContentInstances: DirWithContentQuery[] = []; @Directive({ selector: '[with-content]', }) class DirWithContentQuery { constructor() { withContentInstances.push(this); } @ContentChildren('foo', {descendants: false}) foos!: QueryList; contentInitQuerySnapshot = 0; contentCheckedQuerySnapshot = 0; ngAfterContentInit() { this.contentInitQuerySnapshot = this.foos ? this.foos.length : 0; } ngAfterContentChecked() { this.contentCheckedQuerySnapshot = this.foos ? this.foos.length : 0; } } @Component({ selector: 'comp', template: `
`, }) class Root { items = [1, 2, 3]; } TestBed.configureTestingModule({ declarations: [Root, DirWithContentQuery], imports: [CommonModule], }); const fixture = TestBed.createComponent(Root); fixture.detectChanges(); for (let i = 0; i < 3; i++) { expect(withContentInstances[i].foos.length) .toBe(1, `Expected content query to match .`); expect(withContentInstances[i].contentInitQuerySnapshot) .toBe( 1, `Expected content query results to be available when ngAfterContentInit was called.`); expect(withContentInstances[i].contentCheckedQuerySnapshot) .toBe( 1, `Expected content query results to be available when ngAfterContentChecked was called.`); } }); }); // Some root components may have ContentChildren queries if they are also // usable as a child component. We should still generate an empty QueryList // for these queries when they are at root for backwards compatibility with // ViewEngine. it('should generate an empty QueryList for root components', () => { const fixture = TestBed.createComponent(QueryComp); fixture.detectChanges(); expect(fixture.componentInstance.contentChildren).toBeAnInstanceOf(QueryList); expect(fixture.componentInstance.contentChildren.length).toBe(0); }); describe('descendants: false (default)', () => { /** * A helper function to check if a given object looks like ElementRef. It is used in place of * the `instanceof ElementRef` check since ivy returns a type that looks like ElementRef (have * the same properties but doesn't pass the instanceof ElementRef test) */ function isElementRefLike(result: any): boolean { return result.nativeElement != null; } it('should match directives on elements that used to be wrapped by a required parent in HTML parser', () => { @Directive({selector: '[myDef]'}) class MyDef { } @Component({selector: 'my-container', template: ``}) class MyContainer { @ContentChildren(MyDef) myDefs!: QueryList; } @Component( {selector: 'test-cmpt', template: ``}) class TestCmpt { } TestBed.configureTestingModule({declarations: [TestCmpt, MyContainer, MyDef]}); const fixture = TestBed.createComponent(TestCmpt); const cmptWithQuery = fixture.debugElement.children[0].injector.get(MyContainer); fixture.detectChanges(); expect(cmptWithQuery.myDefs.length).toBe(1); }); it('should match elements with local refs inside ', () => { @Component({selector: 'needs-target', template: ``}) class NeedsTarget { @ContentChildren('target') targets!: QueryList; } @Component({ selector: 'test-cmpt', template: ` `, }) class TestCmpt { } TestBed.configureTestingModule({declarations: [TestCmpt, NeedsTarget]}); const fixture = TestBed.createComponent(TestCmpt); const cmptWithQuery = fixture.debugElement.children[0].injector.get(NeedsTarget); fixture.detectChanges(); expect(cmptWithQuery.targets.length).toBe(1); expect(isElementRefLike(cmptWithQuery.targets.first)).toBeTruthy(); }); it('should match elements with local refs inside nested ', () => { @Component({selector: 'needs-target', template: ``}) class NeedsTarget { @ContentChildren('target') targets!: QueryList; } @Component({ selector: 'test-cmpt', template: ` `, }) class TestCmpt { } TestBed.configureTestingModule({declarations: [TestCmpt, NeedsTarget]}); const fixture = TestBed.createComponent(TestCmpt); const cmptWithQuery = fixture.debugElement.children[0].injector.get(NeedsTarget); fixture.detectChanges(); expect(cmptWithQuery.targets.length).toBe(1); expect(isElementRefLike(cmptWithQuery.targets.first)).toBeTruthy(); }); it('should match directives inside ', () => { @Directive({selector: '[targetDir]'}) class TargetDir { } @Component({selector: 'needs-target', template: ``}) class NeedsTarget { @ContentChildren(TargetDir) targets!: QueryList; } @Component({ selector: 'test-cmpt', template: ` `, }) class TestCmpt { } TestBed.configureTestingModule({declarations: [TestCmpt, NeedsTarget, TargetDir]}); const fixture = TestBed.createComponent(TestCmpt); const cmptWithQuery = fixture.debugElement.children[0].injector.get(NeedsTarget); fixture.detectChanges(); expect(cmptWithQuery.targets.length).toBe(1); expect(cmptWithQuery.targets.first).toBeAnInstanceOf(TargetDir); }); it('should match directives inside nested ', () => { @Directive({selector: '[targetDir]'}) class TargetDir { } @Component({selector: 'needs-target', template: ``}) class NeedsTarget { @ContentChildren(TargetDir) targets!: QueryList; } @Component({ selector: 'test-cmpt', template: ` `, }) class TestCmpt { } TestBed.configureTestingModule({declarations: [TestCmpt, NeedsTarget, TargetDir]}); const fixture = TestBed.createComponent(TestCmpt); const cmptWithQuery = fixture.debugElement.children[0].injector.get(NeedsTarget); fixture.detectChanges(); expect(cmptWithQuery.targets.length).toBe(1); expect(cmptWithQuery.targets.first).toBeAnInstanceOf(TargetDir); }); it('should cross child ng-container when query is declared on ng-container', () => { @Directive({selector: '[targetDir]'}) class TargetDir { } @Directive({selector: '[needs-target]'}) class NeedsTarget { @ContentChildren(TargetDir) targets!: QueryList; } @Component({ selector: 'test-cmpt', template: ` `, }) class TestCmpt { } TestBed.configureTestingModule({declarations: [TestCmpt, NeedsTarget, TargetDir]}); const fixture = TestBed.createComponent(TestCmpt); const cmptWithQuery = fixture.debugElement.children[0].injector.get(NeedsTarget); fixture.detectChanges(); expect(cmptWithQuery.targets.length).toBe(1); expect(cmptWithQuery.targets.first).toBeAnInstanceOf(TargetDir); }); it('should match nodes when using structural directives (*syntax) on ', () => { @Directive({selector: '[targetDir]'}) class TargetDir { } @Component({selector: 'needs-target', template: ``}) class NeedsTarget { @ContentChildren(TargetDir) dirTargets!: QueryList; @ContentChildren('target') localRefsTargets!: QueryList; } @Component({ selector: 'test-cmpt', template: `
`, }) class TestCmpt { } TestBed.configureTestingModule({declarations: [TestCmpt, NeedsTarget, TargetDir]}); const fixture = TestBed.createComponent(TestCmpt); const cmptWithQuery = fixture.debugElement.children[0].injector.get(NeedsTarget); fixture.detectChanges(); expect(cmptWithQuery.dirTargets.length).toBe(1); expect(cmptWithQuery.dirTargets.first).toBeAnInstanceOf(TargetDir); expect(cmptWithQuery.localRefsTargets.length).toBe(1); expect(isElementRefLike(cmptWithQuery.localRefsTargets.first)).toBeTruthy(); }); onlyInIvy( 'VE uses injectors hierarchy to determine if node matches, ivy uses elements as written in a template') .it('should match directives on when crossing nested ', () => { @Directive({selector: '[targetDir]'}) class TargetDir { } @Component({selector: 'needs-target', template: ``}) class NeedsTarget { @ContentChildren(TargetDir) targets!: QueryList; } @Component({ selector: 'test-cmpt', template: ` `, }) class TestCmpt { } TestBed.configureTestingModule({declarations: [TestCmpt, NeedsTarget, TargetDir]}); const fixture = TestBed.createComponent(TestCmpt); const cmptWithQuery = fixture.debugElement.children[0].injector.get(NeedsTarget); fixture.detectChanges(); expect(cmptWithQuery.targets.length).toBe(3); }); }); describe('observable interface', () => { it('should allow observing changes to query list', () => { const fixture = TestBed.createComponent(QueryCompWithChanges); let changes = 0; fixture.detectChanges(); fixture.componentInstance.foos.changes.subscribe((value: any) => { changes += 1; expect(value).toBe(fixture.componentInstance.foos); }); // refresh without setting dirty - no emit fixture.detectChanges(); expect(changes).toBe(0); // refresh with setting dirty - emit fixture.componentInstance.showing = true; fixture.detectChanges(); expect(changes).toBe(1); }); }); describe('view boundaries', () => { describe('ViewContainerRef', () => { @Directive({selector: '[vc]', exportAs: 'vc'}) class ViewContainerManipulatorDirective { constructor(private _vcRef: ViewContainerRef) {} insertTpl(tpl: TemplateRef<{}>, ctx: {}, idx?: number): ViewRef { return this._vcRef.createEmbeddedView(tpl, ctx, idx); } remove(index?: number) { this._vcRef.remove(index); } move(viewRef: ViewRef, index: number) { this._vcRef.move(viewRef, index); } } it('should report results in views inserted / removed by ngIf', () => { @Component({ selector: 'test-comp', template: `
` }) class TestComponent { value: boolean = false; @ViewChildren('foo') query!: QueryList; } TestBed.configureTestingModule({declarations: [TestComponent]}); const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); const queryList = fixture.componentInstance.query; expect(queryList.length).toBe(0); fixture.componentInstance.value = true; fixture.detectChanges(); expect(queryList.length).toBe(1); fixture.componentInstance.value = false; fixture.detectChanges(); expect(queryList.length).toBe(0); }); it('should report results in views inserted / removed by ngFor', () => { @Component({ selector: 'test-comp', template: `
`, }) class TestComponent { value: string[]|undefined; @ViewChildren('foo') query!: QueryList; } TestBed.configureTestingModule({declarations: [TestComponent]}); const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); const queryList = fixture.componentInstance.query; expect(queryList.length).toBe(0); fixture.componentInstance.value = ['a', 'b', 'c']; fixture.detectChanges(); expect(queryList.length).toBe(3); // Remove the "b" element from the value. fixture.componentInstance.value.splice(1, 1); fixture.detectChanges(); expect(queryList.length).toBe(2); // make sure that the "b" element has been removed from query results expect(queryList.first.nativeElement.id).toBe('a'); expect(queryList.last.nativeElement.id).toBe('c'); }); /** * ViewContainerRef API allows "moving" a view to the same (previous) index. Such operation * has no observable effect on the rendered UI (displays stays the same) but internally we've * got 2 implementation choices when it comes to "moving" a view: * - systematically detach and insert a view - this would result in unnecessary processing * when the previous and new indexes for the move operation are the same; * - detect the situation where the indexes are the same and do no processing in such case. * * This tests asserts on the implementation choices done by the VE (detach and insert) so we * can replicate the same behaviour in ivy. */ it('should notify on changes when a given view is removed and re-inserted at the same index', () => { @Component({ selector: 'test-comp', template: `
match
`, }) class TestComponent implements AfterViewInit { queryListNotificationCounter = 0; @ViewChild(ViewContainerManipulatorDirective) vc!: ViewContainerManipulatorDirective; @ViewChild('tpl') tpl!: TemplateRef; @ViewChildren('foo') query!: QueryList; ngAfterViewInit() { this.query.changes.subscribe(() => this.queryListNotificationCounter++); } } TestBed.configureTestingModule( {declarations: [ViewContainerManipulatorDirective, TestComponent]}); const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); const queryList = fixture.componentInstance.query; const {tpl, vc} = fixture.componentInstance; const viewRef = vc.insertTpl(tpl, {}, 0); fixture.detectChanges(); expect(queryList.length).toBe(1); expect(fixture.componentInstance.queryListNotificationCounter).toBe(1); vc.move(viewRef, 0); fixture.detectChanges(); expect(queryList.length).toBe(1); expect(fixture.componentInstance.queryListNotificationCounter).toBe(2); }); it('should support a mix of content queries from the declaration and embedded view', () => { @Directive({selector: '[query-for-lots-of-content]'}) class QueryForLotsOfContent { @ContentChildren('foo', {descendants: true}) foos1!: QueryList; @ContentChildren('foo', {descendants: true}) foos2!: QueryList; } @Directive({selector: '[query-for-content]'}) class QueryForContent { @ContentChildren('foo') foos!: QueryList; } @Component({ selector: 'test-comp', template: `
` }) class TestComponent { items = [1, 2]; } TestBed.configureTestingModule( {declarations: [TestComponent, QueryForContent, QueryForLotsOfContent]}); const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); const lotsOfContentEl = fixture.debugElement.query(By.directive(QueryForLotsOfContent)); const lotsOfContentInstance = lotsOfContentEl.injector.get(QueryForLotsOfContent); const contentEl = fixture.debugElement.query(By.directive(QueryForContent)); const contentInstance = contentEl.injector.get(QueryForContent); expect(lotsOfContentInstance.foos1.length).toBe(2); expect(lotsOfContentInstance.foos2.length).toBe(2); expect(contentInstance.foos.length).toBe(1); fixture.componentInstance.items = []; fixture.detectChanges(); expect(lotsOfContentInstance.foos1.length).toBe(0); expect(lotsOfContentInstance.foos2.length).toBe(0); }); // https://stackblitz.com/edit/angular-rrmmuf?file=src/app/app.component.ts it('should report results when different instances of TemplateRef are inserted into one ViewContainerRefs', () => { @Component({ selector: 'test-comp', template: `
`, }) class TestComponent { @ViewChild(ViewContainerManipulatorDirective) vc!: ViewContainerManipulatorDirective; @ViewChild('tpl1') tpl1!: TemplateRef; @ViewChild('tpl2') tpl2!: TemplateRef; @ViewChildren('foo') query!: QueryList; } TestBed.configureTestingModule( {declarations: [ViewContainerManipulatorDirective, TestComponent]}); const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); const queryList = fixture.componentInstance.query; const {tpl1, tpl2, vc} = fixture.componentInstance; expect(queryList.length).toBe(1); expect(queryList.first.nativeElement.getAttribute('id')).toBe('middle'); vc.insertTpl(tpl1!, {idx: 0}, 0); vc.insertTpl(tpl2!, {idx: 1}, 1); fixture.detectChanges(); expect(queryList.length).toBe(3); let qListArr = queryList.toArray(); expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0'); expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle'); expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1'); vc.insertTpl(tpl1!, {idx: 1}, 1); fixture.detectChanges(); expect(queryList.length).toBe(4); qListArr = queryList.toArray(); expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0'); expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo1_1'); expect(qListArr[2].nativeElement.getAttribute('id')).toBe('middle'); expect(qListArr[3].nativeElement.getAttribute('id')).toBe('foo2_1'); vc.remove(1); fixture.detectChanges(); expect(queryList.length).toBe(3); qListArr = queryList.toArray(); expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0'); expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle'); expect(qListArr[2].nativeElement.getAttribute('id')).toBe('foo2_1'); vc.remove(1); fixture.detectChanges(); expect(queryList.length).toBe(2); qListArr = queryList.toArray(); expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo1_0'); expect(qListArr[1].nativeElement.getAttribute('id')).toBe('middle'); }); // https://stackblitz.com/edit/angular-7vvo9j?file=src%2Fapp%2Fapp.component.ts // https://stackblitz.com/edit/angular-xzwp6n it('should report results when the same TemplateRef is inserted into different ViewContainerRefs', () => { @Component({ selector: 'test-comp', template: `
`, }) class TestComponent { @ViewChild('tpl') tpl!: TemplateRef; @ViewChild('vi0') vi0!: ViewContainerManipulatorDirective; @ViewChild('vi1') vi1!: ViewContainerManipulatorDirective; @ViewChildren('foo') query!: QueryList; } TestBed.configureTestingModule( {declarations: [ViewContainerManipulatorDirective, TestComponent]}); const fixture = TestBed.createComponent(TestComponent); fixture.detectChanges(); const queryList = fixture.componentInstance.query; const {tpl, vi0, vi1} = fixture.componentInstance; expect(queryList.length).toBe(0); vi0.insertTpl(tpl!, {idx: 0, container_idx: 0}, 0); vi1.insertTpl(tpl!, {idx: 0, container_idx: 1}, 0); fixture.detectChanges(); expect(queryList.length).toBe(2); let qListArr = queryList.toArray(); expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_0_0'); expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo_1_0'); vi0.remove(); fixture.detectChanges(); expect(queryList.length).toBe(1); qListArr = queryList.toArray(); expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0'); vi1.remove(); fixture.detectChanges(); expect(queryList.length).toBe(0); }); // https://stackblitz.com/edit/angular-wpd6gv?file=src%2Fapp%2Fapp.component.ts it('should report results from views inserted in a lifecycle hook', () => { @Component({ selector: 'my-app', template: ` `, }) class MyApp { show = false; @ViewChildren('foo') query!: QueryList; } TestBed.configureTestingModule({declarations: [MyApp], imports: [CommonModule]}); const fixture = TestBed.createComponent(MyApp); fixture.detectChanges(); const queryList = fixture.componentInstance.query; expect(queryList.length).toBe(0); fixture.componentInstance.show = true; fixture.detectChanges(); expect(queryList.length).toBe(1); expect(queryList.first.nativeElement.id).toBe('from_tpl'); fixture.componentInstance.show = false; fixture.detectChanges(); expect(queryList.length).toBe(0); }); }); }); describe('non-regression', () => { it('should query by provider super-type in an embedded view', () => { @Directive({selector: '[child]'}) class Child { } @Directive({selector: '[parent]', providers: [{provide: Child, useExisting: Parent}]}) class Parent extends Child { } @Component({ selector: 'test-cmpt', template: `
` }) class TestCmpt { @ViewChildren(Child) instances!: QueryList; } TestBed.configureTestingModule({declarations: [TestCmpt, Parent, Child]}); const fixture = TestBed.createComponent(TestCmpt); fixture.detectChanges(); expect(fixture.componentInstance.instances.length).toBe(1); }); it('should flatten multi-provider results', () => { class MyClass {} @Component({ selector: 'with-multi-provider', template: '', providers: [{provide: MyClass, useExisting: forwardRef(() => WithMultiProvider), multi: true}] }) class WithMultiProvider { } @Component({selector: 'test-cmpt', template: ``}) class TestCmpt { @ViewChildren(MyClass) queryResults!: QueryList; } TestBed.configureTestingModule({declarations: [TestCmpt, WithMultiProvider]}); const fixture = TestBed.createComponent(TestCmpt); fixture.detectChanges(); expect(fixture.componentInstance.queryResults.length).toBe(1); expect(fixture.componentInstance.queryResults.first).toBeAnInstanceOf(WithMultiProvider); }); it('should flatten multi-provider results when crossing ng-template', () => { class MyClass {} @Component({ selector: 'with-multi-provider', template: '', providers: [{provide: MyClass, useExisting: forwardRef(() => WithMultiProvider), multi: true}] }) class WithMultiProvider { } @Component({ selector: 'test-cmpt', template: ` ` }) class TestCmpt { @ViewChildren(MyClass) queryResults!: QueryList; } TestBed.configureTestingModule({declarations: [TestCmpt, WithMultiProvider]}); const fixture = TestBed.createComponent(TestCmpt); fixture.detectChanges(); expect(fixture.componentInstance.queryResults.length).toBe(2); expect(fixture.componentInstance.queryResults.first).toBeAnInstanceOf(WithMultiProvider); expect(fixture.componentInstance.queryResults.last).toBeAnInstanceOf(WithMultiProvider); }); it('should allow undefined provider value in a [View/Content]Child queries', () => { @Directive({selector: '[group]'}) class GroupDir { } @Directive( {selector: '[undefinedGroup]', providers: [{provide: GroupDir, useValue: undefined}]}) class UndefinedGroup { } @Component({ template: `
` }) class App { @ViewChild(GroupDir) group!: GroupDir; } TestBed.configureTestingModule( {declarations: [App, GroupDir, UndefinedGroup], imports: [CommonModule]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.group).toBeAnInstanceOf(GroupDir); }); it('should allow null / undefined provider value in a [View/Content]Children queries', () => { @Directive({selector: '[group]'}) class GroupDir { } @Directive({selector: '[nullGroup]', providers: [{provide: GroupDir, useValue: null}]}) class NullGroup { } @Directive( {selector: '[undefinedGroup]', providers: [{provide: GroupDir, useValue: undefined}]}) class UndefinedGroup { } @Component({ template: `
` }) class App { @ViewChildren(GroupDir) groups!: QueryList; } TestBed.configureTestingModule( {declarations: [App, GroupDir, NullGroup, UndefinedGroup], imports: [CommonModule]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); const queryList = fixture.componentInstance.groups; expect(queryList.length).toBe(3); const groups = queryList.toArray(); expect(groups[0]).toBeNull(); expect(groups[1]).toBeAnInstanceOf(GroupDir); expect(groups[2]).toBeUndefined(); }); }); describe('querying for string token providers', () => { @Directive({ selector: '[text-token]', providers: [{provide: 'Token', useExisting: TextTokenDirective}], }) class TextTokenDirective { } it('should match string injection token in a ViewChild query', () => { @Component({template: '
'}) class App { @ViewChild('Token') token: any; } TestBed.configureTestingModule({declarations: [App, TextTokenDirective]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.token).toBeAnInstanceOf(TextTokenDirective); }); it('should give precedence to local reference if both a reference and a string injection token provider match a ViewChild query', () => { @Component({template: '
'}) class App { @ViewChild('Token') token: any; } TestBed.configureTestingModule({declarations: [App, TextTokenDirective]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.token).toBeAnInstanceOf(ElementRef); }); it('should match string injection token in a ViewChildren query', () => { @Component({template: '
'}) class App { @ViewChildren('Token') tokens!: QueryList; } TestBed.configureTestingModule({declarations: [App, TextTokenDirective]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); const tokens = fixture.componentInstance.tokens; expect(tokens.length).toBe(1); expect(tokens.first).toBeAnInstanceOf(TextTokenDirective); }); it('should match both string injection token and local reference inside a ViewChildren query', () => { @Component({template: '
'}) class App { @ViewChildren('Token') tokens!: QueryList; } TestBed.configureTestingModule({declarations: [App, TextTokenDirective]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.tokens.toArray()).toEqual([ jasmine.any(ElementRef), jasmine.any(TextTokenDirective) ]); }); it('should match string injection token in a ContentChild query', () => { @Component({selector: 'has-query', template: ''}) class HasQuery { @ContentChild('Token') token: any; } @Component({template: '
'}) class App { @ViewChild(HasQuery) queryComp!: HasQuery; } TestBed.configureTestingModule({declarations: [App, HasQuery, TextTokenDirective]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.queryComp.token).toBeAnInstanceOf(TextTokenDirective); }); it('should give precedence to local reference if both a reference and a string injection token provider match a ContentChild query', () => { @Component({selector: 'has-query', template: ''}) class HasQuery { @ContentChild('Token') token: any; } @Component({template: '
'}) class App { @ViewChild(HasQuery) queryComp!: HasQuery; } TestBed.configureTestingModule({declarations: [App, HasQuery, TextTokenDirective]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.queryComp.token).toBeAnInstanceOf(ElementRef); }); it('should match string injection token in a ContentChildren query', () => { @Component({selector: 'has-query', template: ''}) class HasQuery { @ContentChildren('Token') tokens!: QueryList; } @Component({template: '
'}) class App { @ViewChild(HasQuery) queryComp!: HasQuery; } TestBed.configureTestingModule({declarations: [App, HasQuery, TextTokenDirective]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); const tokens = fixture.componentInstance.queryComp.tokens; expect(tokens.length).toBe(1); expect(tokens.first).toBeAnInstanceOf(TextTokenDirective); }); it('should match both string injection token and local reference inside a ContentChildren query', () => { @Component({selector: 'has-query', template: ''}) class HasQuery { @ContentChildren('Token') tokens!: QueryList; } @Component({template: '
'}) class App { @ViewChild(HasQuery) queryComp!: HasQuery; } TestBed.configureTestingModule({declarations: [App, HasQuery, TextTokenDirective]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.queryComp.tokens.toArray()).toEqual([ jasmine.any(ElementRef), jasmine.any(TextTokenDirective) ]); }); it('should match string token specified through the `read` option of a view query', () => { @Component({template: '
'}) class App { @ViewChild('Token', {read: 'Token'}) token: any; } TestBed.configureTestingModule({declarations: [App, TextTokenDirective]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.token).toBeAnInstanceOf(TextTokenDirective); }); it('should match string token specified through the `read` option of a content query', () => { @Component({selector: 'has-query', template: ''}) class HasQuery { @ContentChild('Token', {read: 'Token'}) token: any; } @Component({template: '
'}) class App { @ViewChild(HasQuery) queryComp!: HasQuery; } TestBed.configureTestingModule({declarations: [App, HasQuery, TextTokenDirective]}); const fixture = TestBed.createComponent(App); fixture.detectChanges(); expect(fixture.componentInstance.queryComp.token).toBeAnInstanceOf(TextTokenDirective); }); }); }); function initWithTemplate(compType: Type, template: string) { TestBed.overrideComponent(compType, {set: new Component({template})}); const fixture = TestBed.createComponent(compType); fixture.detectChanges(); return fixture; } @Component({selector: 'local-ref-query-component', template: ''}) class QueryComp { @ViewChild('viewQuery') viewChild!: any; @ContentChild('contentQuery') contentChild!: any; @ViewChildren('viewQuery') viewChildren!: QueryList; @ContentChildren('contentQuery') contentChildren!: QueryList; } @Component({selector: 'app-comp', template: ``}) class AppComp { } @Component({selector: 'simple-comp-a', template: ''}) class SimpleCompA { } @Component({selector: 'simple-comp-b', template: ''}) class SimpleCompB { } @Directive({selector: '[text]'}) class TextDirective { @Input() text = ''; } @Component({ selector: 'static-view-query-comp', template: `
` }) class StaticViewQueryComp { private _textDir!: TextDirective; private _foo!: ElementRef; setEvents: string[] = []; @ViewChild(TextDirective, {static: true}) get textDir(): TextDirective { return this._textDir; } set textDir(value: TextDirective) { this.setEvents.push('textDir set'); this._textDir = value; } @ViewChild('foo') get foo(): ElementRef { return this._foo; } set foo(value: ElementRef) { this.setEvents.push('foo set'); this._foo = value; } text = 'some text'; } @Component({ selector: 'subclass-static-view-query-comp', template: `
` }) class SubclassStaticViewQueryComp extends StaticViewQueryComp { @ViewChild('bar', {static: true}) bar!: ElementRef; @ViewChild('baz') baz!: ElementRef; } @Component({selector: 'static-content-query-comp', template: ``}) class StaticContentQueryComp { private _textDir!: TextDirective; private _foo!: ElementRef; setEvents: string[] = []; @ContentChild(TextDirective, {static: true}) get textDir(): TextDirective { return this._textDir; } set textDir(value: TextDirective) { this.setEvents.push('textDir set'); this._textDir = value; } @ContentChild('foo') get foo(): ElementRef { return this._foo; } set foo(value: ElementRef) { this.setEvents.push('foo set'); this._foo = value; } } @Directive({selector: '[staticContentQueryDir]'}) class StaticContentQueryDir { private _textDir!: TextDirective; private _foo!: ElementRef; setEvents: string[] = []; @ContentChild(TextDirective, {static: true}) get textDir(): TextDirective { return this._textDir; } set textDir(value: TextDirective) { this.setEvents.push('textDir set'); this._textDir = value; } @ContentChild('foo') get foo(): ElementRef { return this._foo; } set foo(value: ElementRef) { this.setEvents.push('foo set'); this._foo = value; } } @Component({selector: 'subclass-static-content-query-comp', template: ``}) class SubclassStaticContentQueryComp extends StaticContentQueryComp { @ContentChild('bar', {static: true}) bar!: ElementRef; @ContentChild('baz') baz!: ElementRef; } @Component({ selector: 'query-with-changes', template: `
` }) export class QueryCompWithChanges { @ViewChildren('foo') foos!: QueryList; showing = false; } @Component({selector: 'query-target', template: ''}) class SuperDirectiveQueryTarget { } @Directive({selector: '[super-directive]'}) class SuperDirective { @ViewChildren(SuperDirectiveQueryTarget) headers!: QueryList; } @Component({ template: ` One Two ` }) class SubComponent extends SuperDirective { } const MY_OPTION_TOKEN = new InjectionToken('ComponentWithToken'); @Component({ selector: 'my-option', template: 'Option', providers: [{provide: MY_OPTION_TOKEN, useExisting: TestComponentWithToken}], }) class TestComponentWithToken { } @Component({ selector: 'test-injection-token', template: ` ` }) class TestInjectionTokenQueries { @ViewChild(MY_OPTION_TOKEN) viewFirstOption!: TestComponentWithToken; @ViewChildren(MY_OPTION_TOKEN) viewOptions!: QueryList; @ContentChild(MY_OPTION_TOKEN) contentFirstOption!: TestComponentWithToken; @ContentChildren(MY_OPTION_TOKEN) contentOptions!: QueryList; } @Component({ template: ` ` }) class TestInjectionTokenContentQueries { }