fix(ivy): local refs in View and Content Queries should be pulled out into consts in generated code (#26240)

PR Close #26240
This commit is contained in:
Andrew Kushnir 2018-10-03 13:49:24 -07:00 committed by Jason Aden
parent d1b7bb52e7
commit d5e9405d4f
3 changed files with 138 additions and 106 deletions

View File

@ -1150,11 +1150,12 @@ describe('compiler compliance', () => {
@Component({ @Component({
selector: 'view-query-component', selector: 'view-query-component',
template: \` template: \`
<div someDir></div> <div someDir></div>
\` \`
}) })
export class ViewQueryComponent { export class ViewQueryComponent {
@ViewChild(SomeDirective) someDir: SomeDirective; @ViewChild(SomeDirective) someDir: SomeDirective;
@ViewChildren(SomeDirective) someDirs: QueryList<SomeDirective>;
} }
@NgModule({declarations: [SomeDirective, ViewQueryComponent]}) @NgModule({declarations: [SomeDirective, ViewQueryComponent]})
@ -1205,8 +1206,8 @@ describe('compiler compliance', () => {
@Component({ @Component({
selector: 'view-query-component', selector: 'view-query-component',
template: \` template: \`
<div #myRef></div> <div #myRef></div>
<div #myRef1></div> <div #myRef1></div>
\` \`
}) })
export class ViewQueryComponent { export class ViewQueryComponent {
@ -1221,15 +1222,15 @@ describe('compiler compliance', () => {
}; };
const ViewQueryComponentDefinition = ` const ViewQueryComponentDefinition = `
const $e0_attrs$ = ["myRef", ""]; const $e0_attrs$ = ["myRef"];
const $e1_attrs$ = ["myRef1", ""]; const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"];
ViewQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({ ViewQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({
viewQuery: function ViewQueryComponent_Query(rf, ctx) { viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵquery(0, ["myRef"], true); $r3$.ɵquery(0, $e0_attrs$, true);
$r3$.ɵquery(1, ["myRef1", "myRef2", "myRef3"], true); $r3$.ɵquery(1, $e1_attrs$, true);
} }
if (rf & 2) { if (rf & 2) {
var $tmp$; var $tmp$;
@ -1249,19 +1250,24 @@ describe('compiler compliance', () => {
it('should support view queries with read tokens specified', () => { it('should support view queries with read tokens specified', () => {
const files = { const files = {
app: { app: {
...directive,
'view_query.component.ts': ` 'view_query.component.ts': `
import {Component, NgModule, ViewChild, ViewChildren, QueryList, ElementRef} from '@angular/core'; import {Component, NgModule, ViewChild, ViewChildren, QueryList, ElementRef, TemplateRef} from '@angular/core';
import {SomeDirective} from './some.directive';
@Component({ @Component({
selector: 'view-query-component', selector: 'view-query-component',
template: \` template: \`
<div #myRef></div> <div someDir></div>
<div #myRef1></div> <div #myRef></div>
<div #myRef1></div>
\` \`
}) })
export class ViewQueryComponent { export class ViewQueryComponent {
@ViewChild('myRef', {read: ElementRef}) myRef: any; @ViewChild('myRef', {read: TemplateRef}) myRef: TemplateRef;
@ViewChildren('myRef1, myRef2, myRef3', {read: ElementRef}) myRefs: QueryList<any>; @ViewChildren('myRef1, myRef2, myRef3', {read: ElementRef}) myRefs: QueryList<ElementRef>;
@ViewChild(SomeDirective, {read: ElementRef}) someDir: ElementRef;
@ViewChildren(SomeDirective, {read: TemplateRef}) someDirs: QueryList<TemplateRef>;
} }
@NgModule({declarations: [ViewQueryComponent]}) @NgModule({declarations: [ViewQueryComponent]})
@ -1271,20 +1277,24 @@ describe('compiler compliance', () => {
}; };
const ViewQueryComponentDefinition = ` const ViewQueryComponentDefinition = `
const $e0_attrs$ = ["myRef", ""]; const $e0_attrs$ = ["myRef"];
const $e1_attrs$ = ["myRef1", ""]; const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"];
ViewQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({ ViewQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({
viewQuery: function ViewQueryComponent_Query(rf, ctx) { viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵquery(0, ["myRef"], true, ElementRef); $r3$.ɵquery(0, $e0_attrs$, true, TemplateRef);
$r3$.ɵquery(1, ["myRef1", "myRef2", "myRef3"], true, ElementRef); $r3$.ɵquery(1, SomeDirective, true, ElementRef);
$r3$.ɵquery(2, $e1_attrs$, true, ElementRef);
$r3$.ɵquery(3, SomeDirective, true, TemplateRef);
} }
if (rf & 2) { if (rf & 2) {
var $tmp$; var $tmp$;
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵload(0))) && (ctx.myRef = $tmp$.first)); ($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵload(0))) && (ctx.myRef = $tmp$.first));
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵload(1))) && (ctx.myRefs = $tmp$)); ($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵload(1))) && (ctx.someDir = $tmp$.first));
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵload(2))) && (ctx.myRefs = $tmp$));
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵload(3))) && (ctx.someDirs = $tmp$));
} }
}, },
@ -1366,104 +1376,112 @@ describe('compiler compliance', () => {
expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration');
}); });
});
it('should support content queries with local refs', () => { it('should support content queries with local refs', () => {
const files = { const files = {
app: { app: {
'content_query.component.ts': ` 'content_query.component.ts': `
import {Component, ContentChild, ContentChildren, NgModule, QueryList} from '@angular/core'; import {Component, ContentChild, ContentChildren, NgModule, QueryList} from '@angular/core';
@Component({ @Component({
selector: 'content-query-component', selector: 'content-query-component',
template: \` template: \`
<div #myRef></div> <div #myRef></div>
<div #myRef1></div> <div #myRef1></div>
\` \`
}) })
export class ContentQueryComponent { export class ContentQueryComponent {
@ContentChild('myRef') myRef: any; @ContentChild('myRef') myRef: any;
@ContentChildren('myRef1, myRef2, myRef3') myRefs: QueryList<any>; @ContentChildren('myRef1, myRef2, myRef3') myRefs: QueryList<any>;
}
@NgModule({declarations: [ContentQueryComponent]})
export class MyModule {}
`
} }
};
@NgModule({declarations: [ContentQueryComponent]}) const ContentQueryComponentDefinition = `
export class MyModule {} const $e0_attrs$ = ["myRef"];
` const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"];
}
};
const ContentQueryComponentDefinition = `
const $e0_attrs$ = ["myRef", ""];
const $e1_attrs$ = ["myRef1", ""];
ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({
contentQueries: function ContentQueryComponent_ContentQueries() { ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({
$r3$.ɵregisterContentQuery($r3$.ɵquery(null, ["myRef"], true));
$r3$.ɵregisterContentQuery($r3$.ɵquery(null, ["myRef1", "myRef2", "myRef3"], false)); contentQueries: function ContentQueryComponent_ContentQueries() {
}, $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e0_attrs$, true));
contentQueriesRefresh: function ContentQueryComponent_ContentQueriesRefresh(dirIndex, queryStartIndex) { $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e1_attrs$, false));
const instance = $r3$.ɵload(dirIndex); },
var $tmp$; contentQueriesRefresh: function ContentQueryComponent_ContentQueriesRefresh(dirIndex, queryStartIndex) {
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList(queryStartIndex))) && (instance.myRef = $tmp$.first)); const instance = $r3$.ɵload(dirIndex);
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList((queryStartIndex + 1)))) && (instance.myRefs = $tmp$)); var $tmp$;
}, ($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList(queryStartIndex))) && (instance.myRef = $tmp$.first));
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList((queryStartIndex + 1)))) && (instance.myRefs = $tmp$));
});`; },
});`;
const result = compile(files, angularFiles); const result = compile(files, angularFiles);
const source = result.source; const source = result.source;
expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration');
}); });
it('should support content queries with read tokens specified', () => { it('should support content queries with read tokens specified', () => {
const files = { const files = {
app: { app: {
'content_query.component.ts': ` ...directive,
import {Component, ContentChild, ContentChildren, NgModule, QueryList, ElementRef} from '@angular/core'; 'content_query.component.ts': `
import {Component, ContentChild, ContentChildren, NgModule, QueryList, ElementRef, TemplateRef} from '@angular/core';
import {SomeDirective} from './some.directive';
@Component({ @Component({
selector: 'content-query-component', selector: 'content-query-component',
template: \` template: \`
<div #myRef></div> <div someDir></div>
<div #myRef1></div> <div #myRef></div>
\` <div #myRef1></div>
}) \`
export class ContentQueryComponent { })
@ContentChild('myRef', {read: ElementRef}) myRef: any; export class ContentQueryComponent {
@ContentChildren('myRef1, myRef2, myRef3', {read: ElementRef}) myRefs: QueryList<any>; @ContentChild('myRef', {read: TemplateRef}) myRef: TemplateRef;
@ContentChildren('myRef1, myRef2, myRef3', {read: ElementRef}) myRefs: QueryList<ElementRef>;
@ContentChild(SomeDirective, {read: ElementRef}) someDir: ElementRef;
@ContentChildren(SomeDirective, {read: TemplateRef}) someDirs: QueryList<TemplateRef>;
}
@NgModule({declarations: [ContentQueryComponent]})
export class MyModule {}
`
} }
};
@NgModule({declarations: [ContentQueryComponent]}) const ContentQueryComponentDefinition = `
export class MyModule {} const $e0_attrs$ = ["myRef"];
` const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"];
}
};
const ContentQueryComponentDefinition = `
const $e0_attrs$ = ["myRef", ""];
const $e1_attrs$ = ["myRef1", ""];
ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({
contentQueries: function ContentQueryComponent_ContentQueries() { ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({
$r3$.ɵregisterContentQuery($r3$.ɵquery(null, ["myRef"], true, ElementRef));
$r3$.ɵregisterContentQuery($r3$.ɵquery(null, ["myRef1", "myRef2", "myRef3"], false, ElementRef)); contentQueries: function ContentQueryComponent_ContentQueries() {
}, $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e0_attrs$ , true, TemplateRef));
contentQueriesRefresh: function ContentQueryComponent_ContentQueriesRefresh(dirIndex, queryStartIndex) { $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, true, ElementRef));
const instance = $r3$.ɵload(dirIndex); $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e1_attrs$, false, ElementRef));
var $tmp$; $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, false, TemplateRef));
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList(queryStartIndex))) && (instance.myRef = $tmp$.first)); },
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList((queryStartIndex + 1)))) && (instance.myRefs = $tmp$)); contentQueriesRefresh: function ContentQueryComponent_ContentQueriesRefresh(dirIndex, queryStartIndex) {
}, const instance = $r3$.ɵload(dirIndex);
var $tmp$;
});`; ($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList(queryStartIndex))) && (instance.myRef = $tmp$.first));
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList((queryStartIndex + 1)))) && (instance.someDir = $tmp$.first));
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList((queryStartIndex + 2)))) && (instance.myRefs = $tmp$));
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadQueryList((queryStartIndex + 3)))) && (instance.someDirs = $tmp$));
},
});`;
const result = compile(files, angularFiles); const result = compile(files, angularFiles);
const source = result.source; const source = result.source;
expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration');
});
expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration');
}); });
describe('pipes', () => { describe('pipes', () => {

View File

@ -384,6 +384,14 @@ describe('ngtsc behavioral tests', () => {
}); });
it('should generate queries for components', () => { it('should generate queries for components', () => {
// Helper functions to construct RegExps for output validation
const varRegExp = (name: string): RegExp => new RegExp(`var \\w+ = \\[\"${name}\"\\];`);
const queryRegExp = (id: number | null, descend: boolean, ref?: string): RegExp => {
const maybeRef = ref ? `, ${ref}` : ``;
return new RegExp(`i0\\.ɵquery\\(${id}, \\w+, ${descend}${maybeRef}\\)`);
};
env.tsconfig(); env.tsconfig();
env.write(`test.ts`, ` env.write(`test.ts`, `
import {Component, ContentChild, ContentChildren, TemplateRef, ViewChild} from '@angular/core'; import {Component, ContentChild, ContentChildren, TemplateRef, ViewChild} from '@angular/core';
@ -406,11 +414,17 @@ describe('ngtsc behavioral tests', () => {
env.driveMain(); env.driveMain();
const jsContents = env.getContents('test.js'); const jsContents = env.getContents('test.js');
expect(jsContents).toContain(`i0.ɵquery(null, ["bar"], true, TemplateRef)`); expect(jsContents).toMatch(varRegExp('bar'));
expect(jsContents).toMatch(varRegExp('test1'));
expect(jsContents).toMatch(varRegExp('test2'));
expect(jsContents).toMatch(varRegExp('accessor'));
expect(jsContents).toContain(`i0.ɵquery(null, TemplateRef, false)`); expect(jsContents).toContain(`i0.ɵquery(null, TemplateRef, false)`);
expect(jsContents).toContain(`i0.ɵquery(null, ["test2"], true)`); expect(jsContents)
expect(jsContents).toContain(`i0.ɵquery(0, ["accessor"], true)`); .toMatch(queryRegExp(
expect(jsContents).toContain(`i0.ɵquery(1, ["test1"], true)`); null, true, 'TemplateRef')); // match `i0.ɵquery(null, _c0, true, TemplateRef)`
expect(jsContents).toMatch(queryRegExp(null, true)); // match `i0.ɵquery(null, _c0, true)`
expect(jsContents).toMatch(queryRegExp(0, true)); // match `i0.ɵquery(0, _c0, true)`
expect(jsContents).toMatch(queryRegExp(1, true)); // match `i0.ɵquery(1, _c0, true)`
}); });
it('should handle queries that use forwardRef', () => { it('should handle queries that use forwardRef', () => {

View File

@ -112,7 +112,7 @@ export function getQueryPredicate(
const selectors = selector.split(',').map(token => o.literal(token.trim())); const selectors = selector.split(',').map(token => o.literal(token.trim()));
predicate.push(...selectors); predicate.push(...selectors);
}); });
return constantPool.getConstLiteral(o.literalArr(predicate)); return constantPool.getConstLiteral(o.literalArr(predicate), true);
} else { } else {
return query.predicate; return query.predicate;
} }