Skip to content

Commit c8ab5cb

Browse files
tboschvicb
authored andcommitted
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
1 parent 92084f2 commit c8ab5cb

File tree

2 files changed

+47
-15
lines changed

2 files changed

+47
-15
lines changed

packages/compiler/src/view_compiler/view_compiler.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
147147
// Note: queries start with id 1 so we can use the number in a Bloom filter!
148148
const queryId = queryIndex + 1;
149149
const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All;
150-
let flags = NodeFlags.TypeViewQuery;
151-
if (queryIds.staticQueryIds.has(queryId)) {
152-
flags |= NodeFlags.StaticQuery;
153-
} else {
154-
flags |= NodeFlags.DynamicQuery;
155-
}
150+
const flags =
151+
NodeFlags.TypeViewQuery | calcStaticDynamicQueryFlags(queryIds, queryId, query.first);
156152
this.nodes.push(() => ({
157153
sourceSpan: null,
158154
nodeFlags: flags,
@@ -491,15 +487,9 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
491487
this.nodes.push(null);
492488

493489
dirAst.directive.queries.forEach((query, queryIndex) => {
494-
let flags = NodeFlags.TypeContentQuery;
495490
const queryId = dirAst.contentQueryStartId + queryIndex;
496-
// Note: We only make queries static that query for a single item.
497-
// This is because of backwards compatibility with the old view compiler...
498-
if (queryIds.staticQueryIds.has(queryId) && query.first) {
499-
flags |= NodeFlags.StaticQuery;
500-
} else {
501-
flags |= NodeFlags.DynamicQuery;
502-
}
491+
const flags =
492+
NodeFlags.TypeContentQuery | calcStaticDynamicQueryFlags(queryIds, queryId, query.first);
503493
const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All;
504494
this.nodes.push(() => ({
505495
sourceSpan: dirAst.sourceSpan,
@@ -1194,3 +1184,16 @@ function elementEventNameAndTarget(
11941184
return eventAst;
11951185
}
11961186
}
1187+
1188+
function calcStaticDynamicQueryFlags(
1189+
queryIds: StaticAndDynamicQueryIds, queryId: number, isFirst: boolean) {
1190+
let flags = NodeFlags.None;
1191+
// Note: We only make queries static that query for a single item.
1192+
// This is because of backwards compatibility with the old view compiler...
1193+
if (isFirst && (queryIds.staticQueryIds.has(queryId) || !queryIds.dynamicQueryIds.has(queryId))) {
1194+
flags |= NodeFlags.StaticQuery;
1195+
} else {
1196+
flags |= NodeFlags.DynamicQuery;
1197+
}
1198+
return flags;
1199+
}

packages/core/test/linker/regression_integration_spec.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, Directive, InjectionToken, Injector, Input, Pipe, PipeTransform, Provider, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChildren, ViewContainerRef} from '@angular/core';
9+
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ContentChild, Directive, InjectionToken, Injector, Input, Pipe, PipeTransform, Provider, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChildren, ViewContainerRef} from '@angular/core';
1010
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
1111
import {By} from '@angular/platform-browser';
1212
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -336,6 +336,35 @@ function declareTests({useJit}: {useJit: boolean}) {
336336
expect(fixture.debugElement.childNodes.length).toBe(2);
337337
});
338338
});
339+
340+
it('should support @ContentChild and @Input on the same property for static queries', () => {
341+
@Directive({selector: 'test'})
342+
class Test {
343+
@Input() @ContentChild(TemplateRef) tpl: TemplateRef<any>;
344+
}
345+
346+
@Component({
347+
selector: 'my-app',
348+
template: `
349+
<test></test><br>
350+
<test><ng-template>Custom as a child</ng-template></test><br>
351+
<ng-template #custom>Custom as a binding</ng-template>
352+
<test [tpl]="custom"></test><br>
353+
`
354+
})
355+
class App {
356+
}
357+
358+
const fixture =
359+
TestBed.configureTestingModule({declarations: [App, Test]}).createComponent(App);
360+
fixture.detectChanges();
361+
362+
const testDirs =
363+
fixture.debugElement.queryAll(By.directive(Test)).map(el => el.injector.get(Test));
364+
expect(testDirs[0].tpl).toBeUndefined();
365+
expect(testDirs[1].tpl).toBeDefined();
366+
expect(testDirs[2].tpl).toBeDefined();
367+
});
339368
});
340369
}
341370

0 commit comments

Comments
 (0)