diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts
index 4d23588ce5..c56a375c99 100644
--- a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts
+++ b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts
@@ -394,6 +394,10 @@ export function queriesFromFields(
fields: {member: ClassMember, decorators: Decorator[]}[], reflector: ReflectionHost,
evaluator: PartialEvaluator): R3QueryMetadata[] {
return fields.map(({member, decorators}) => {
+ // Throw in case of `@Input() @ContentChild('foo') foo: any`, which is not supported in Ivy
+ if (member.decorators !.some(v => v.name === 'Input')) {
+ throw new Error(`Cannot combine @Input decorators with query decorators`);
+ }
if (decorators.length !== 1) {
throw new Error(`Cannot have multiple query decorators on the same class member`);
} else if (!isPropertyTypeMember(member)) {
diff --git a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
index 836f6f11a3..8eca2159c4 100644
--- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
+++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts
@@ -1688,6 +1688,43 @@ describe('compiler compliance', () => {
expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration');
});
+ it('should throw error if content queries share a property with inputs', () => {
+ const files = {
+ app: {
+ ...directive,
+ 'content_query.ts': `
+ import {Component, ContentChild, Input, NgModule} from '@angular/core';
+
+ @Component({
+ selector: 'content-query-component',
+ template: \`
+
+ \`
+ })
+ export class ContentQueryComponent {
+ @Input() @ContentChild('foo') foo: any;
+ }
+
+ @Component({
+ selector: 'my-app',
+ template: \`
+
+
+
+ \`
+ })
+ export class MyApp { }
+
+ @NgModule({declarations: [ContentQueryComponent, MyApp]})
+ export class MyModule { }
+ `
+ }
+ };
+
+ expect(() => compile(files, angularFiles))
+ .toThrowError(/Cannot combine @Input decorators with query decorators/);
+ });
+
});
describe('pipes', () => {
diff --git a/packages/core/src/render3/jit/directive.ts b/packages/core/src/render3/jit/directive.ts
index d81f138411..2f53453aa9 100644
--- a/packages/core/src/render3/jit/directive.ts
+++ b/packages/core/src/render3/jit/directive.ts
@@ -13,7 +13,7 @@ import {resolveForwardRef} from '../../di/forward_ref';
import {getReflect, reflectDependencies} from '../../di/jit/util';
import {Type} from '../../interface/type';
import {Query} from '../../metadata/di';
-import {Component, Directive} from '../../metadata/directives';
+import {Component, Directive, Input} from '../../metadata/directives';
import {componentNeedsResolution, maybeQueueResolutionOfComponentResources} from '../../metadata/resource_loading';
import {ViewEncapsulation} from '../../metadata/view';
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
@@ -180,13 +180,17 @@ function extractQueriesMetadata(
const queriesMeta: R3QueryMetadataFacade[] = [];
for (const field in propMetadata) {
if (propMetadata.hasOwnProperty(field)) {
- propMetadata[field].forEach(ann => {
+ const annotations = propMetadata[field];
+ annotations.forEach(ann => {
if (isQueryAnn(ann)) {
if (!ann.selector) {
throw new Error(
`Can't construct a query for the property "${field}" of ` +
`"${renderStringify(type)}" since the query selector wasn't defined.`);
}
+ if (annotations.some(isInputAnn)) {
+ throw new Error(`Cannot combine @Input decorators with query decorators`);
+ }
queriesMeta.push(convertToR3QueryMetadata(field, ann));
}
});
@@ -213,6 +217,10 @@ function isViewQuery(value: any): value is Query {
return name === 'ViewChild' || name === 'ViewChildren';
}
+function isInputAnn(value: any): value is Input {
+ return value.ngMetadataName === 'Input';
+}
+
function splitByComma(value: string): string[] {
return value.split(',').map(piece => piece.trim());
}
diff --git a/packages/core/test/linker/regression_integration_spec.ts b/packages/core/test/linker/regression_integration_spec.ts
index e46ba87c19..e6c0dbd65f 100644
--- a/packages/core/test/linker/regression_integration_spec.ts
+++ b/packages/core/test/linker/regression_integration_spec.ts
@@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ANALYZE_FOR_ENTRY_COMPONENTS, ApplicationRef, Component, ComponentRef, ContentChild, Directive, ErrorHandler, EventEmitter, HostListener, InjectionToken, Injector, Input, NgModule, NgModuleRef, NgZone, Output, Pipe, PipeTransform, Provider, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChildren, ViewContainerRef, destroyPlatform, ɵivyEnabled as ivyEnabled} from '@angular/core';
+import {ANALYZE_FOR_ENTRY_COMPONENTS, ApplicationRef, Component, ComponentRef, ContentChild, Directive, ErrorHandler, EventEmitter, HostListener, InjectionToken, Injector, Input, NgModule, NgModuleRef, NgZone, Output, Pipe, PipeTransform, Provider, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef, destroyPlatform, ɵivyEnabled as ivyEnabled} from '@angular/core';
import {TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
import {BrowserModule, By, DOCUMENT} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/src/matchers';
-import {fixmeIvy, modifiedInIvy} from '@angular/private/testing';
+import {fixmeIvy, modifiedInIvy, onlyInIvy} from '@angular/private/testing';
if (ivyEnabled) {
describe('ivy', () => { declareTests(); });
@@ -355,7 +355,7 @@ function declareTests(config?: {useJit: boolean}) {
});
});
- fixmeIvy('FW-797: @ContentChildren results are assigned after @Input bindings')
+ modifiedInIvy('Static ViewChild and ContentChild queries are resolved in update mode')
.it('should support @ContentChild and @Input on the same property for static queries',
() => {
@Directive({selector: 'test'})
@@ -387,6 +387,22 @@ function declareTests(config?: {useJit: boolean}) {
expect(testDirs[2].tpl).toBeDefined();
});
+ onlyInIvy('Ivy does not support @ContentChild and @Input on the same property')
+ .it('should throw if @ContentChild and @Input are on the same property', () => {
+ @Directive({selector: 'test'})
+ class Test {
+ @Input() @ContentChild(TemplateRef) tpl !: TemplateRef;
+ }
+
+ @Component({selector: 'my-app', template: ``})
+ class App {
+ }
+
+ expect(() => {
+ TestBed.configureTestingModule({declarations: [App, Test]}).createComponent(App);
+ }).toThrowError(/Cannot combine @Input decorators with query decorators/);
+ });
+
it('should not add ng-version for dynamically created components', () => {
@Component({template: ''})
class App {