diff --git a/packages/compiler/src/compile_metadata.ts b/packages/compiler/src/compile_metadata.ts index 44f8194448..7d8b3be1f6 100644 --- a/packages/compiler/src/compile_metadata.ts +++ b/packages/compiler/src/compile_metadata.ts @@ -164,6 +164,7 @@ export interface CompileQueryMetadata { first: boolean; propertyName: string; read: CompileTokenMetadata; + static?: boolean; } /** diff --git a/packages/compiler/src/core.ts b/packages/compiler/src/core.ts index 2b3081f46e..b83020803b 100644 --- a/packages/compiler/src/core.ts +++ b/packages/compiler/src/core.ts @@ -29,6 +29,7 @@ export interface Query { read: any; isViewQuery: boolean; selector: any; + static?: boolean; } export const createContentChildren = makeMetadataFactory( diff --git a/packages/compiler/src/metadata_resolver.ts b/packages/compiler/src/metadata_resolver.ts index af1c0f1a9d..fb58976059 100644 --- a/packages/compiler/src/metadata_resolver.ts +++ b/packages/compiler/src/metadata_resolver.ts @@ -1157,7 +1157,8 @@ export class CompileMetadataResolver { selectors, first: q.first, descendants: q.descendants, propertyName, - read: q.read ? this._getTokenMetadata(q.read) : null ! + read: q.read ? this._getTokenMetadata(q.read) : null !, + static: q.static }; } diff --git a/packages/compiler/src/view_compiler/view_compiler.ts b/packages/compiler/src/view_compiler/view_compiler.ts index 3fbe01806f..5fe530bb04 100644 --- a/packages/compiler/src/view_compiler/view_compiler.ts +++ b/packages/compiler/src/view_compiler/view_compiler.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompileDirectiveMetadata, CompilePipeSummary, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata'; +import {CompileDirectiveMetadata, CompilePipeSummary, CompileQueryMetadata, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata'; import {CompileReflector} from '../compile_reflector'; import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter'; import {ArgumentType, BindingFlags, ChangeDetectionStrategy, NodeFlags, QueryBindingType, QueryValueType, ViewFlags} from '../core'; @@ -145,7 +145,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver { const queryId = queryIndex + 1; const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All; const flags = - NodeFlags.TypeViewQuery | calcStaticDynamicQueryFlags(queryIds, queryId, query.first); + NodeFlags.TypeViewQuery | calcStaticDynamicQueryFlags(queryIds, queryId, query); this.nodes.push(() => ({ sourceSpan: null, nodeFlags: flags, @@ -493,7 +493,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver { dirAst.directive.queries.forEach((query, queryIndex) => { const queryId = dirAst.contentQueryStartId + queryIndex; const flags = - NodeFlags.TypeContentQuery | calcStaticDynamicQueryFlags(queryIds, queryId, query.first); + NodeFlags.TypeContentQuery | calcStaticDynamicQueryFlags(queryIds, queryId, query); const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All; this.nodes.push(() => ({ sourceSpan: dirAst.sourceSpan, @@ -1081,11 +1081,11 @@ function elementEventNameAndTarget( } function calcStaticDynamicQueryFlags( - queryIds: StaticAndDynamicQueryIds, queryId: number, isFirst: boolean) { + queryIds: StaticAndDynamicQueryIds, queryId: number, query: CompileQueryMetadata) { let flags = NodeFlags.None; // Note: We only make queries static that query for a single item. // This is because of backwards compatibility with the old view compiler... - if (isFirst && (queryIds.staticQueryIds.has(queryId) || !queryIds.dynamicQueryIds.has(queryId))) { + if (query.first && shouldResolveAsStaticQuery(queryIds, queryId, query)) { flags |= NodeFlags.StaticQuery; } else { flags |= NodeFlags.DynamicQuery; @@ -1093,6 +1093,16 @@ function calcStaticDynamicQueryFlags( return flags; } +function shouldResolveAsStaticQuery( + queryIds: StaticAndDynamicQueryIds, queryId: number, query: CompileQueryMetadata): boolean { + // If query.static has been set by the user, use that value to determine whether + // the query is static. If none has been set, sort the query into static/dynamic + // based on query results (i.e. dynamic if CD needs to run to get all results). + return query.static || + query.static == null && + (queryIds.staticQueryIds.has(queryId) || !queryIds.dynamicQueryIds.has(queryId)); +} + export function elementEventFullName(target: string | null, name: string): string { return target ? `${target}:${name}` : name; } diff --git a/packages/core/src/metadata/di.ts b/packages/core/src/metadata/di.ts index 867259c416..1267296c1f 100644 --- a/packages/core/src/metadata/di.ts +++ b/packages/core/src/metadata/di.ts @@ -102,6 +102,7 @@ export interface Query { read: any; isViewQuery: boolean; selector: any; + static?: boolean; } /** @@ -199,6 +200,12 @@ export interface ContentChildDecorator { * * * **selector** - the directive type or the name used for querying. * * **read** - read a different token from the queried element. + * * **static** - whether or not to resolve query results before change detection runs (i.e. + * return static results only). If this option is not provided, the compiler will fall back + * to its default behavior, which is to use query results to determine the timing of query + * resolution. If any query results are inside a nested view (e.g. *ngIf), the query will be + * resolved after change detection runs. Otherwise, it will be resolved before change detection + * runs. * * @usageNotes * ### Example @@ -211,8 +218,8 @@ export interface ContentChildDecorator { * * @Annotation */ - (selector: Type|Function|string, opts?: {read?: any}): any; - new (selector: Type|Function|string, opts?: {read?: any}): ContentChild; + (selector: Type|Function|string, opts?: {read?: any, static?: boolean}): any; + new (selector: Type|Function|string, opts?: {read?: any, static?: boolean}): ContentChild; } /** @@ -311,6 +318,12 @@ export interface ViewChildDecorator { * * * **selector** - the directive type or the name used for querying. * * **read** - read a different token from the queried elements. + * * **static** - whether or not to resolve query results before change detection runs (i.e. + * return static results only). If this option is not provided, the compiler will fall back + * to its default behavior, which is to use query results to determine the timing of query + * resolution. If any query results are inside a nested view (e.g. *ngIf), the query will be + * resolved after change detection runs. Otherwise, it will be resolved before change detection + * runs. * * Supported selectors include: * * any class with the `@Component` or `@Directive` decorator @@ -337,8 +350,8 @@ export interface ViewChildDecorator { * * @Annotation */ - (selector: Type|Function|string, opts?: {read?: any}): any; - new (selector: Type|Function|string, opts?: {read?: any}): ViewChild; + (selector: Type|Function|string, opts?: {read?: any, static?: boolean}): any; + new (selector: Type|Function|string, opts?: {read?: any, static?: boolean}): ViewChild; } /** diff --git a/packages/core/test/acceptance/query_spec.ts b/packages/core/test/acceptance/query_spec.ts index 80775e1c1a..162948c9ca 100644 --- a/packages/core/test/acceptance/query_spec.ts +++ b/packages/core/test/acceptance/query_spec.ts @@ -6,181 +6,290 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, ContentChild, ContentChildren, ElementRef, QueryList, TemplateRef, Type, ViewChild, ViewChildren} from '@angular/core'; +import {Component, ContentChild, ContentChildren, Directive, ElementRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; -import {onlyInIvy} from '@angular/private/testing'; - +import {fixmeIvy, onlyInIvy} from '@angular/private/testing'; describe('query logic', () => { beforeEach(() => { - TestBed.configureTestingModule({declarations: [AppComp, QueryComp, SimpleCompA, SimpleCompB]}); + TestBed.configureTestingModule({ + declarations: [ + AppComp, QueryComp, SimpleCompA, SimpleCompB, StaticViewQueryComp, TextDirective, + SubclassStaticViewQueryComp, StaticContentQueryComp, SubclassStaticContentQueryComp + ] + }); }); - it('should return Component instances when Components are labelled and retrieved via View query', - () => { - const template = ` -
-
- `; - const fixture = initWithTemplate(QueryComp, template); - const comp = fixture.componentInstance; - expect(comp.viewChild).toBeAnInstanceOf(SimpleCompA); - expect(comp.viewChildren.first).toBeAnInstanceOf(SimpleCompA); - expect(comp.viewChildren.last).toBeAnInstanceOf(SimpleCompB); - }); + describe('view queries', () => { + it('should return Component instances when Components are labeled and retrieved', () => { + const template = ` +
+
+ `; + const fixture = initWithTemplate(QueryComp, template); + const comp = fixture.componentInstance; + expect(comp.viewChild).toBeAnInstanceOf(SimpleCompA); + expect(comp.viewChildren.first).toBeAnInstanceOf(SimpleCompA); + expect(comp.viewChildren.last).toBeAnInstanceOf(SimpleCompB); + }); - it('should return Component instance when Component is labelled and retrieved via Content query', - () => { - const template = ` - - - - `; - const fixture = initWithTemplate(AppComp, template); - const comp = fixture.debugElement.children[0].references['q']; - expect(comp.contentChild).toBeAnInstanceOf(SimpleCompA); - expect(comp.contentChildren.first).toBeAnInstanceOf(SimpleCompA); - }); - - onlyInIvy('multiple local refs are supported in Ivy') - .it('should return Component instances when Components are labelled and retrieved via Content query', - () => { - const template = ` - - - - - `; - const fixture = initWithTemplate(AppComp, template); - const comp = fixture.debugElement.children[0].references['q']; - expect(comp.contentChild).toBeAnInstanceOf(SimpleCompA); - expect(comp.contentChildren.first).toBeAnInstanceOf(SimpleCompA); - expect(comp.contentChildren.last).toBeAnInstanceOf(SimpleCompB); - expect(comp.contentChildren.length).toBe(2); - }); - - it('should return ElementRef when HTML element is labelled and retrieved via View query', () => { - const template = ` + it('should return ElementRef when HTML element is labeled and retrieved', () => { + const template = `
`; - const fixture = initWithTemplate(QueryComp, template); - const comp = fixture.componentInstance; - expect(comp.viewChild).toBeAnInstanceOf(ElementRef); - expect(comp.viewChildren.first).toBeAnInstanceOf(ElementRef); - }); + const fixture = initWithTemplate(QueryComp, template); + const comp = fixture.componentInstance; + expect(comp.viewChild).toBeAnInstanceOf(ElementRef); + expect(comp.viewChildren.first).toBeAnInstanceOf(ElementRef); + }); - onlyInIvy('multiple local refs are supported in Ivy') - .it('should return ElementRefs when HTML elements are labelled and retrieved via View query', - () => { - const template = ` + onlyInIvy('multiple local refs are supported in Ivy') + .it('should return ElementRefs when HTML elements are labeled and retrieved', () => { + const template = `
A
B
`; - const fixture = initWithTemplate(QueryComp, template); - const comp = fixture.componentInstance; + const fixture = initWithTemplate(QueryComp, template); + const comp = fixture.componentInstance; - expect(comp.viewChild).toBeAnInstanceOf(ElementRef); - expect(comp.viewChild.nativeElement) - .toBe(fixture.debugElement.children[0].nativeElement); + expect(comp.viewChild).toBeAnInstanceOf(ElementRef); + expect(comp.viewChild.nativeElement).toBe(fixture.debugElement.children[0].nativeElement); - expect(comp.viewChildren.first).toBeAnInstanceOf(ElementRef); - expect(comp.viewChildren.last).toBeAnInstanceOf(ElementRef); - expect(comp.viewChildren.length).toBe(2); - }); + expect(comp.viewChildren.first).toBeAnInstanceOf(ElementRef); + expect(comp.viewChildren.last).toBeAnInstanceOf(ElementRef); + expect(comp.viewChildren.length).toBe(2); + }); - it('should return TemplateRef when template is labelled and retrieved via View query', () => { - const template = ` + it('should return TemplateRef when template is labeled and retrieved', () => { + const template = ` `; - const fixture = initWithTemplate(QueryComp, template); - const comp = fixture.componentInstance; - expect(comp.viewChildren.first).toBeAnInstanceOf(TemplateRef); - }); + const fixture = initWithTemplate(QueryComp, template); + const comp = fixture.componentInstance; + expect(comp.viewChildren.first).toBeAnInstanceOf(TemplateRef); + }); - onlyInIvy('multiple local refs are supported in Ivy') - .it('should return TemplateRefs when templates are labelled and retrieved via View query', - () => { - const template = ` + onlyInIvy('multiple local refs are supported in Ivy') + .it('should return TemplateRefs when templates are labeled and retrieved', () => { + const template = ` `; - const fixture = initWithTemplate(QueryComp, template); - const comp = fixture.componentInstance; - expect(comp.viewChild).toBeAnInstanceOf(TemplateRef); - expect(comp.viewChild.elementRef.nativeElement) - .toBe(fixture.debugElement.childNodes[0].nativeNode); + 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); - }); + expect(comp.viewChildren.first).toBeAnInstanceOf(TemplateRef); + expect(comp.viewChildren.last).toBeAnInstanceOf(TemplateRef); + expect(comp.viewChildren.length).toBe(2); + }); - it('should return ElementRef when HTML element is labelled and retrieved via Content query', - () => { - const template = ` + fixmeIvy('Must support static view queries in Ivy') + .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']); + }); + + fixmeIvy('Must support static view queries in Ivy') + .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]); + }); + + }); + + describe('content queries', () => { + it('should return Component instance when Component is labeled and retrieved', () => { + const template = ` + + + + `; + const fixture = initWithTemplate(AppComp, template); + const comp = fixture.debugElement.children[0].references['q']; + expect(comp.contentChild).toBeAnInstanceOf(SimpleCompA); + expect(comp.contentChildren.first).toBeAnInstanceOf(SimpleCompA); + }); + + onlyInIvy('multiple local refs are supported in Ivy') + .it('should return Component instances when Components are labeled and retrieved', () => { + const template = ` + + + + + `; + const fixture = initWithTemplate(AppComp, template); + const comp = fixture.debugElement.children[0].references['q']; + expect(comp.contentChild).toBeAnInstanceOf(SimpleCompA); + expect(comp.contentChildren.first).toBeAnInstanceOf(SimpleCompA); + expect(comp.contentChildren.last).toBeAnInstanceOf(SimpleCompB); + expect(comp.contentChildren.length).toBe(2); + }); + + + it('should return ElementRef when HTML element is labeled and retrieved', () => { + const template = `
`; - const fixture = initWithTemplate(AppComp, template); - const comp = fixture.debugElement.children[0].references['q']; - expect(comp.contentChildren.first).toBeAnInstanceOf(ElementRef); - }); + const fixture = initWithTemplate(AppComp, template); + const comp = fixture.debugElement.children[0].references['q']; + expect(comp.contentChildren.first).toBeAnInstanceOf(ElementRef); + }); - onlyInIvy('multiple local refs are supported in Ivy') - .it('should return ElementRefs when HTML elements are labelled and retrieved via Content query', - () => { - const template = ` + onlyInIvy('multiple local refs are supported in Ivy') + .it('should return ElementRefs when HTML elements are labeled and retrieved', () => { + const template = `
`; - const fixture = initWithTemplate(AppComp, template); - const firstChild = fixture.debugElement.children[0]; - const comp = firstChild.references['q']; + const fixture = initWithTemplate(AppComp, template); + const firstChild = fixture.debugElement.children[0]; + const comp = firstChild.references['q']; - expect(comp.contentChild).toBeAnInstanceOf(ElementRef); - expect(comp.contentChild.nativeElement).toBe(firstChild.children[0].nativeElement); + expect(comp.contentChild).toBeAnInstanceOf(ElementRef); + expect(comp.contentChild.nativeElement).toBe(firstChild.children[0].nativeElement); - expect(comp.contentChildren.first).toBeAnInstanceOf(ElementRef); - expect(comp.contentChildren.last).toBeAnInstanceOf(ElementRef); - expect(comp.contentChildren.length).toBe(2); - }); + expect(comp.contentChildren.first).toBeAnInstanceOf(ElementRef); + expect(comp.contentChildren.last).toBeAnInstanceOf(ElementRef); + expect(comp.contentChildren.length).toBe(2); + }); - it('should return TemplateRef when template is labelled and retrieved via Content query', () => { - const template = ` + it('should return TemplateRef when template is labeled and retrieved', () => { + const template = ` `; - const fixture = initWithTemplate(AppComp, template); - const comp = fixture.debugElement.children[0].references['q']; - expect(comp.contentChildren.first).toBeAnInstanceOf(TemplateRef); - }); + const fixture = initWithTemplate(AppComp, template); + const comp = fixture.debugElement.children[0].references['q']; + expect(comp.contentChildren.first).toBeAnInstanceOf(TemplateRef); + }); - onlyInIvy('multiple local refs are supported in Ivy') - .it('should return TemplateRefs when templates are labelled and retrieved via Content query', - () => { - const template = ` + onlyInIvy('multiple local refs are supported in Ivy') + .it('should return TemplateRefs when templates are labeled and retrieved', () => { + const template = ` `; - const fixture = initWithTemplate(AppComp, template); - const firstChild = fixture.debugElement.children[0]; - const comp = firstChild.references['q']; + const fixture = initWithTemplate(AppComp, template); + const firstChild = fixture.debugElement.children[0]; + const comp = firstChild.references['q']; - expect(comp.contentChild).toBeAnInstanceOf(TemplateRef); - expect(comp.contentChild.elementRef.nativeElement) - .toBe(firstChild.childNodes[0].nativeNode); + expect(comp.contentChild).toBeAnInstanceOf(TemplateRef); + expect(comp.contentChild.elementRef.nativeElement) + .toBe(firstChild.childNodes[0].nativeNode); - expect(comp.contentChildren.first).toBeAnInstanceOf(TemplateRef); - expect(comp.contentChildren.last).toBeAnInstanceOf(TemplateRef); - expect(comp.contentChildren.length).toBe(2); + expect(comp.contentChildren.first).toBeAnInstanceOf(TemplateRef); + expect(comp.contentChildren.last).toBeAnInstanceOf(TemplateRef); + expect(comp.contentChildren.length).toBe(2); + }); + + }); + + fixmeIvy('Must support static content queries in Ivy') + .it('should set static content child queries in creation mode (and just in creation mode)', + () => { + const template = ` + +
+ +
+ `; + TestBed.overrideComponent(AppComp, {set: new Component({template})}); + const fixture = TestBed.createComponent(AppComp); + const component = fixture.debugElement.children[0].injector.get(StaticContentQueryComp); + + // static ContentChild query should be set in creation mode, before CD runs + expect(component.textDir).toBeAnInstanceOf(TextDirective); + expect(component.textDir.text).toEqual(''); + expect(component.setEvents).toEqual(['textDir set']); + + // dynamic ContentChild query should not have been resolved yet + expect(component.foo).not.toBeDefined(); + + const span = fixture.nativeElement.querySelector('span'); + (fixture.componentInstance as any).text = 'some text'; + fixture.detectChanges(); + + expect(component.textDir.text).toEqual('some text'); + expect(component.foo.nativeElement).toBe(span); + expect(component.setEvents).toEqual(['textDir set', 'foo set']); }); + + fixmeIvy('Must support static content queries in Ivy') + .it('should support static content child queries inherited from superclasses', () => { + const template = ` + +
+ +
+ +
+ `; + TestBed.overrideComponent(AppComp, {set: new Component({template})}); + const fixture = TestBed.createComponent(AppComp); + const component = + fixture.debugElement.children[0].injector.get(SubclassStaticContentQueryComp); + const divs = fixture.nativeElement.querySelectorAll('div'); + const spans = fixture.nativeElement.querySelectorAll('span'); + + // static ContentChild queries should be set in creation mode, before CD runs + expect(component.textDir).toBeAnInstanceOf(TextDirective); + expect(component.textDir.text).toEqual(''); + expect(component.bar.nativeElement).toEqual(divs[1]); + + // dynamic ContentChild queries should not have been resolved yet + expect(component.foo).not.toBeDefined(); + expect(component.baz).not.toBeDefined(); + + (fixture.componentInstance as any).text = 'some text'; + fixture.detectChanges(); + expect(component.textDir.text).toEqual('some text'); + expect(component.foo.nativeElement).toBe(spans[0]); + expect(component.baz.nativeElement).toBe(spans[1]); + }); + }); function initWithTemplate(compType: Type, template: string) { @@ -209,4 +318,91 @@ class SimpleCompA { @Component({selector: 'simple-comp-b', template: ''}) class SimpleCompB { -} \ No newline at end of file +} + +@Directive({selector: '[text]'}) +class TextDirective { + @Input() text = ''; +} + +@Component({ + selector: 'static-view-query-comp', + template: ` +
+ + ` +}) +class StaticViewQueryComp { + private _textDir !: TextDirective; + private _foo !: ElementRef; + setEvents: string[] = []; + + @ViewChild(TextDirective, {static: true}) + get textDir(): TextDirective { return this._textDir; } + + set textDir(value: TextDirective) { + this.setEvents.push('textDir set'); + this._textDir = value; + } + + @ViewChild('foo', {static: false}) + get foo(): ElementRef { return this._foo; } + + set foo(value: ElementRef) { + this.setEvents.push('foo set'); + this._foo = value; + } + + text = 'some text'; +} + +@Component({ + selector: 'subclass-static-view-query-comp', + template: ` +
+ + +
+ + ` +}) +class SubclassStaticViewQueryComp extends StaticViewQueryComp { + @ViewChild('bar', {static: true}) + bar !: ElementRef; + + @ViewChild('baz', {static: false}) + baz !: ElementRef; +} + + +@Component({selector: 'static-content-query-comp', template: ``}) +class StaticContentQueryComp { + private _textDir !: TextDirective; + private _foo !: ElementRef; + setEvents: string[] = []; + + @ContentChild(TextDirective, {static: true}) + get textDir(): TextDirective { return this._textDir; } + + set textDir(value: TextDirective) { + this.setEvents.push('textDir set'); + this._textDir = value; + } + + @ContentChild('foo', {static: false}) + get foo(): ElementRef { return this._foo; } + + set foo(value: ElementRef) { + this.setEvents.push('foo set'); + this._foo = value; + } +} + +@Component({selector: 'subclass-static-content-query-comp', template: ``}) +class SubclassStaticContentQueryComp extends StaticContentQueryComp { + @ContentChild('bar', {static: true}) + bar !: ElementRef; + + @ContentChild('baz', {static: false}) + baz !: ElementRef; +} diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 254c813b25..634e2ffc35 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -166,9 +166,11 @@ export declare type ContentChild = Query; export interface ContentChildDecorator { (selector: Type | Function | string, opts?: { read?: any; + static?: boolean; }): any; new (selector: Type | Function | string, opts?: { read?: any; + static?: boolean; }): ContentChild; } @@ -670,6 +672,7 @@ export interface Query { isViewQuery: boolean; read: any; selector: any; + static?: boolean; } export declare abstract class Query { @@ -947,9 +950,11 @@ export declare type ViewChild = Query; export interface ViewChildDecorator { (selector: Type | Function | string, opts?: { read?: any; + static?: boolean; }): any; new (selector: Type | Function | string, opts?: { read?: any; + static?: boolean; }): ViewChild; }