Skip to content

Commit 3567e81

Browse files
committedNov 8, 2018
fix(ivy): restore missing match operation in View and Content Queries (angular#26847)
PR Close angular#26847
1 parent ff2ee64 commit 3567e81

File tree

2 files changed

+256
-24
lines changed

2 files changed

+256
-24
lines changed
 

‎packages/core/src/render3/query.ts

+37-20
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ function getIdxOfMatchingDirective(tNode: TNode, currentView: LViewData, type: T
269269
}
270270

271271
// TODO: "read" should be an AbstractType (FW-486)
272-
function queryRead(tNode: TNode, currentView: LViewData, read: any): any {
272+
function queryByReadToken(read: any, tNode: TNode, currentView: LViewData): any {
273273
const factoryFn = (read as any)[NG_ELEMENT_ID];
274274
if (typeof factoryFn === 'function') {
275275
return factoryFn();
@@ -282,7 +282,7 @@ function queryRead(tNode: TNode, currentView: LViewData, read: any): any {
282282
return null;
283283
}
284284

285-
function queryReadByTNodeType(tNode: TNode, currentView: LViewData): any {
285+
function queryByTNodeType(tNode: TNode, currentView: LViewData): any {
286286
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) {
287287
return createElementRef(ViewEngine_ElementRef, tNode, currentView);
288288
}
@@ -292,37 +292,54 @@ function queryReadByTNodeType(tNode: TNode, currentView: LViewData): any {
292292
return null;
293293
}
294294

295+
function queryByTemplateRef(
296+
templateRefToken: ViewEngine_TemplateRef<any>, tNode: TNode, currentView: LViewData,
297+
read: any): any {
298+
const templateRefResult = (templateRefToken as any)[NG_ELEMENT_ID]();
299+
if (read) {
300+
return templateRefResult ? queryByReadToken(read, tNode, currentView) : null;
301+
}
302+
return templateRefResult;
303+
}
304+
305+
function queryRead(tNode: TNode, currentView: LViewData, read: any, matchingIdx: number): any {
306+
if (read) {
307+
return queryByReadToken(read, tNode, currentView);
308+
}
309+
if (matchingIdx > -1) {
310+
return currentView[matchingIdx];
311+
}
312+
// if read token and / or strategy is not specified,
313+
// detect it using appropriate tNode type
314+
return queryByTNodeType(tNode, currentView);
315+
}
316+
295317
function add(
296318
query: LQuery<any>| null, tNode: TElementNode | TContainerNode | TElementContainerNode) {
297319
const currentView = getViewData();
298320

299321
while (query) {
300322
const predicate = query.predicate;
301-
const type = predicate.type;
323+
const type = predicate.type as any;
302324
if (type) {
303-
// if read token and / or strategy is not specified, use type as read token
304-
const result = queryRead(tNode, currentView, predicate.read || type);
325+
let result = null;
326+
if (type === ViewEngine_TemplateRef) {
327+
result = queryByTemplateRef(type, tNode, currentView, predicate.read);
328+
} else {
329+
const matchingIdx = getIdxOfMatchingDirective(tNode, currentView, type);
330+
if (matchingIdx !== null) {
331+
result = queryRead(tNode, currentView, predicate.read, matchingIdx);
332+
}
333+
}
305334
if (result !== null) {
306335
addMatch(query, result);
307336
}
308337
} else {
309338
const selector = predicate.selector !;
310339
for (let i = 0; i < selector.length; i++) {
311-
const directiveIdx = getIdxOfMatchingSelector(tNode, selector[i]);
312-
if (directiveIdx !== null) {
313-
let result: any = null;
314-
if (predicate.read) {
315-
result = queryRead(tNode, currentView, predicate.read);
316-
} else {
317-
if (directiveIdx > -1) {
318-
result = currentView[directiveIdx];
319-
} else {
320-
// if read token and / or strategy is not specified,
321-
// detect it using appropriate tNode type
322-
result = queryReadByTNodeType(tNode, currentView);
323-
}
324-
}
325-
340+
const matchingIdx = getIdxOfMatchingSelector(tNode, selector[i]);
341+
if (matchingIdx !== null) {
342+
const result = queryRead(tNode, currentView, predicate.read, matchingIdx);
326343
if (result !== null) {
327344
addMatch(query, result);
328345
}

‎packages/core/test/render3/query_spec.ts

+219-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {EventEmitter} from '../..';
1313

1414
import {AttributeMarker, QueryList, defineComponent, defineDirective, detectChanges} from '../../src/render3/index';
1515
import {getNativeByIndex} from '../../src/render3/util';
16-
import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadQueryList, reference, registerContentQuery, template} from '../../src/render3/instructions';
16+
import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadQueryList, reference, registerContentQuery, template, text} from '../../src/render3/instructions';
1717
import {RenderFlags} from '../../src/render3/interfaces/definition';
1818
import {query, queryRefresh} from '../../src/render3/query';
1919
import {getViewData} from '../../src/render3/state';
@@ -949,7 +949,7 @@ describe('query', () => {
949949
expect(qList.last).toBe(childInstance);
950950
});
951951

952-
it('should not add results to query if a requested token cant be read', () => {
952+
it('should not add results to selector-based query if a requested token cant be read', () => {
953953
const Child = createDirective('child');
954954

955955
/**
@@ -976,11 +976,226 @@ describe('query', () => {
976976
}
977977
});
978978

979-
const cmptInstance = renderComponent(Cmpt);
980-
const qList = (cmptInstance.query as QueryList<any>);
979+
const {component} = new ComponentFixture(Cmpt);
980+
const qList = component.query;
981+
expect(qList.length).toBe(0);
982+
});
983+
984+
it('should not add results to directive-based query if requested token cant be read', () => {
985+
const Child = createDirective('child');
986+
const OtherChild = createDirective('otherchild');
987+
988+
/**
989+
* <div child></div>
990+
* class Cmpt {
991+
* @ViewChildren(Child, {read: OtherChild}) query;
992+
* }
993+
*/
994+
const Cmpt = createComponent(
995+
'cmpt',
996+
function(rf: RenderFlags, ctx: any) {
997+
if (rf & RenderFlags.Create) {
998+
element(1, 'div', ['child', '']);
999+
}
1000+
},
1001+
2, 0, [Child, OtherChild], [],
1002+
function(rf: RenderFlags, ctx: any) {
1003+
if (rf & RenderFlags.Create) {
1004+
query(0, Child, false, OtherChild);
1005+
}
1006+
if (rf & RenderFlags.Update) {
1007+
let tmp: any;
1008+
queryRefresh(tmp = load<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
1009+
}
1010+
});
1011+
1012+
const {component} = new ComponentFixture(Cmpt);
1013+
const qList = component.query;
1014+
expect(qList.length).toBe(0);
1015+
});
1016+
1017+
it('should not add results to directive-based query if only read token matches', () => {
1018+
const Child = createDirective('child');
1019+
const OtherChild = createDirective('otherchild');
1020+
1021+
/**
1022+
* <div child></div>
1023+
* class Cmpt {
1024+
* @ViewChildren(OtherChild, {read: Child}) query;
1025+
* }
1026+
*/
1027+
const Cmpt = createComponent(
1028+
'cmpt',
1029+
function(rf: RenderFlags, ctx: any) {
1030+
if (rf & RenderFlags.Create) {
1031+
element(1, 'div', ['child', '']);
1032+
}
1033+
},
1034+
2, 0, [Child, OtherChild], [],
1035+
function(rf: RenderFlags, ctx: any) {
1036+
if (rf & RenderFlags.Create) {
1037+
query(0, OtherChild, false, Child);
1038+
}
1039+
if (rf & RenderFlags.Update) {
1040+
let tmp: any;
1041+
queryRefresh(tmp = load<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
1042+
}
1043+
});
1044+
1045+
const {component} = new ComponentFixture(Cmpt);
1046+
const qList = component.query;
1047+
expect(qList.length).toBe(0);
1048+
});
1049+
1050+
it('should not add results to TemplateRef-based query if only read token matches', () => {
1051+
/**
1052+
* <div></div>
1053+
* class Cmpt {
1054+
* @ViewChildren(TemplateRef, {read: ElementRef}) query;
1055+
* }
1056+
*/
1057+
const Cmpt = createComponent(
1058+
'cmpt',
1059+
function(rf: RenderFlags, ctx: any) {
1060+
if (rf & RenderFlags.Create) {
1061+
element(1, 'div');
1062+
}
1063+
},
1064+
2, 0, [], [],
1065+
function(rf: RenderFlags, ctx: any) {
1066+
if (rf & RenderFlags.Create) {
1067+
query(0, TemplateRef as any, false, ElementRef);
1068+
}
1069+
if (rf & RenderFlags.Update) {
1070+
let tmp: any;
1071+
queryRefresh(tmp = load<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
1072+
}
1073+
});
1074+
1075+
const {component} = new ComponentFixture(Cmpt);
1076+
const qList = component.query;
9811077
expect(qList.length).toBe(0);
9821078
});
9831079

1080+
it('should match using string selector and directive as a read argument', () => {
1081+
const Child = createDirective('child');
1082+
1083+
/**
1084+
* <div child #foo></div>
1085+
* class Cmpt {
1086+
* @ViewChildren('foo', {read: Child}) query;
1087+
* }
1088+
*/
1089+
const Cmpt = createComponent(
1090+
'cmpt',
1091+
function(rf: RenderFlags, ctx: any) {
1092+
if (rf & RenderFlags.Create) {
1093+
element(1, 'div', ['child', ''], ['foo', '']);
1094+
}
1095+
},
1096+
3, 0, [Child], [],
1097+
function(rf: RenderFlags, ctx: any) {
1098+
if (rf & RenderFlags.Create) {
1099+
query(0, ['foo'], false, Child);
1100+
}
1101+
if (rf & RenderFlags.Update) {
1102+
let tmp: any;
1103+
queryRefresh(tmp = load<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
1104+
}
1105+
});
1106+
1107+
const {component} = new ComponentFixture(Cmpt);
1108+
const qList = component.query;
1109+
expect(qList.length).toBe(1);
1110+
expect(qList.first instanceof Child).toBeTruthy();
1111+
});
1112+
1113+
it('should not add results to the query in case no match found (via TemplateRef)', () => {
1114+
const Child = createDirective('child');
1115+
1116+
/**
1117+
* <div child></div>
1118+
* class Cmpt {
1119+
* @ViewChildren(TemplateRef) query;
1120+
* }
1121+
*/
1122+
const Cmpt = createComponent(
1123+
'cmpt',
1124+
function(rf: RenderFlags, ctx: any) {
1125+
if (rf & RenderFlags.Create) {
1126+
element(1, 'div', ['child', '']);
1127+
}
1128+
},
1129+
2, 0, [Child], [],
1130+
function(rf: RenderFlags, ctx: any) {
1131+
if (rf & RenderFlags.Create) {
1132+
query(0, TemplateRef as any, false);
1133+
}
1134+
if (rf & RenderFlags.Update) {
1135+
let tmp: any;
1136+
queryRefresh(tmp = load<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
1137+
}
1138+
});
1139+
1140+
const {component} = new ComponentFixture(Cmpt);
1141+
const qList = component.query;
1142+
expect(qList.length).toBe(0);
1143+
});
1144+
1145+
it('should query templates if the type is TemplateRef (and respect "read" option)', () => {
1146+
function Cmpt_Template_1(rf: RenderFlags, ctx1: any) {
1147+
if (rf & RenderFlags.Create) {
1148+
elementStart(0, 'div');
1149+
text(1, 'Test');
1150+
elementEnd();
1151+
}
1152+
}
1153+
/**
1154+
* <ng-template #foo><div>Test</div></ng-template>
1155+
* <ng-template #bar><div>Test</div></ng-template>
1156+
* <ng-template #baz><div>Test</div></ng-template>
1157+
* class Cmpt {
1158+
* @ViewChildren(TemplateRef) tmplQuery;
1159+
* @ViewChildren(TemplateRef, {read: ElementRef}) elemQuery;
1160+
* }
1161+
*/
1162+
const Cmpt = createComponent(
1163+
'cmpt',
1164+
function(rf: RenderFlags, ctx: any) {
1165+
if (rf & RenderFlags.Create) {
1166+
template(2, Cmpt_Template_1, 2, 0, null, null, ['foo', ''], templateRefExtractor);
1167+
template(3, Cmpt_Template_1, 2, 0, null, null, ['bar', ''], templateRefExtractor);
1168+
template(4, Cmpt_Template_1, 2, 0, null, null, ['baz', ''], templateRefExtractor);
1169+
}
1170+
},
1171+
5, 0, [], [],
1172+
function(rf: RenderFlags, ctx: any) {
1173+
if (rf & RenderFlags.Create) {
1174+
query(0, TemplateRef as any, false);
1175+
query(1, TemplateRef as any, false, ElementRef);
1176+
}
1177+
if (rf & RenderFlags.Update) {
1178+
let tmp: any;
1179+
queryRefresh(tmp = load<QueryList<any>>(0)) &&
1180+
(ctx.tmplQuery = tmp as QueryList<any>);
1181+
queryRefresh(tmp = load<QueryList<any>>(1)) &&
1182+
(ctx.elemQuery = tmp as QueryList<any>);
1183+
}
1184+
});
1185+
1186+
const {component} = new ComponentFixture(Cmpt);
1187+
1188+
// check template-based query set
1189+
const tmplQList = component.tmplQuery;
1190+
expect(tmplQList.length).toBe(3);
1191+
expect(isTemplateRef(tmplQList.first)).toBeTruthy();
1192+
1193+
// check element-based query set
1194+
const elemQList = component.elemQuery;
1195+
expect(elemQList.length).toBe(3);
1196+
expect(isElementRef(elemQList.first)).toBeTruthy();
1197+
});
1198+
9841199
});
9851200
});
9861201

0 commit comments

Comments
 (0)
Please sign in to comment.