angular-cn/packages/core/test/acceptance/query_spec.ts

2020 lines
70 KiB
TypeScript
Raw Normal View History

/**
* @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(() => {
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
TestBed.configureTestingModule({
declarations: [
AppComp,
QueryComp,
SimpleCompA,
SimpleCompB,
StaticViewQueryComp,
TextDirective,
SubclassStaticViewQueryComp,
StaticContentQueryComp,
SubclassStaticContentQueryComp,
QueryCompWithChanges,
StaticContentQueryDir,
SuperDirectiveQueryTarget,
SuperDirective,
SubComponent,
TestComponentWithToken,
TestInjectionTokenContentQueries,
TestInjectionTokenQueries,
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
]
});
});
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
describe('view queries', () => {
it('should return Component instances when Components are labeled and retrieved', () => {
const template = `
<div><simple-comp-a #viewQuery></simple-comp-a></div>
<div><simple-comp-b #viewQuery></simple-comp-b></div>
`;
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);
});
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
it('should return ElementRef when HTML element is labeled and retrieved', () => {
const template = `
<div #viewQuery></div>
`;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
const fixture = initWithTemplate(QueryComp, template);
const comp = fixture.componentInstance;
expect(comp.viewChild).toBeAnInstanceOf(ElementRef);
expect(comp.viewChildren.first).toBeAnInstanceOf(ElementRef);
});
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
onlyInIvy('multiple local refs are supported in Ivy')
.it('should return ElementRefs when HTML elements are labeled and retrieved', () => {
const template = `
<div #viewQuery #first>A</div>
<div #viewQuery #second>B</div>
`;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
const fixture = initWithTemplate(QueryComp, template);
const comp = fixture.componentInstance;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
expect(comp.viewChild).toBeAnInstanceOf(ElementRef);
expect(comp.viewChild.nativeElement).toBe(fixture.debugElement.children[0].nativeElement);
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
expect(comp.viewChildren.first).toBeAnInstanceOf(ElementRef);
expect(comp.viewChildren.last).toBeAnInstanceOf(ElementRef);
expect(comp.viewChildren.length).toBe(2);
});
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
it('should return TemplateRef when template is labeled and retrieved', () => {
const template = `
<ng-template #viewQuery></ng-template>
`;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
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);
});
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
onlyInIvy('multiple local refs are supported in Ivy')
.it('should return TemplateRefs when templates are labeled and retrieved', () => {
const template = `
<ng-template #viewQuery></ng-template>
<ng-template #viewQuery></ng-template>
`;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
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 = `
<static-view-query-comp></static-view-query-comp>
<static-view-query-comp></static-view-query-comp>
`;
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']);
});
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
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: '<div #foo></div>'})
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: '<div #foo></div>'})
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<SomeDir>;
}
@Component({
selector: 'sub-comp',
template: `
<div some-dir></div>
<div some-dir></div>
`
})
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<SomeDir>;
}
class MyComp extends MySuperComp {}
@Component({
selector: 'sub-comp',
template: `
<div some-dir></div>
<div some-dir></div>
`
})
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: `<ng-container [ngTemplateOutlet]="content"></ng-container>`
})
class Insertion {
@Input() content!: TemplateRef<{}>;
}
@Component({
template: `
<ng-template #template>
<required></required>
</ng-template>
<insertion [content]="template"></insertion>
`
})
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<any>;
@Component({
selector: 'comp-with-view-query',
template: '<div #foo>Content</div>',
})
class ComponentWithViewQuery {
@ViewChildren('foo')
set foo(value: any) {
queryInstance = value;
}
get foo() {
return queryInstance;
}
}
@Component({
selector: 'root',
template: `
<ng-container *ngIf="condition">
<comp-with-view-query></comp-with-view-query>
</ng-container>
`
})
class Root {
condition = true;
}
TestBed.configureTestingModule({
declarations: [Root, ComponentWithViewQuery],
imports: [CommonModule],
});
const fixture = TestBed.createComponent(Root);
fixture.detectChanges();
expect((queryInstance!.changes as EventEmitter<any>).closed).toBeFalsy();
fixture.componentInstance.condition = false;
fixture.detectChanges();
expect((queryInstance!.changes as EventEmitter<any>).closed).toBeTruthy();
});
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
});
describe('content queries', () => {
it('should return Component instance when Component is labeled and retrieved', () => {
const template = `
<local-ref-query-component #q>
<simple-comp-a #contentQuery></simple-comp-a>
</local-ref-query-component>
`;
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);
});
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
onlyInIvy('multiple local refs are supported in Ivy')
.it('should return Component instances when Components are labeled and retrieved', () => {
const template = `
<local-ref-query-component #q>
<simple-comp-a #contentQuery></simple-comp-a>
<simple-comp-b #contentQuery></simple-comp-b>
</local-ref-query-component>
`;
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 = `
<local-ref-query-component #q>
<div #contentQuery></div>
</local-ref-query-component>
`;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
const fixture = initWithTemplate(AppComp, template);
const comp = fixture.debugElement.children[0].references['q'];
expect(comp.contentChildren.first).toBeAnInstanceOf(ElementRef);
});
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
onlyInIvy('multiple local refs are supported in Ivy')
.it('should return ElementRefs when HTML elements are labeled and retrieved', () => {
const template = `
<local-ref-query-component #q>
<div #contentQuery></div>
<div #contentQuery></div>
</local-ref-query-component>
`;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
const fixture = initWithTemplate(AppComp, template);
const firstChild = fixture.debugElement.children[0];
const comp = firstChild.references['q'];
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
expect(comp.contentChild).toBeAnInstanceOf(ElementRef);
expect(comp.contentChild.nativeElement).toBe(firstChild.children[0].nativeElement);
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
expect(comp.contentChildren.first).toBeAnInstanceOf(ElementRef);
expect(comp.contentChildren.last).toBeAnInstanceOf(ElementRef);
expect(comp.contentChildren.length).toBe(2);
});
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
it('should return TemplateRef when template is labeled and retrieved', () => {
const template = `
<local-ref-query-component #q>
<ng-template #contentQuery></ng-template>
</local-ref-query-component>
`;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
const fixture = initWithTemplate(AppComp, template);
const comp = fixture.debugElement.children[0].references['q'];
expect(comp.contentChildren.first).toBeAnInstanceOf(TemplateRef);
});
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
onlyInIvy('multiple local refs are supported in Ivy')
.it('should return TemplateRefs when templates are labeled and retrieved', () => {
const template = `
<local-ref-query-component #q>
<ng-template #contentQuery></ng-template>
<ng-template #contentQuery></ng-template>
</local-ref-query-component>
`;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
const fixture = initWithTemplate(AppComp, template);
const firstChild = fixture.debugElement.children[0];
const comp = firstChild.references['q'];
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
expect(comp.contentChild).toBeAnInstanceOf(TemplateRef);
expect(comp.contentChild.elementRef.nativeElement)
.toBe(firstChild.childNodes[0].nativeNode);
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
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 = `
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
<static-content-query-comp>
<div [text]="text"></div>
<span #foo></span>
</static-content-query-comp>
`;
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 = `
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
<subclass-static-content-query-comp>
<div [text]="text"></div>
<span #foo></span>
<div #bar></div>
<span #baz></span>
</subclass-static-content-query-comp>
`;
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 = `
<div staticContentQueryDir>
<div [text]="text"></div>
<span #foo></span>
</div>
`;
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 = `
<static-content-query-comp>
<div [text]="text"></div>
<span #foo></span>
</static-content-query-comp>
<static-content-query-comp>
<div [text]="text"></div>
<span #foo></span>
</static-content-query-comp>
`;
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: '<ng-content></ng-content>'})
class SubComp extends MyComp {
}
@Component({template: '<sub-comp><div #foo></div></sub-comp>'})
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: '<ng-content></ng-content>'})
class SubComp extends MyComp {
}
@Component({template: '<sub-comp><div #foo></div></sub-comp>'})
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<SomeDir>;
}
@Component({selector: 'sub-comp', template: '<ng-content></ng-content>'})
class SubComp extends MyComp {
}
@Component({
template: `
<sub-comp>
<div some-dir></div>
<div some-dir></div>
</sub-comp>
`
})
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<SomeDir>;
}
class MyComp extends MySuperComp {}
@Component({selector: 'sub-comp', template: '<ng-content></ng-content>'})
class SubComp extends MyComp {
}
@Component({
template: `
<sub-comp>
<div some-dir></div>
<div some-dir></div>
</sub-comp>
`
})
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: `
<shallow-comp>
<div *ngIf="showing" #foo></div>
</shallow-comp>
`
})
class TestComponent {
showing = false;
}
@Component({
selector: 'shallow-comp',
template: '',
})
class ShallowComp {
@ContentChildren('foo', {descendants: false}) foos!: QueryList<ElementRef>;
}
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<ElementRef>;
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: `
<ng-container *ngFor="let item of items">
<div with-content>
<span #foo></span>
</div>
</ng-container>
`,
})
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 <span #foo>.`);
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.`);
}
});
});
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
// 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<MyDef>;
}
@Component(
{selector: 'test-cmpt', template: `<my-container><tr myDef></tr></my-container>`})
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 <ng-container>', () => {
@Component({selector: 'needs-target', template: ``})
class NeedsTarget {
@ContentChildren('target') targets!: QueryList<ElementRef>;
}
@Component({
selector: 'test-cmpt',
template: `
<needs-target>
<ng-container>
<tr #target></tr>
</ng-container>
</needs-target>
`,
})
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 <ng-container>', () => {
@Component({selector: 'needs-target', template: ``})
class NeedsTarget {
@ContentChildren('target') targets!: QueryList<ElementRef>;
}
@Component({
selector: 'test-cmpt',
template: `
<needs-target>
<ng-container>
<ng-container>
<ng-container>
<tr #target></tr>
</ng-container>
</ng-container>
</ng-container>
</needs-target>
`,
})
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 <ng-container>', () => {
@Directive({selector: '[targetDir]'})
class TargetDir {
}
@Component({selector: 'needs-target', template: ``})
class NeedsTarget {
@ContentChildren(TargetDir) targets!: QueryList<HTMLElement>;
}
@Component({
selector: 'test-cmpt',
template: `
<needs-target>
<ng-container>
<tr targetDir></tr>
</ng-container>
</needs-target>
`,
})
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 <ng-container>', () => {
@Directive({selector: '[targetDir]'})
class TargetDir {
}
@Component({selector: 'needs-target', template: ``})
class NeedsTarget {
@ContentChildren(TargetDir) targets!: QueryList<HTMLElement>;
}
@Component({
selector: 'test-cmpt',
template: `
<needs-target>
<ng-container>
<ng-container>
<ng-container>
<tr targetDir></tr>
</ng-container>
</ng-container>
</ng-container>
</needs-target>
`,
})
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<HTMLElement>;
}
@Component({
selector: 'test-cmpt',
template: `
<ng-container targetDir>
<ng-container needs-target>
<ng-container>
<tr targetDir></tr>
</ng-container>
</ng-container>
</ng-container>
`,
})
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 <ng-container>', () => {
@Directive({selector: '[targetDir]'})
class TargetDir {
}
@Component({selector: 'needs-target', template: ``})
class NeedsTarget {
@ContentChildren(TargetDir) dirTargets!: QueryList<TargetDir>;
@ContentChildren('target') localRefsTargets!: QueryList<ElementRef>;
}
@Component({
selector: 'test-cmpt',
template: `
<needs-target>
<ng-container *ngIf="true">
<div targetDir></div>
<div #target></div>
</ng-container>
</needs-target>
`,
})
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 <ng-container> when crossing nested <ng-container>', () => {
@Directive({selector: '[targetDir]'})
class TargetDir {
}
@Component({selector: 'needs-target', template: ``})
class NeedsTarget {
@ContentChildren(TargetDir) targets!: QueryList<HTMLElement>;
}
@Component({
selector: 'test-cmpt',
template: `
<needs-target>
<ng-container>
<ng-container targetDir>
<ng-container targetDir>
<tr targetDir></tr>
</ng-container>
</ng-container>
</ng-container>
</needs-target>
`,
})
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: `
<ng-template [ngIf]="value">
<div #foo></div>
</ng-template>
`
})
class TestComponent {
value: boolean = false;
@ViewChildren('foo') query!: QueryList<any>;
}
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: `
<ng-template ngFor let-item [ngForOf]="value">
<div #foo [id]="item"></div>
</ng-template>
`,
})
class TestComponent {
value: string[]|undefined;
@ViewChildren('foo') query!: QueryList<any>;
}
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: `
<ng-template #tpl><div #foo>match</div></ng-template>
<ng-template vc></ng-template>
`,
})
class TestComponent implements AfterViewInit {
queryListNotificationCounter = 0;
@ViewChild(ViewContainerManipulatorDirective) vc!: ViewContainerManipulatorDirective;
@ViewChild('tpl') tpl!: TemplateRef<any>;
@ViewChildren('foo') query!: QueryList<any>;
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<ElementRef>;
@ContentChildren('foo', {descendants: true}) foos2!: QueryList<ElementRef>;
}
@Directive({selector: '[query-for-content]'})
class QueryForContent {
@ContentChildren('foo') foos!: QueryList<ElementRef>;
}
@Component({
selector: 'test-comp',
template: `
<div query-for-lots-of-content>
<ng-template ngFor let-item [ngForOf]="items">
<div query-for-content>
<span #foo></span>
</div>
</ng-template>
</div>
`
})
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: `
<ng-template #tpl1 let-idx="idx">
<div #foo [id]="'foo1_' + idx"></div>
</ng-template>
<div #foo id="middle"></div>
<ng-template #tpl2 let-idx="idx">
<div #foo [id]="'foo2_' + idx"></div>
</ng-template>
<ng-template vc></ng-template>
`,
})
class TestComponent {
@ViewChild(ViewContainerManipulatorDirective) vc!: ViewContainerManipulatorDirective;
@ViewChild('tpl1') tpl1!: TemplateRef<any>;
@ViewChild('tpl2') tpl2!: TemplateRef<any>;
@ViewChildren('foo') query!: QueryList<any>;
}
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: `
<ng-template #tpl let-idx="idx" let-container_idx="container_idx">
<div #foo [id]="'foo_' + container_idx + '_' + idx"></div>
</ng-template>
<ng-template vc #vi0="vc"></ng-template>
<ng-template vc #vi1="vc"></ng-template>
`,
})
class TestComponent {
@ViewChild('tpl') tpl!: TemplateRef<any>;
@ViewChild('vi0') vi0!: ViewContainerManipulatorDirective;
@ViewChild('vi1') vi1!: ViewContainerManipulatorDirective;
@ViewChildren('foo') query!: QueryList<any>;
}
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: `
<ng-template #tpl>
<span #foo id="from_tpl"></span>
</ng-template>
<ng-template [ngTemplateOutlet]="show ? tpl : null"></ng-template>
`,
})
class MyApp {
show = false;
@ViewChildren('foo') query!: QueryList<any>;
}
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:
`<ng-template [ngIf]="true"><ng-template [ngIf]="true"><div parent></div></ng-template></ng-template>`
})
class TestCmpt {
@ViewChildren(Child) instances!: QueryList<Child>;
}
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: `<with-multi-provider></with-multi-provider>`})
class TestCmpt {
@ViewChildren(MyClass) queryResults!: QueryList<WithMultiProvider>;
}
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: `
<ng-template [ngIf]="true"><with-multi-provider></with-multi-provider></ng-template>
<with-multi-provider></with-multi-provider>
`
})
class TestCmpt {
@ViewChildren(MyClass) queryResults!: QueryList<WithMultiProvider>;
}
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: `
<div group></div>
<ng-template [ngIf]="true">
<div undefinedGroup></div>
</ng-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: `
<ng-template [ngIf]="true">
<div nullGroup></div>
</ng-template>
<div group></div>
<ng-template [ngIf]="true">
<div undefinedGroup></div>
</ng-template>
`
})
class App {
@ViewChildren(GroupDir) groups!: QueryList<GroupDir>;
}
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: '<div text-token></div>'})
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: '<div text-token #Token></div>'})
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: '<div text-token></div>'})
class App {
@ViewChildren('Token') tokens!: QueryList<any>;
}
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: '<div text-token #Token></div>'})
class App {
@ViewChildren('Token') tokens!: QueryList<any>;
}
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: '<ng-content></ng-content>'})
class HasQuery {
@ContentChild('Token') token: any;
}
@Component({template: '<has-query><div text-token></div></has-query>'})
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: '<ng-content></ng-content>'})
class HasQuery {
@ContentChild('Token') token: any;
}
@Component({template: '<has-query><div text-token #Token></div></has-query>'})
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: '<ng-content></ng-content>'})
class HasQuery {
@ContentChildren('Token') tokens!: QueryList<any>;
}
@Component({template: '<has-query><div text-token></div></has-query>'})
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: '<ng-content></ng-content>'})
class HasQuery {
@ContentChildren('Token') tokens!: QueryList<any>;
}
@Component({template: '<has-query><div text-token #Token></div></has-query>'})
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: '<div text-token #Token></div>'})
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: '<ng-content></ng-content>'})
class HasQuery {
@ContentChild('Token', {read: 'Token'}) token: any;
}
@Component({template: '<has-query><div text-token #Token></div></has-query>'})
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<any>, 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: '<ng-content></ng-content>'})
class QueryComp {
@ViewChild('viewQuery') viewChild!: any;
@ContentChild('contentQuery') contentChild!: any;
@ViewChildren('viewQuery') viewChildren!: QueryList<any>;
@ContentChildren('contentQuery') contentChildren!: QueryList<any>;
}
@Component({selector: 'app-comp', template: ``})
class AppComp {
}
@Component({selector: 'simple-comp-a', template: ''})
class SimpleCompA {
}
@Component({selector: 'simple-comp-b', template: ''})
class SimpleCompB {
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
}
@Directive({selector: '[text]'})
class TextDirective {
@Input() text = '';
}
@Component({
selector: 'static-view-query-comp',
template: `
<div [text]="text"></div>
<span #foo></span>
`
})
class StaticViewQueryComp {
private _textDir!: TextDirective;
private _foo!: ElementRef;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
setEvents: string[] = [];
@ViewChild(TextDirective, {static: true})
get textDir(): TextDirective {
return this._textDir;
}
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
set textDir(value: TextDirective) {
this.setEvents.push('textDir set');
this._textDir = value;
}
@ViewChild('foo')
get foo(): ElementRef {
return this._foo;
}
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
set foo(value: ElementRef) {
this.setEvents.push('foo set');
this._foo = value;
}
text = 'some text';
}
@Component({
selector: 'subclass-static-view-query-comp',
template: `
<div [text]="text"></div>
<span #foo></span>
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
<div #bar></div>
<span #baz></span>
`
})
class SubclassStaticViewQueryComp extends StaticViewQueryComp {
@ViewChild('bar', {static: true}) bar!: ElementRef;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
@ViewChild('baz') baz!: ElementRef;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
}
@Component({selector: 'static-content-query-comp', template: `<ng-content></ng-content>`})
class StaticContentQueryComp {
private _textDir!: TextDirective;
private _foo!: ElementRef;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
setEvents: string[] = [];
@ContentChild(TextDirective, {static: true})
get textDir(): TextDirective {
return this._textDir;
}
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
set textDir(value: TextDirective) {
this.setEvents.push('textDir set');
this._textDir = value;
}
@ContentChild('foo')
get foo(): ElementRef {
return this._foo;
}
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
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;
}
}
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
@Component({selector: 'subclass-static-content-query-comp', template: `<ng-content></ng-content>`})
class SubclassStaticContentQueryComp extends StaticContentQueryComp {
@ContentChild('bar', {static: true}) bar!: ElementRef;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
@ContentChild('baz') baz!: ElementRef;
feat(core): allow users to define timing of ViewChild/ContentChild queries (#28810) Prior to this commit, the timing of `ViewChild`/`ContentChild` query resolution depended on the results of each query. If any results for a particular query were nested inside embedded views (e.g. *ngIfs), that query would be resolved after change detection ran. Otherwise, the query would be resolved as soon as nodes were created. This inconsistency in resolution timing had the potential to cause confusion because query results would sometimes be available in ngOnInit, but sometimes wouldn't be available until ngAfterContentInit or ngAfterViewInit. Code depending on a query result could suddenly stop working as soon as an *ngIf or an *ngFor was added to the template. With this commit, users can dictate when they want a particular `ViewChild` or `ContentChild` query to be resolved with the `static` flag. For example, one can mark a particular query as `static: false` to ensure change detection always runs before its results are set: ```ts @ContentChild('foo', {static: false}) foo !: ElementRef; ``` This means that even if there isn't a query result wrapped in an *ngIf or an *ngFor now, adding one to the template later won't change the timing of the query resolution and potentially break your component. Similarly, if you know that your query needs to be resolved earlier (e.g. you need results in an ngOnInit hook), you can mark it as `static: true`. ```ts @ViewChild(TemplateRef, {static: true}) foo !: TemplateRef; ``` Note: this means that your component will not support *ngIf results. If you do not supply a `static` option when creating your `ViewChild` or `ContentChild` query, the default query resolution timing will kick in. Note: This new option only applies to `ViewChild` and `ContentChild` queries, not `ViewChildren` or `ContentChildren` queries, as those types already resolve after CD runs. PR Close #28810
2019-02-18 17:38:14 -05:00
}
@Component({
selector: 'query-with-changes',
template: `
<div *ngIf="showing" #foo></div>
`
})
export class QueryCompWithChanges {
@ViewChildren('foo') foos!: QueryList<any>;
showing = false;
}
@Component({selector: 'query-target', template: '<ng-content></ng-content>'})
class SuperDirectiveQueryTarget {
}
@Directive({selector: '[super-directive]'})
class SuperDirective {
@ViewChildren(SuperDirectiveQueryTarget) headers!: QueryList<SuperDirectiveQueryTarget>;
}
@Component({
template: `
<query-target>One</query-target>
<query-target>Two</query-target>
`
})
class SubComponent extends SuperDirective {
}
const MY_OPTION_TOKEN = new InjectionToken<TestComponentWithToken>('ComponentWithToken');
@Component({
selector: 'my-option',
template: 'Option',
providers: [{provide: MY_OPTION_TOKEN, useExisting: TestComponentWithToken}],
})
class TestComponentWithToken {
}
@Component({
selector: 'test-injection-token',
template: `
<my-option></my-option>
<my-option></my-option>
<ng-content></ng-content>
`
})
class TestInjectionTokenQueries {
@ViewChild(MY_OPTION_TOKEN) viewFirstOption!: TestComponentWithToken;
@ViewChildren(MY_OPTION_TOKEN) viewOptions!: QueryList<TestComponentWithToken>;
@ContentChild(MY_OPTION_TOKEN) contentFirstOption!: TestComponentWithToken;
@ContentChildren(MY_OPTION_TOKEN) contentOptions!: QueryList<TestComponentWithToken>;
}
@Component({
template: `
<test-injection-token>
<my-option></my-option>
<my-option></my-option>
</test-injection-token>
`
})
class TestInjectionTokenContentQueries {
}