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
This commit is contained in:
parent
92084f2b6a
commit
c8ab5cb0c5
|
@ -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!
|
// Note: queries start with id 1 so we can use the number in a Bloom filter!
|
||||||
const queryId = queryIndex + 1;
|
const queryId = queryIndex + 1;
|
||||||
const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All;
|
const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All;
|
||||||
let flags = NodeFlags.TypeViewQuery;
|
const flags =
|
||||||
if (queryIds.staticQueryIds.has(queryId)) {
|
NodeFlags.TypeViewQuery | calcStaticDynamicQueryFlags(queryIds, queryId, query.first);
|
||||||
flags |= NodeFlags.StaticQuery;
|
|
||||||
} else {
|
|
||||||
flags |= NodeFlags.DynamicQuery;
|
|
||||||
}
|
|
||||||
this.nodes.push(() => ({
|
this.nodes.push(() => ({
|
||||||
sourceSpan: null,
|
sourceSpan: null,
|
||||||
nodeFlags: flags,
|
nodeFlags: flags,
|
||||||
|
@ -491,15 +487,9 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||||
this.nodes.push(null);
|
this.nodes.push(null);
|
||||||
|
|
||||||
dirAst.directive.queries.forEach((query, queryIndex) => {
|
dirAst.directive.queries.forEach((query, queryIndex) => {
|
||||||
let flags = NodeFlags.TypeContentQuery;
|
|
||||||
const queryId = dirAst.contentQueryStartId + queryIndex;
|
const queryId = dirAst.contentQueryStartId + queryIndex;
|
||||||
// Note: We only make queries static that query for a single item.
|
const flags =
|
||||||
// This is because of backwards compatibility with the old view compiler...
|
NodeFlags.TypeContentQuery | calcStaticDynamicQueryFlags(queryIds, queryId, query.first);
|
||||||
if (queryIds.staticQueryIds.has(queryId) && query.first) {
|
|
||||||
flags |= NodeFlags.StaticQuery;
|
|
||||||
} else {
|
|
||||||
flags |= NodeFlags.DynamicQuery;
|
|
||||||
}
|
|
||||||
const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All;
|
const bindingType = query.first ? QueryBindingType.First : QueryBindingType.All;
|
||||||
this.nodes.push(() => ({
|
this.nodes.push(() => ({
|
||||||
sourceSpan: dirAst.sourceSpan,
|
sourceSpan: dirAst.sourceSpan,
|
||||||
|
@ -1194,3 +1184,16 @@ function elementEventNameAndTarget(
|
||||||
return eventAst;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||||
import {By} from '@angular/platform-browser';
|
import {By} from '@angular/platform-browser';
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
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);
|
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<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-app',
|
||||||
|
template: `
|
||||||
|
<test></test><br>
|
||||||
|
<test><ng-template>Custom as a child</ng-template></test><br>
|
||||||
|
<ng-template #custom>Custom as a binding</ng-template>
|
||||||
|
<test [tpl]="custom"></test><br>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue