fix(ivy): report results to appropriate content queries (#24673)
PR Close #24673
This commit is contained in:
parent
fe8fcc834c
commit
50fb13fb09
|
@ -127,9 +127,19 @@ let tView: TView;
|
||||||
|
|
||||||
let currentQueries: LQueries|null;
|
let currentQueries: LQueries|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query instructions can ask for "current queries" in 2 different cases:
|
||||||
|
* - when creating view queries (at the root of a component view, before any node is created - in
|
||||||
|
* this case currentQueries points to view queries)
|
||||||
|
* - when creating content queries (inb this previousOrParentNode points to a node on which we
|
||||||
|
* create content queries).
|
||||||
|
*/
|
||||||
export function getCurrentQueries(QueryType: {new (): LQueries}): LQueries {
|
export function getCurrentQueries(QueryType: {new (): LQueries}): LQueries {
|
||||||
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
// top level variables should not be exported for performance reasons (PERF_NOTES.md)
|
||||||
return currentQueries || (currentQueries = (previousOrParentNode.queries || new QueryType()));
|
return currentQueries ||
|
||||||
|
(currentQueries =
|
||||||
|
(previousOrParentNode.queries && previousOrParentNode.queries.clone() ||
|
||||||
|
new QueryType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -13,7 +13,14 @@ import {LNode} from './node';
|
||||||
/** Used for tracking queries (e.g. ViewChild, ContentChild). */
|
/** Used for tracking queries (e.g. ViewChild, ContentChild). */
|
||||||
export interface LQueries {
|
export interface LQueries {
|
||||||
/**
|
/**
|
||||||
* Used to ask querieis if those should be cloned to the child element.
|
* Ask queries to prepare copy of itself. This assures that tracking new queries on child nodes
|
||||||
|
* doesn't mutate list of queries tracked on a parent node. We will clone LQueries before
|
||||||
|
* constructing content queries.
|
||||||
|
*/
|
||||||
|
clone(): LQueries|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to ask queries if those should be cloned to the child element.
|
||||||
*
|
*
|
||||||
* For example in the case of deep queries the `child()` returns
|
* For example in the case of deep queries the `child()` returns
|
||||||
* queries for the child node. In case of shallow queries it returns
|
* queries for the child node. In case of shallow queries it returns
|
||||||
|
|
|
@ -94,8 +94,6 @@ export class LQueries_ implements LQueries {
|
||||||
track<T>(
|
track<T>(
|
||||||
queryList: viewEngine_QueryList<T>, predicate: Type<T>|string[], descend?: boolean,
|
queryList: viewEngine_QueryList<T>, predicate: Type<T>|string[], descend?: boolean,
|
||||||
read?: QueryReadType<T>|Type<T>): void {
|
read?: QueryReadType<T>|Type<T>): void {
|
||||||
// TODO(misko): This is not right. In case of inherited state, a calling track will incorrectly
|
|
||||||
// mutate parent.
|
|
||||||
if (descend) {
|
if (descend) {
|
||||||
this.deep = createQuery(this.deep, queryList, predicate, read != null ? read : null);
|
this.deep = createQuery(this.deep, queryList, predicate, read != null ? read : null);
|
||||||
} else {
|
} else {
|
||||||
|
@ -103,6 +101,8 @@ export class LQueries_ implements LQueries {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clone(): LQueries|null { return this.deep ? new LQueries_(this.deep) : null; }
|
||||||
|
|
||||||
child(): LQueries|null {
|
child(): LQueries|null {
|
||||||
if (this.deep === null) {
|
if (this.deep === null) {
|
||||||
// if we don't have any deep queries then no need to track anything more.
|
// if we don't have any deep queries then no need to track anything more.
|
||||||
|
|
|
@ -1752,5 +1752,141 @@ describe('query', () => {
|
||||||
expect(viewQList.last.nativeElement.id).toBe('after');
|
expect(viewQList.last.nativeElement.id).toBe('after');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should report results to appropriate queries where content queries are nested', () => {
|
||||||
|
|
||||||
|
class QueryDirective {
|
||||||
|
fooBars: any;
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: QueryDirective,
|
||||||
|
selectors: [['', 'query', '']],
|
||||||
|
exportAs: 'query',
|
||||||
|
factory: () => {
|
||||||
|
// @ContentChildren('foo, bar, baz', {descendants: true}) fooBars:
|
||||||
|
// QueryList<ElementRef>;
|
||||||
|
return [
|
||||||
|
new QueryDirective(), query(null, ['foo', 'bar', 'baz'], true, QUERY_READ_FROM_NODE)
|
||||||
|
];
|
||||||
|
},
|
||||||
|
hostBindings: function ContentQueryComponent_HostBindings(
|
||||||
|
dirIndex: number, elIndex: number) {
|
||||||
|
let tmp: any;
|
||||||
|
const instance = loadDirective<any[]>(dirIndex)[0];
|
||||||
|
queryRefresh(tmp = loadDirective<any[]>(dirIndex)[1]) &&
|
||||||
|
(instance.fooBars = tmp as QueryList<any>);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let outInstance: QueryDirective;
|
||||||
|
let inInstance: QueryDirective;
|
||||||
|
|
||||||
|
const AppComponent = createComponent(
|
||||||
|
'app-component',
|
||||||
|
/**
|
||||||
|
* <div query #out="query">
|
||||||
|
* <span #foo></span>
|
||||||
|
* <div query #in="query">
|
||||||
|
* <span #bar></span>
|
||||||
|
* </div>
|
||||||
|
* <span #baz></span>
|
||||||
|
* </div>
|
||||||
|
*/
|
||||||
|
function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'div', [AttributeMarker.SelectOnly, 'query'], ['out', 'query']);
|
||||||
|
{
|
||||||
|
element(2, 'span', ['id', 'foo'], ['foo', '']);
|
||||||
|
elementStart(4, 'div', [AttributeMarker.SelectOnly, 'query'], ['in', 'query']);
|
||||||
|
{ element(6, 'span', ['id', 'bar'], ['bar', '']); }
|
||||||
|
elementEnd();
|
||||||
|
element(8, 'span', ['id', 'baz'], ['baz', '']);
|
||||||
|
}
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
outInstance = (<any>load(1))[0] as QueryDirective;
|
||||||
|
inInstance = (<any>load(5))[0] as QueryDirective;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[QueryDirective]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(AppComponent);
|
||||||
|
expect(outInstance !.fooBars.length).toBe(3);
|
||||||
|
expect(inInstance !.fooBars.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect shallow flag on content queries when mixing deep and shallow queries',
|
||||||
|
() => {
|
||||||
|
class ShallowQueryDirective {
|
||||||
|
foos: any;
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: ShallowQueryDirective,
|
||||||
|
selectors: [['', 'shallow-query', '']],
|
||||||
|
exportAs: 'shallow-query',
|
||||||
|
factory: () => {
|
||||||
|
// @ContentChildren('foo', {descendants: false}) fooBars: QueryList<ElementRef>;
|
||||||
|
return [
|
||||||
|
new ShallowQueryDirective(), query(null, ['foo'], false, QUERY_READ_FROM_NODE)
|
||||||
|
];
|
||||||
|
},
|
||||||
|
hostBindings: function ContentQueryComponent_HostBindings(
|
||||||
|
dirIndex: number, elIndex: number) {
|
||||||
|
let tmp: any;
|
||||||
|
const instance = loadDirective<any[]>(dirIndex)[0];
|
||||||
|
queryRefresh(tmp = loadDirective<any[]>(dirIndex)[1]) &&
|
||||||
|
(instance.foos = tmp as QueryList<any>);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeepQueryDirective {
|
||||||
|
foos: any;
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: DeepQueryDirective,
|
||||||
|
selectors: [['', 'deep-query', '']],
|
||||||
|
exportAs: 'deep-query',
|
||||||
|
factory: () => {
|
||||||
|
// @ContentChildren('foo', {descendants: true}) fooBars: QueryList<ElementRef>;
|
||||||
|
return [new DeepQueryDirective(), query(null, ['foo'], true, QUERY_READ_FROM_NODE)];
|
||||||
|
},
|
||||||
|
hostBindings: function ContentQueryComponent_HostBindings(
|
||||||
|
dirIndex: number, elIndex: number) {
|
||||||
|
let tmp: any;
|
||||||
|
const instance = loadDirective<any[]>(dirIndex)[0];
|
||||||
|
queryRefresh(tmp = loadDirective<any[]>(dirIndex)[1]) &&
|
||||||
|
(instance.foos = tmp as QueryList<any>);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let shallowInstance: ShallowQueryDirective;
|
||||||
|
let deepInstance: DeepQueryDirective;
|
||||||
|
|
||||||
|
const AppComponent = createComponent(
|
||||||
|
'app-component',
|
||||||
|
/**
|
||||||
|
* <div shallow-query #shallow="shallow-query" deep-query #deep="deep-query">
|
||||||
|
* <span #foo></span>
|
||||||
|
* </div>
|
||||||
|
*/
|
||||||
|
function(rf: RenderFlags, ctx: any) {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(
|
||||||
|
0, 'div', [AttributeMarker.SelectOnly, 'shallow-query', 'deep-query'],
|
||||||
|
['shallow', 'shallow-query', 'deep', 'deep-query']);
|
||||||
|
{ element(3, 'span', ['id', 'foo'], ['foo', '']); }
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
shallowInstance = (<any>load(1))[0] as ShallowQueryDirective;
|
||||||
|
deepInstance = (<any>load(2))[0] as DeepQueryDirective;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[ShallowQueryDirective, DeepQueryDirective]);
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(AppComponent);
|
||||||
|
expect(shallowInstance !.foos.length).toBe(1);
|
||||||
|
expect(deepInstance !.foos.length).toBe(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue