From d5e9405d4f7bfe43fb0679aa9f63bfb0bff4f34f Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Wed, 3 Oct 2018 13:49:24 -0700 Subject: [PATCH] fix(ivy): local refs in View and Content Queries should be pulled out into consts in generated code (#26240) PR Close #26240 --- .../compliance/r3_compiler_compliance_spec.ts | 220 ++++++++++-------- .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 22 +- packages/compiler/src/render3/view/util.ts | 2 +- 3 files changed, 138 insertions(+), 106 deletions(-) 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 f7e3863652..8898992403 100644 --- a/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts @@ -1150,11 +1150,12 @@ describe('compiler compliance', () => { @Component({ selector: 'view-query-component', template: \` -
+
\` }) export class ViewQueryComponent { @ViewChild(SomeDirective) someDir: SomeDirective; + @ViewChildren(SomeDirective) someDirs: QueryList; } @NgModule({declarations: [SomeDirective, ViewQueryComponent]}) @@ -1205,8 +1206,8 @@ describe('compiler compliance', () => { @Component({ selector: 'view-query-component', template: \` -
-
+
+
\` }) export class ViewQueryComponent { @@ -1221,15 +1222,15 @@ describe('compiler compliance', () => { }; const ViewQueryComponentDefinition = ` - const $e0_attrs$ = ["myRef", ""]; - const $e1_attrs$ = ["myRef1", ""]; + const $e0_attrs$ = ["myRef"]; + const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"]; … ViewQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({ … viewQuery: function ViewQueryComponent_Query(rf, ctx) { if (rf & 1) { - $r3$.ɵquery(0, ["myRef"], true); - $r3$.ɵquery(1, ["myRef1", "myRef2", "myRef3"], true); + $r3$.ɵquery(0, $e0_attrs$, true); + $r3$.ɵquery(1, $e1_attrs$, true); } if (rf & 2) { var $tmp$; @@ -1249,19 +1250,24 @@ describe('compiler compliance', () => { it('should support view queries with read tokens specified', () => { const files = { app: { + ...directive, '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({ selector: 'view-query-component', template: \` -
-
+
+
+
\` }) export class ViewQueryComponent { - @ViewChild('myRef', {read: ElementRef}) myRef: any; - @ViewChildren('myRef1, myRef2, myRef3', {read: ElementRef}) myRefs: QueryList; + @ViewChild('myRef', {read: TemplateRef}) myRef: TemplateRef; + @ViewChildren('myRef1, myRef2, myRef3', {read: ElementRef}) myRefs: QueryList; + @ViewChild(SomeDirective, {read: ElementRef}) someDir: ElementRef; + @ViewChildren(SomeDirective, {read: TemplateRef}) someDirs: QueryList; } @NgModule({declarations: [ViewQueryComponent]}) @@ -1271,20 +1277,24 @@ describe('compiler compliance', () => { }; const ViewQueryComponentDefinition = ` - const $e0_attrs$ = ["myRef", ""]; - const $e1_attrs$ = ["myRef1", ""]; + const $e0_attrs$ = ["myRef"]; + const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"]; … ViewQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({ … viewQuery: function ViewQueryComponent_Query(rf, ctx) { if (rf & 1) { - $r3$.ɵquery(0, ["myRef"], true, ElementRef); - $r3$.ɵquery(1, ["myRef1", "myRef2", "myRef3"], true, ElementRef); + $r3$.ɵquery(0, $e0_attrs$, true, TemplateRef); + $r3$.ɵquery(1, SomeDirective, true, ElementRef); + $r3$.ɵquery(2, $e1_attrs$, true, ElementRef); + $r3$.ɵquery(3, SomeDirective, true, TemplateRef); } if (rf & 2) { var $tmp$; ($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'); }); - }); - it('should support content queries with local refs', () => { - const files = { - app: { - 'content_query.component.ts': ` - import {Component, ContentChild, ContentChildren, NgModule, QueryList} from '@angular/core'; + it('should support content queries with local refs', () => { + const files = { + app: { + 'content_query.component.ts': ` + import {Component, ContentChild, ContentChildren, NgModule, QueryList} from '@angular/core'; - @Component({ - selector: 'content-query-component', - template: \` -
-
- \` - }) - export class ContentQueryComponent { - @ContentChild('myRef') myRef: any; - @ContentChildren('myRef1, myRef2, myRef3') myRefs: QueryList; + @Component({ + selector: 'content-query-component', + template: \` +
+
+ \` + }) + export class ContentQueryComponent { + @ContentChild('myRef') myRef: any; + @ContentChildren('myRef1, myRef2, myRef3') myRefs: QueryList; + } + @NgModule({declarations: [ContentQueryComponent]}) + export class MyModule {} + ` } + }; - @NgModule({declarations: [ContentQueryComponent]}) - export class MyModule {} - ` - } - }; - - const ContentQueryComponentDefinition = ` - const $e0_attrs$ = ["myRef", ""]; - const $e1_attrs$ = ["myRef1", ""]; - … - ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({ + const ContentQueryComponentDefinition = ` + const $e0_attrs$ = ["myRef"]; + const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"]; … - contentQueries: function ContentQueryComponent_ContentQueries() { - $r3$.ɵregisterContentQuery($r3$.ɵquery(null, ["myRef"], true)); - $r3$.ɵregisterContentQuery($r3$.ɵquery(null, ["myRef1", "myRef2", "myRef3"], false)); - }, - 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.myRefs = $tmp$)); - }, - … - });`; + ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({ + … + contentQueries: function ContentQueryComponent_ContentQueries() { + $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e0_attrs$, true)); + $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e1_attrs$, false)); + }, + 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.myRefs = $tmp$)); + }, + … + });`; - const result = compile(files, angularFiles); - const source = result.source; + const result = compile(files, angularFiles); + 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', () => { - const files = { - app: { - 'content_query.component.ts': ` - import {Component, ContentChild, ContentChildren, NgModule, QueryList, ElementRef} from '@angular/core'; + it('should support content queries with read tokens specified', () => { + const files = { + app: { + ...directive, + 'content_query.component.ts': ` + import {Component, ContentChild, ContentChildren, NgModule, QueryList, ElementRef, TemplateRef} from '@angular/core'; + import {SomeDirective} from './some.directive'; - @Component({ - selector: 'content-query-component', - template: \` -
-
- \` - }) - export class ContentQueryComponent { - @ContentChild('myRef', {read: ElementRef}) myRef: any; - @ContentChildren('myRef1, myRef2, myRef3', {read: ElementRef}) myRefs: QueryList; + @Component({ + selector: 'content-query-component', + template: \` +
+
+
+ \` + }) + export class ContentQueryComponent { + @ContentChild('myRef', {read: TemplateRef}) myRef: TemplateRef; + @ContentChildren('myRef1, myRef2, myRef3', {read: ElementRef}) myRefs: QueryList; + @ContentChild(SomeDirective, {read: ElementRef}) someDir: ElementRef; + @ContentChildren(SomeDirective, {read: TemplateRef}) someDirs: QueryList; + } + @NgModule({declarations: [ContentQueryComponent]}) + export class MyModule {} + ` } + }; - @NgModule({declarations: [ContentQueryComponent]}) - export class MyModule {} - ` - } - }; - - const ContentQueryComponentDefinition = ` - const $e0_attrs$ = ["myRef", ""]; - const $e1_attrs$ = ["myRef1", ""]; - … - ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({ + const ContentQueryComponentDefinition = ` + const $e0_attrs$ = ["myRef"]; + const $e1_attrs$ = ["myRef1", "myRef2", "myRef3"]; … - contentQueries: function ContentQueryComponent_ContentQueries() { - $r3$.ɵregisterContentQuery($r3$.ɵquery(null, ["myRef"], true, ElementRef)); - $r3$.ɵregisterContentQuery($r3$.ɵquery(null, ["myRef1", "myRef2", "myRef3"], false, ElementRef)); - }, - 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.myRefs = $tmp$)); - }, - … - });`; + ContentQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({ + … + contentQueries: function ContentQueryComponent_ContentQueries() { + $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e0_attrs$ , true, TemplateRef)); + $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, true, ElementRef)); + $r3$.ɵregisterContentQuery($r3$.ɵquery(null, $e1_attrs$, false, ElementRef)); + $r3$.ɵregisterContentQuery($r3$.ɵquery(null, SomeDirective, false, TemplateRef)); + }, + 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 source = result.source; + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); + }); - expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); }); describe('pipes', () => { diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 140cb6d097..04b048b074 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -384,6 +384,14 @@ describe('ngtsc behavioral tests', () => { }); 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.write(`test.ts`, ` import {Component, ContentChild, ContentChildren, TemplateRef, ViewChild} from '@angular/core'; @@ -406,11 +414,17 @@ describe('ngtsc behavioral tests', () => { env.driveMain(); 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, ["test2"], true)`); - expect(jsContents).toContain(`i0.ɵquery(0, ["accessor"], true)`); - expect(jsContents).toContain(`i0.ɵquery(1, ["test1"], true)`); + expect(jsContents) + .toMatch(queryRegExp( + 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', () => { diff --git a/packages/compiler/src/render3/view/util.ts b/packages/compiler/src/render3/view/util.ts index 07db9e71da..55c6174840 100644 --- a/packages/compiler/src/render3/view/util.ts +++ b/packages/compiler/src/render3/view/util.ts @@ -112,7 +112,7 @@ export function getQueryPredicate( const selectors = selector.split(',').map(token => o.literal(token.trim())); predicate.push(...selectors); }); - return constantPool.getConstLiteral(o.literalArr(predicate)); + return constantPool.getConstLiteral(o.literalArr(predicate), true); } else { return query.predicate; }