diff --git a/goldens/public-api/core/core.d.ts b/goldens/public-api/core/core.d.ts index 7dd098bd1a..48bb530ff1 100644 --- a/goldens/public-api/core/core.d.ts +++ b/goldens/public-api/core/core.d.ts @@ -163,11 +163,11 @@ export declare interface ConstructorSansProvider { export declare type ContentChild = Query; export declare interface ContentChildDecorator { - (selector: Type | Function | string, opts?: { + (selector: Type | InjectionToken | Function | string, opts?: { read?: any; static?: boolean; }): any; - new (selector: Type | Function | string, opts?: { + new (selector: Type | InjectionToken | Function | string, opts?: { read?: any; static?: boolean; }): ContentChild; @@ -176,11 +176,11 @@ export declare interface ContentChildDecorator { export declare type ContentChildren = Query; export declare interface ContentChildrenDecorator { - (selector: Type | Function | string, opts?: { + (selector: Type | InjectionToken | Function | string, opts?: { descendants?: boolean; read?: any; }): any; - new (selector: Type | Function | string, opts?: { + new (selector: Type | InjectionToken | Function | string, opts?: { descendants?: boolean; read?: any; }): Query; @@ -725,7 +725,7 @@ export declare type ɵɵComponentDefWithMeta any, useCapture?: boolean, eventTargetResolver?: GlobalTargetResolver): typeof ɵɵcomponentHostSyntheticListener; -export declare function ɵɵcontentQuery(directiveIndex: number, predicate: Type | string[], descend: boolean, read?: any): void; +export declare function ɵɵcontentQuery(directiveIndex: number, predicate: Type | InjectionToken | string[], descend: boolean, read?: any): void; export declare function ɵɵCopyDefinitionFeature(definition: ɵDirectiveDef | ɵComponentDef): void; @@ -1008,9 +1008,9 @@ export declare function ɵɵsetNgModuleScope(type: any, scope: { exports?: Type[] | (() => Type[]); }): void; -export declare function ɵɵstaticContentQuery(directiveIndex: number, predicate: Type | string[], descend: boolean, read?: any): void; +export declare function ɵɵstaticContentQuery(directiveIndex: number, predicate: Type | InjectionToken | string[], descend: boolean, read?: any): void; -export declare function ɵɵstaticViewQuery(predicate: Type | string[], descend: boolean, read?: any): void; +export declare function ɵɵstaticViewQuery(predicate: Type | InjectionToken | string[], descend: boolean, read?: any): void; export declare function ɵɵstyleMap(styles: { [styleName: string]: any; @@ -1082,7 +1082,7 @@ export declare function ɵɵtextInterpolateV(values: any[]): typeof ɵɵtextInte export declare function ɵɵupdateSyntheticHostBinding(propName: string, value: T | ɵNO_CHANGE, sanitizer?: SanitizerFn | null): typeof ɵɵupdateSyntheticHostBinding; -export declare function ɵɵviewQuery(predicate: Type | string[], descend: boolean, read?: any): void; +export declare function ɵɵviewQuery(predicate: Type | InjectionToken | string[], descend: boolean, read?: any): void; export declare const PACKAGE_ROOT_URL: InjectionToken; @@ -1385,11 +1385,11 @@ export declare const VERSION: Version; export declare type ViewChild = Query; export declare interface ViewChildDecorator { - (selector: Type | Function | string, opts?: { + (selector: Type | InjectionToken | Function | string, opts?: { read?: any; static?: boolean; }): any; - new (selector: Type | Function | string, opts?: { + new (selector: Type | InjectionToken | Function | string, opts?: { read?: any; static?: boolean; }): ViewChild; @@ -1398,10 +1398,10 @@ export declare interface ViewChildDecorator { export declare type ViewChildren = Query; export declare interface ViewChildrenDecorator { - (selector: Type | Function | string, opts?: { + (selector: Type | InjectionToken | Function | string, opts?: { read?: any; }): any; - new (selector: Type | Function | string, opts?: { + new (selector: Type | InjectionToken | Function | string, opts?: { read?: any; }): ViewChildren; } diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 1f4ed4a5b6..c2b93c2e64 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -2931,8 +2931,8 @@ runInEachFileSystem(os => { template: '
', }) class FooCmp { - @ViewChild(TOKEN as any) viewChild: any; - @ContentChild(TOKEN as any) contentChild: any; + @ViewChild(TOKEN) viewChild: any; + @ContentChild(TOKEN) contentChild: any; } `); diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts index f6ff754773..acc8126c83 100644 --- a/packages/compiler/src/render3/view/api.ts +++ b/packages/compiler/src/render3/view/api.ts @@ -221,7 +221,8 @@ export interface R3QueryMetadata { first: boolean; /** - * Either an expression representing a type for the query predicate, or a set of string selectors. + * Either an expression representing a type or `InjectionToken` for the query + * predicate, or a set of string selectors. */ predicate: o.Expression|string[]; diff --git a/packages/core/src/metadata/di.ts b/packages/core/src/metadata/di.ts index bb8f5cd4bd..5de1bdf118 100644 --- a/packages/core/src/metadata/di.ts +++ b/packages/core/src/metadata/di.ts @@ -157,8 +157,10 @@ export interface ContentChildrenDecorator { * * @Annotation */ - (selector: Type|Function|string, opts?: {descendants?: boolean, read?: any}): any; - new(selector: Type|Function|string, opts?: {descendants?: boolean, read?: any}): Query; + (selector: Type|InjectionToken|Function|string, + opts?: {descendants?: boolean, read?: any}): any; + new(selector: Type|InjectionToken|Function|string, + opts?: {descendants?: boolean, read?: any}): Query; } /** @@ -218,8 +220,10 @@ export interface ContentChildDecorator { * * @Annotation */ - (selector: Type|Function|string, opts?: {read?: any, static?: boolean}): any; - new(selector: Type|Function|string, opts?: {read?: any, static?: boolean}): ContentChild; + (selector: Type|InjectionToken|Function|string, + opts?: {read?: any, static?: boolean}): any; + new(selector: Type|InjectionToken|Function|string, + opts?: {read?: any, static?: boolean}): ContentChild; } /** @@ -275,8 +279,9 @@ export interface ViewChildrenDecorator { * * @Annotation */ - (selector: Type|Function|string, opts?: {read?: any}): any; - new(selector: Type|Function|string, opts?: {read?: any}): ViewChildren; + (selector: Type|InjectionToken|Function|string, opts?: {read?: any}): any; + new(selector: Type|InjectionToken|Function|string, + opts?: {read?: any}): ViewChildren; } /** @@ -343,8 +348,10 @@ export interface ViewChildDecorator { * * @Annotation */ - (selector: Type|Function|string, opts?: {read?: any, static?: boolean}): any; - new(selector: Type|Function|string, opts?: {read?: any, static?: boolean}): ViewChild; + (selector: Type|InjectionToken|Function|string, + opts?: {read?: any, static?: boolean}): any; + new(selector: Type|InjectionToken|Function|string, + opts?: {read?: any, static?: boolean}): ViewChild; } /** diff --git a/packages/core/src/render3/interfaces/query.ts b/packages/core/src/render3/interfaces/query.ts index aca9b86d97..f189a93d29 100644 --- a/packages/core/src/render3/interfaces/query.ts +++ b/packages/core/src/render3/interfaces/query.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {InjectionToken} from '../../di/injection_token'; import {Type} from '../../interface/type'; import {QueryList} from '../../linker/query_list'; @@ -16,7 +17,7 @@ import {TView} from './view'; * An object representing query metadata extracted from query annotations. */ export interface TQueryMetadata { - predicate: Type|string[]; + predicate: Type|InjectionToken|string[]; descendants: boolean; read: any; isStatic: boolean; diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index 1487fdabbb..0e73f530db 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -9,6 +9,7 @@ // We are temporarily importing the existing viewEngine_from core so we can be sure we are // correctly implementing its interfaces for backwards compatibility. +import {InjectionToken} from '../di/injection_token'; import {Type} from '../interface/type'; import {ElementRef as ViewEngine_ElementRef} from '../linker/element_ref'; import {QueryList} from '../linker/query_list'; @@ -89,8 +90,8 @@ class LQueries_ implements LQueries { class TQueryMetadata_ implements TQueryMetadata { constructor( - public predicate: Type|string[], public descendants: boolean, public isStatic: boolean, - public read: any = null) {} + public predicate: Type|InjectionToken|string[], public descendants: boolean, + public isStatic: boolean, public read: any = null) {} } class TQueries_ implements TQueries { @@ -454,7 +455,7 @@ export function ɵɵqueryRefresh(queryList: QueryList): boolean { * @codeGenApi */ export function ɵɵstaticViewQuery( - predicate: Type|string[], descend: boolean, read?: any): void { + predicate: Type|InjectionToken|string[], descend: boolean, read?: any): void { viewQueryInternal(getTView(), getLView(), predicate, descend, read, true); } @@ -467,13 +468,14 @@ export function ɵɵstaticViewQuery( * * @codeGenApi */ -export function ɵɵviewQuery(predicate: Type|string[], descend: boolean, read?: any): void { +export function ɵɵviewQuery( + predicate: Type|InjectionToken|string[], descend: boolean, read?: any): void { viewQueryInternal(getTView(), getLView(), predicate, descend, read, false); } function viewQueryInternal( - tView: TView, lView: LView, predicate: Type|string[], descend: boolean, read: any, - isStatic: boolean): void { + tView: TView, lView: LView, predicate: Type|InjectionToken|string[], + descend: boolean, read: any, isStatic: boolean): void { if (tView.firstCreatePass) { createTQuery(tView, new TQueryMetadata_(predicate, descend, isStatic, read), -1); if (isStatic) { @@ -496,7 +498,8 @@ function viewQueryInternal( * @codeGenApi */ export function ɵɵcontentQuery( - directiveIndex: number, predicate: Type|string[], descend: boolean, read?: any): void { + directiveIndex: number, predicate: Type|InjectionToken|string[], descend: boolean, + read?: any): void { contentQueryInternal( getTView(), getLView(), predicate, descend, read, false, getPreviousOrParentTNode(), directiveIndex); @@ -515,15 +518,16 @@ export function ɵɵcontentQuery( * @codeGenApi */ export function ɵɵstaticContentQuery( - directiveIndex: number, predicate: Type|string[], descend: boolean, read?: any): void { + directiveIndex: number, predicate: Type|InjectionToken|string[], descend: boolean, + read?: any): void { contentQueryInternal( getTView(), getLView(), predicate, descend, read, true, getPreviousOrParentTNode(), directiveIndex); } function contentQueryInternal( - tView: TView, lView: LView, predicate: Type|string[], descend: boolean, read: any, - isStatic: boolean, tNode: TNode, directiveIndex: number): void { + tView: TView, lView: LView, predicate: Type|InjectionToken|string[], + descend: boolean, read: any, isStatic: boolean, tNode: TNode, directiveIndex: number): void { if (tView.firstCreatePass) { createTQuery(tView, new TQueryMetadata_(predicate, descend, isStatic, read), tNode.index); saveContentQueryAndDirectiveIndex(tView, directiveIndex); diff --git a/packages/core/test/acceptance/query_spec.ts b/packages/core/test/acceptance/query_spec.ts index 8921d4d66a..3ee127eed3 100644 --- a/packages/core/test/acceptance/query_spec.ts +++ b/packages/core/test/acceptance/query_spec.ts @@ -7,7 +7,7 @@ */ import {CommonModule} from '@angular/common'; -import {AfterViewInit, Component, ContentChild, ContentChildren, Directive, ElementRef, EventEmitter, forwardRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, ViewRef} from '@angular/core'; +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'; @@ -17,10 +17,23 @@ describe('query logic', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [ - AppComp, QueryComp, SimpleCompA, SimpleCompB, StaticViewQueryComp, TextDirective, - SubclassStaticViewQueryComp, StaticContentQueryComp, SubclassStaticContentQueryComp, - QueryCompWithChanges, StaticContentQueryDir, SuperDirectiveQueryTarget, SuperDirective, - SubComponent + AppComp, + QueryComp, + SimpleCompA, + SimpleCompB, + StaticViewQueryComp, + TextDirective, + SubclassStaticViewQueryComp, + StaticContentQueryComp, + SubclassStaticContentQueryComp, + QueryCompWithChanges, + StaticContentQueryDir, + SuperDirectiveQueryTarget, + SuperDirective, + SubComponent, + TestComponentWithToken, + TestInjectionTokenContentQueries, + TestInjectionTokenQueries, ] }); }); @@ -74,6 +87,19 @@ describe('query logic', () => { expect(comp.viewChildren.first).toBeAnInstanceOf(TemplateRef); }); + it('should support selecting InjectionToken', () => { + const fixture = TestBed.createComponent(TestInjectionTokenQueries); + const instance = fixture.componentInstance; + fixture.detectChanges(); + expect(instance.viewFirstOption).toBeDefined(); + expect(instance.viewFirstOption instanceof TestComponentWithToken).toBe(true); + expect(instance.viewOptions).toBeDefined(); + expect(instance.viewOptions.length).toBe(2); + expect(instance.contentFirstOption).toBeUndefined(); + expect(instance.contentOptions).toBeDefined(); + expect(instance.contentOptions.length).toBe(0); + }); + onlyInIvy('multiple local refs are supported in Ivy') .it('should return TemplateRefs when templates are labeled and retrieved', () => { const template = ` @@ -360,6 +386,17 @@ describe('query logic', () => { expect(comp.contentChildren.first).toBeAnInstanceOf(SimpleCompA); }); + it('should support selecting InjectionToken', () => { + const fixture = TestBed.createComponent(TestInjectionTokenContentQueries); + const instance = + fixture.debugElement.query(By.directive(TestInjectionTokenQueries)).componentInstance; + fixture.detectChanges(); + expect(instance.contentFirstOption).toBeDefined(); + expect(instance.contentFirstOption instanceof TestComponentWithToken).toBe(true); + expect(instance.contentOptions).toBeDefined(); + expect(instance.contentOptions.length).toBe(2); + }); + onlyInIvy('multiple local refs are supported in Ivy') .it('should return Component instances when Components are labeled and retrieved', () => { const template = ` @@ -1771,3 +1808,39 @@ class SuperDirective { }) class SubComponent extends SuperDirective { } + +const MY_OPTION_TOKEN = new InjectionToken('ComponentWithToken'); + +@Component({ + selector: 'my-option', + template: 'Option', + providers: [{provide: MY_OPTION_TOKEN, useExisting: TestComponentWithToken}], +}) +class TestComponentWithToken { +} + +@Component({ + selector: 'test-injection-token', + template: ` + + + + ` +}) +class TestInjectionTokenQueries { + @ViewChild(MY_OPTION_TOKEN) viewFirstOption!: TestComponentWithToken; + @ViewChildren(MY_OPTION_TOKEN) viewOptions!: QueryList; + @ContentChild(MY_OPTION_TOKEN) contentFirstOption!: TestComponentWithToken; + @ContentChildren(MY_OPTION_TOKEN) contentOptions!: QueryList; +} + +@Component({ + template: ` + + + + + ` +}) +class TestInjectionTokenContentQueries { +}