From c8ab5cb0c512fb1b9960b65dca80c1da7439d93c Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Thu, 23 Mar 2017 13:37:45 -0700 Subject: [PATCH] fix(compiler): assume queries with no matches as static (#15429) This has the side effect of allowing `@Input` and `@ContentChild` on the same property if the query is static (see the bug description for details). Fixes #15417 --- .../src/view_compiler/view_compiler.ts | 31 ++++++++++--------- .../linker/regression_integration_spec.ts | 31 ++++++++++++++++++- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/packages/compiler/src/view_compiler/view_compiler.ts b/packages/compiler/src/view_compiler/view_compiler.ts index ad7b77e3ec..3f32287981 100644 --- a/packages/compiler/src/view_compiler/view_compiler.ts +++ b/packages/compiler/src/view_compiler/view_compiler.ts @@ -147,12 +147,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver { // Note: queries start with id 1 so we can use the number in a Bloom filter! const queryId = queryIndex + 1; const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All; - let flags = NodeFlags.TypeViewQuery; - if (queryIds.staticQueryIds.has(queryId)) { - flags |= NodeFlags.StaticQuery; - } else { - flags |= NodeFlags.DynamicQuery; - } + const flags = + NodeFlags.TypeViewQuery | calcStaticDynamicQueryFlags(queryIds, queryId, query.first); this.nodes.push(() => ({ sourceSpan: null, nodeFlags: flags, @@ -491,15 +487,9 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver { this.nodes.push(null); dirAst.directive.queries.forEach((query, queryIndex) => { - let flags = NodeFlags.TypeContentQuery; const queryId = dirAst.contentQueryStartId + queryIndex; - // Note: We only make queries static that query for a single item. - // This is because of backwards compatibility with the old view compiler... - if (queryIds.staticQueryIds.has(queryId) && query.first) { - flags |= NodeFlags.StaticQuery; - } else { - flags |= NodeFlags.DynamicQuery; - } + const flags = + NodeFlags.TypeContentQuery | calcStaticDynamicQueryFlags(queryIds, queryId, query.first); const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All; this.nodes.push(() => ({ sourceSpan: dirAst.sourceSpan, @@ -1194,3 +1184,16 @@ function elementEventNameAndTarget( return eventAst; } } + +function calcStaticDynamicQueryFlags( + queryIds: StaticAndDynamicQueryIds, queryId: number, isFirst: boolean) { + 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))) { + flags |= NodeFlags.StaticQuery; + } else { + flags |= NodeFlags.DynamicQuery; + } + return flags; +} diff --git a/packages/core/test/linker/regression_integration_spec.ts b/packages/core/test/linker/regression_integration_spec.ts index 5028abdd5d..dd11db580a 100644 --- a/packages/core/test/linker/regression_integration_spec.ts +++ b/packages/core/test/linker/regression_integration_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, Directive, InjectionToken, Injector, Input, Pipe, PipeTransform, Provider, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChildren, ViewContainerRef} from '@angular/core'; +import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ContentChild, Directive, InjectionToken, Injector, Input, Pipe, PipeTransform, Provider, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChildren, ViewContainerRef} from '@angular/core'; import {TestBed, fakeAsync, tick} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {expect} from '@angular/platform-browser/testing/src/matchers'; @@ -336,6 +336,35 @@ function declareTests({useJit}: {useJit: boolean}) { expect(fixture.debugElement.childNodes.length).toBe(2); }); }); + + it('should support @ContentChild and @Input on the same property for static queries', () => { + @Directive({selector: 'test'}) + class Test { + @Input() @ContentChild(TemplateRef) tpl: TemplateRef; + } + + @Component({ + selector: 'my-app', + template: ` +
+ Custom as a child
+ Custom as a binding +
+ ` + }) + class App { + } + + const fixture = + TestBed.configureTestingModule({declarations: [App, Test]}).createComponent(App); + fixture.detectChanges(); + + const testDirs = + fixture.debugElement.queryAll(By.directive(Test)).map(el => el.injector.get(Test)); + expect(testDirs[0].tpl).toBeUndefined(); + expect(testDirs[1].tpl).toBeDefined(); + expect(testDirs[2].tpl).toBeDefined(); + }); }); }