feat(ivy): support deep queries through view boundaries (#21700)
PR Close #21700
This commit is contained in:
parent
3e03dbe576
commit
5269ce287e
|
@ -8,14 +8,8 @@
|
||||||
|
|
||||||
import './ng_dev_mode';
|
import './ng_dev_mode';
|
||||||
|
|
||||||
import {ElementRef} from '../linker/element_ref';
|
|
||||||
import {TemplateRef} from '../linker/template_ref';
|
|
||||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
|
||||||
import {Type} from '../type';
|
|
||||||
|
|
||||||
import {assertEqual, assertLessThan, assertNotEqual, assertNotNull} from './assert';
|
import {assertEqual, assertLessThan, assertNotEqual, assertNotNull} from './assert';
|
||||||
import {LContainer, TContainer} from './interfaces/container';
|
import {LContainer, TContainer} from './interfaces/container';
|
||||||
import {LInjector} from './interfaces/injector';
|
|
||||||
import {CssSelector, LProjection} from './interfaces/projection';
|
import {CssSelector, LProjection} from './interfaces/projection';
|
||||||
import {LQuery, QueryReadType} from './interfaces/query';
|
import {LQuery, QueryReadType} from './interfaces/query';
|
||||||
import {LView, LifecycleStage, TData, TView} from './interfaces/view';
|
import {LView, LifecycleStage, TData, TView} from './interfaces/view';
|
||||||
|
@ -147,6 +141,8 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentView = newView;
|
currentView = newView;
|
||||||
|
currentQuery = newView.query;
|
||||||
|
|
||||||
return oldView !;
|
return oldView !;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +177,8 @@ export function createLView(
|
||||||
template: template,
|
template: template,
|
||||||
context: context,
|
context: context,
|
||||||
dynamicViewCount: 0,
|
dynamicViewCount: 0,
|
||||||
lifecycleStage: LifecycleStage.INIT
|
lifecycleStage: LifecycleStage.INIT,
|
||||||
|
query: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
return newView;
|
return newView;
|
||||||
|
@ -1003,14 +1000,17 @@ export function container(
|
||||||
renderParent = currentParent as LElementNode;
|
renderParent = currentParent as LElementNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = createLNode(index, LNodeFlags.Container, comment, <LContainer>{
|
const lContainer = <LContainer>{
|
||||||
views: [],
|
views: [],
|
||||||
nextIndex: 0, renderParent,
|
nextIndex: 0, renderParent,
|
||||||
template: template == null ? null : template,
|
template: template == null ? null : template,
|
||||||
next: null,
|
next: null,
|
||||||
parent: currentView,
|
parent: currentView,
|
||||||
dynamicViewCount: 0,
|
dynamicViewCount: 0,
|
||||||
});
|
query: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const node = createLNode(index, LNodeFlags.Container, comment, lContainer);
|
||||||
|
|
||||||
if (node.tNode == null) {
|
if (node.tNode == null) {
|
||||||
// TODO(misko): implement queryName caching
|
// TODO(misko): implement queryName caching
|
||||||
|
@ -1025,8 +1025,13 @@ export function container(
|
||||||
|
|
||||||
isParent = false;
|
isParent = false;
|
||||||
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container);
|
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container);
|
||||||
const query = previousOrParentNode.query;
|
const query = node.query;
|
||||||
query && query.addNode(previousOrParentNode);
|
if (query) {
|
||||||
|
// check if a given container node matches
|
||||||
|
query.addNode(node);
|
||||||
|
// prepare place for matching nodes from views inserted into a given container
|
||||||
|
lContainer.query = query.container();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1107,6 +1112,10 @@ export function viewStart(viewBlockId: number): boolean {
|
||||||
// When we create a new LView, we always reset the state of the instructions.
|
// When we create a new LView, we always reset the state of the instructions.
|
||||||
const newView =
|
const newView =
|
||||||
createLView(viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container));
|
createLView(viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container));
|
||||||
|
if (lContainer.query) {
|
||||||
|
newView.query = lContainer.query.enterView(lContainer.nextIndex);
|
||||||
|
}
|
||||||
|
|
||||||
enterView(newView, createLNode(null, LNodeFlags.View, null, newView));
|
enterView(newView, createLNode(null, LNodeFlags.View, null, newView));
|
||||||
lContainer.nextIndex++;
|
lContainer.nextIndex++;
|
||||||
}
|
}
|
||||||
|
@ -1830,4 +1839,4 @@ function assertDataInRange(index: number, arr?: any[]) {
|
||||||
|
|
||||||
function assertDataNext(index: number) {
|
function assertDataNext(index: number) {
|
||||||
assertEqual(data.length, index, 'data.length not in sequence');
|
assertEqual(data.length, index, 'data.length not in sequence');
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,11 @@
|
||||||
|
|
||||||
import {ComponentTemplate} from './definition';
|
import {ComponentTemplate} from './definition';
|
||||||
import {LElementNode, LViewNode} from './node';
|
import {LElementNode, LViewNode} from './node';
|
||||||
|
import {LQuery} from './query';
|
||||||
import {LView, TView} from './view';
|
import {LView, TView} from './view';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** The state associated with an LContainer */
|
/** The state associated with an LContainer */
|
||||||
export interface LContainer {
|
export interface LContainer {
|
||||||
/**
|
/**
|
||||||
|
@ -67,12 +69,17 @@ export interface LContainer {
|
||||||
*/
|
*/
|
||||||
readonly template: ComponentTemplate<any>|null;
|
readonly template: ComponentTemplate<any>|null;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A count of dynamic views rendered into this container. If this is non-zero, the `views` array
|
* A count of dynamic views rendered into this container. If this is non-zero, the `views` array
|
||||||
* will be traversed when refreshing dynamic views on this container.
|
* will be traversed when refreshing dynamic views on this container.
|
||||||
*/
|
*/
|
||||||
dynamicViewCount: number;
|
dynamicViewCount: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries active for this container - all the views inserted to / removed from
|
||||||
|
* this container are reported to queries referenced here.
|
||||||
|
*/
|
||||||
|
query: LQuery|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,9 +8,7 @@
|
||||||
|
|
||||||
import {QueryList} from '../../linker';
|
import {QueryList} from '../../linker';
|
||||||
import {Type} from '../../type';
|
import {Type} from '../../type';
|
||||||
|
import {LNode} from './node';
|
||||||
import {LInjector} from './injector';
|
|
||||||
import {LContainerNode, LNode, LViewNode} from './node';
|
|
||||||
|
|
||||||
|
|
||||||
/** Used for tracking queries (e.g. ViewChild, ContentChild). */
|
/** Used for tracking queries (e.g. ViewChild, ContentChild). */
|
||||||
|
@ -25,19 +23,28 @@ export interface LQuery {
|
||||||
child(): LQuery|null;
|
child(): LQuery|null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify `LQuery` that a `LNode` has been created.
|
* Notify `LQuery` that a new `LNode` has been created and needs to be added to query results
|
||||||
|
* if matching query predicate.
|
||||||
*/
|
*/
|
||||||
addNode(node: LNode): void;
|
addNode(node: LNode): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify `LQuery` that an `LViewNode` has been added to `LContainerNode`.
|
* Notify `LQuery` that a `LNode` has been created and needs to be added to query results
|
||||||
|
* if matching query predicate.
|
||||||
*/
|
*/
|
||||||
insertView(container: LContainerNode, view: LViewNode, insertIndex: number): void;
|
container(): LQuery|null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify `LQuery` that an `LViewNode` has been removed from `LContainerNode`.
|
* Notify `LQuery` that a new view was created and is being entered in the creation mode.
|
||||||
|
* This allow queries to prepare space for matching nodes from views.
|
||||||
*/
|
*/
|
||||||
removeView(container: LContainerNode, view: LViewNode, removeIndex: number): void;
|
enterView(newViewIndex: number): LQuery|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify `LQuery` that an `LViewNode` has been removed from `LContainerNode`. As a result all
|
||||||
|
* the matching nodes from this view should be removed from container's queries.
|
||||||
|
*/
|
||||||
|
removeView(removeIndex: number): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add additional `QueryList` to track.
|
* Add additional `QueryList` to track.
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import {LContainer} from './container';
|
import {LContainer} from './container';
|
||||||
import {ComponentTemplate, DirectiveDef} from './definition';
|
import {ComponentTemplate, DirectiveDef} from './definition';
|
||||||
import {LElementNode, LViewNode, TNode} from './node';
|
import {LElementNode, LViewNode, TNode} from './node';
|
||||||
|
import {LQuery} from './query';
|
||||||
import {Renderer3} from './renderer';
|
import {Renderer3} from './renderer';
|
||||||
|
|
||||||
|
|
||||||
|
@ -170,6 +171,11 @@ export interface LView {
|
||||||
* after refreshing the view itself.
|
* after refreshing the view itself.
|
||||||
*/
|
*/
|
||||||
dynamicViewCount: number;
|
dynamicViewCount: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries active for this view - nodes from a view are reported to those queries
|
||||||
|
*/
|
||||||
|
query: LQuery|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Interface necessary to work with view tree traversal */
|
/** Interface necessary to work with view tree traversal */
|
||||||
|
|
|
@ -222,8 +222,6 @@ export function insertView(
|
||||||
container, newView, true, findBeforeNode(index, state, container.native));
|
container, newView, true, findBeforeNode(index, state, container.native));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify query that view has been inserted
|
|
||||||
container.query && container.query.insertView(container, newView, index);
|
|
||||||
return newView;
|
return newView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +246,7 @@ export function removeView(container: LContainerNode, removeIndex: number): LVie
|
||||||
destroyViewTree(viewNode.data);
|
destroyViewTree(viewNode.data);
|
||||||
addRemoveViewFromContainer(container, viewNode, false);
|
addRemoveViewFromContainer(container, viewNode, false);
|
||||||
// Notify query that view has been removed
|
// Notify query that view has been removed
|
||||||
container.query && container.query.removeView(container, viewNode, removeIndex);
|
container.data.query && container.data.query.removeView(removeIndex);
|
||||||
return viewNode;
|
return viewNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,37 +10,21 @@
|
||||||
// correctly implementing its interfaces for backwards compatibility.
|
// correctly implementing its interfaces for backwards compatibility.
|
||||||
import {Observable} from 'rxjs/Observable';
|
import {Observable} from 'rxjs/Observable';
|
||||||
|
|
||||||
import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref';
|
|
||||||
import {QueryList as viewEngine_QueryList} from '../linker/query_list';
|
import {QueryList as viewEngine_QueryList} from '../linker/query_list';
|
||||||
import {TemplateRef as viewEngine_TemplateRef} from '../linker/template_ref';
|
|
||||||
import {Type} from '../type';
|
import {Type} from '../type';
|
||||||
|
|
||||||
import {assertNotNull} from './assert';
|
import {assertEqual, assertNotNull} from './assert';
|
||||||
import {ReadFromInjectorFn, getOrCreateNodeInjectorForNode} from './di';
|
import {ReadFromInjectorFn, getOrCreateNodeInjectorForNode} from './di';
|
||||||
import {assertPreviousIsParent, getCurrentQuery} from './instructions';
|
import {assertPreviousIsParent, getCurrentQuery} from './instructions';
|
||||||
import {DirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition';
|
import {DirectiveDef, unusedValueExportToPlacateAjd as unused1} from './interfaces/definition';
|
||||||
import {LInjector, unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
|
import {LInjector, unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
|
||||||
import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode, TNode, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
|
import {LContainerNode, LElementNode, LNode, LNodeFlags, TNode, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
|
||||||
import {LQuery, QueryReadType, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
|
import {LQuery, QueryReadType, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
|
||||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
import {flatten} from './util';
|
||||||
|
|
||||||
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
|
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
|
||||||
|
|
||||||
|
|
||||||
export function query<T>(
|
|
||||||
predicate: Type<any>| string[], descend?: boolean,
|
|
||||||
read?: QueryReadType<T>| Type<T>): QueryList<T> {
|
|
||||||
ngDevMode && assertPreviousIsParent();
|
|
||||||
const queryList = new QueryList<T>();
|
|
||||||
const query = getCurrentQuery(LQuery_);
|
|
||||||
query.track(queryList, predicate, descend, read);
|
|
||||||
return queryList;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function queryRefresh(query: QueryList<any>): boolean {
|
|
||||||
return (query as any)._refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A predicate which determines if a given element/directive should be included in the query
|
* A predicate which determines if a given element/directive should be included in the query
|
||||||
*/
|
*/
|
||||||
|
@ -112,17 +96,90 @@ export class LQuery_ implements LQuery {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
container(): LQuery|null {
|
||||||
|
let result: QueryPredicate<any>|null = null;
|
||||||
|
let predicate = this.deep;
|
||||||
|
|
||||||
|
while (predicate) {
|
||||||
|
const containerValues: any[] = []; // prepare room for views
|
||||||
|
predicate.values.push(containerValues);
|
||||||
|
const clonedPredicate: QueryPredicate<any> = {
|
||||||
|
next: null,
|
||||||
|
list: predicate.list,
|
||||||
|
type: predicate.type,
|
||||||
|
selector: predicate.selector,
|
||||||
|
read: predicate.read,
|
||||||
|
values: containerValues
|
||||||
|
};
|
||||||
|
clonedPredicate.next = result;
|
||||||
|
result = clonedPredicate;
|
||||||
|
predicate = predicate.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result ? new LQuery_(result) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
enterView(index: number): LQuery|null {
|
||||||
|
let result: QueryPredicate<any>|null = null;
|
||||||
|
let predicate = this.deep;
|
||||||
|
|
||||||
|
while (predicate) {
|
||||||
|
const viewValues: any[] = []; // prepare room for view nodes
|
||||||
|
predicate.values.splice(index, 0, viewValues);
|
||||||
|
const clonedPredicate: QueryPredicate<any> = {
|
||||||
|
next: null,
|
||||||
|
list: predicate.list,
|
||||||
|
type: predicate.type,
|
||||||
|
selector: predicate.selector,
|
||||||
|
read: predicate.read,
|
||||||
|
values: viewValues
|
||||||
|
};
|
||||||
|
clonedPredicate.next = result;
|
||||||
|
result = clonedPredicate;
|
||||||
|
predicate = predicate.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result ? new LQuery_(result) : null;
|
||||||
|
}
|
||||||
|
|
||||||
addNode(node: LNode): void {
|
addNode(node: LNode): void {
|
||||||
add(this.shallow, node);
|
add(this.shallow, node);
|
||||||
add(this.deep, node);
|
add(this.deep, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
insertView(container: LContainerNode, view: LViewNode, index: number): void {
|
removeView(index: number): void {
|
||||||
throw new Error('Method not implemented.');
|
let predicate = this.deep;
|
||||||
|
while (predicate) {
|
||||||
|
const removed = predicate.values.splice(index, 1);
|
||||||
|
|
||||||
|
// mark a query as dirty only when removed view had matching modes
|
||||||
|
ngDevMode && assertEqual(removed.length, 1, 'removed.length');
|
||||||
|
if (removed[0].length) {
|
||||||
|
predicate.list.setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
predicate = predicate.next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeView(container: LContainerNode, view: LViewNode, index: number): void {
|
/**
|
||||||
throw new Error('Method not implemented.');
|
* Clone LQuery by taking all the deep query predicates and cloning those using a provided clone
|
||||||
|
* function.
|
||||||
|
* Shallow predicates are ignored.
|
||||||
|
*/
|
||||||
|
private _clonePredicates(
|
||||||
|
predicateCloneFn: (predicate: QueryPredicate<any>) => QueryPredicate<any>): LQuery|null {
|
||||||
|
let result: QueryPredicate<any>|null = null;
|
||||||
|
let predicate = this.deep;
|
||||||
|
|
||||||
|
while (predicate) {
|
||||||
|
const clonedPredicate = predicateCloneFn(predicate);
|
||||||
|
clonedPredicate.next = result;
|
||||||
|
result = clonedPredicate;
|
||||||
|
predicate = predicate.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result ? new LQuery_(result) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,10 +248,10 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) {
|
||||||
if (predicate.read !== null) {
|
if (predicate.read !== null) {
|
||||||
const requestedRead = readFromNodeInjector(nodeInjector, node, predicate.read);
|
const requestedRead = readFromNodeInjector(nodeInjector, node, predicate.read);
|
||||||
if (requestedRead !== null) {
|
if (requestedRead !== null) {
|
||||||
predicate.values.push(requestedRead);
|
addMatch(predicate, requestedRead);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
predicate.values.push(node.view.data[directiveIdx]);
|
addMatch(predicate, node.view.data[directiveIdx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -207,10 +264,10 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) {
|
||||||
if (predicate.read !== null) {
|
if (predicate.read !== null) {
|
||||||
const result = readFromNodeInjector(nodeInjector, node, predicate.read !, directiveIdx);
|
const result = readFromNodeInjector(nodeInjector, node, predicate.read !, directiveIdx);
|
||||||
if (result !== null) {
|
if (result !== null) {
|
||||||
predicate.values.push(result);
|
addMatch(predicate, result);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
predicate.values.push(node.view.data[directiveIdx]);
|
addMatch(predicate, node.view.data[directiveIdx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,27 +276,31 @@ function add(predicate: QueryPredicate<any>| null, node: LNode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addMatch(predicate: QueryPredicate<any>, matchingValue: any): void {
|
||||||
|
predicate.values.push(matchingValue);
|
||||||
|
predicate.list.setDirty();
|
||||||
|
}
|
||||||
|
|
||||||
function createPredicate<T>(
|
function createPredicate<T>(
|
||||||
previous: QueryPredicate<any>| null, queryList: QueryList<T>, predicate: Type<T>| string[],
|
previous: QueryPredicate<any>| null, queryList: QueryList<T>, predicate: Type<T>| string[],
|
||||||
read: QueryReadType<T>| Type<T>| null): QueryPredicate<T> {
|
read: QueryReadType<T>| Type<T>| null): QueryPredicate<T> {
|
||||||
const isArray = Array.isArray(predicate);
|
const isArray = Array.isArray(predicate);
|
||||||
const values = <any>[];
|
|
||||||
if ((queryList as any as QueryList_<T>)._valuesTree === null) {
|
|
||||||
(queryList as any as QueryList_<T>)._valuesTree = values;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
next: previous,
|
next: previous,
|
||||||
list: queryList,
|
list: queryList,
|
||||||
type: isArray ? null : predicate as Type<T>,
|
type: isArray ? null : predicate as Type<T>,
|
||||||
selector: isArray ? predicate as string[] : null,
|
selector: isArray ? predicate as string[] : null,
|
||||||
read: read,
|
read: read,
|
||||||
values: values
|
values: (queryList as any as QueryList_<T>)._valuesTree
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
|
class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
|
||||||
dirty: boolean = false;
|
readonly dirty = true;
|
||||||
changes: Observable<T>;
|
readonly changes: Observable<T>;
|
||||||
|
private _values: T[]|null = null;
|
||||||
|
/** @internal */
|
||||||
|
_valuesTree: any[] = [];
|
||||||
|
|
||||||
get length(): number {
|
get length(): number {
|
||||||
ngDevMode && assertNotNull(this._values, 'refreshed');
|
ngDevMode && assertNotNull(this._values, 'refreshed');
|
||||||
|
@ -258,21 +319,6 @@ class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
|
||||||
return values.length ? values[values.length - 1] : null;
|
return values.length ? values[values.length - 1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_valuesTree: any[]|null = null;
|
|
||||||
/** @internal */
|
|
||||||
_values: T[]|null = null;
|
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_refresh(): boolean {
|
|
||||||
// TODO(misko): needs more logic to flatten tree.
|
|
||||||
if (this._values === null) {
|
|
||||||
this._values = this._valuesTree;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
map<U>(fn: (item: T, index: number, array: T[]) => U): U[] {
|
map<U>(fn: (item: T, index: number, array: T[]) => U): U[] {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
@ -295,10 +341,16 @@ class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
|
||||||
ngDevMode && assertNotNull(this._values, 'refreshed');
|
ngDevMode && assertNotNull(this._values, 'refreshed');
|
||||||
return this._values !;
|
return this._values !;
|
||||||
}
|
}
|
||||||
toString(): string { throw new Error('Method not implemented.'); }
|
toString(): string {
|
||||||
reset(res: (any[]|T)[]): void { throw new Error('Method not implemented.'); }
|
ngDevMode && assertNotNull(this._values, 'refreshed');
|
||||||
|
return this._values !.toString();
|
||||||
|
}
|
||||||
|
reset(res: (any[]|T)[]): void {
|
||||||
|
this._values = flatten(res);
|
||||||
|
(this as{dirty: boolean}).dirty = false;
|
||||||
|
}
|
||||||
notifyOnChanges(): void { throw new Error('Method not implemented.'); }
|
notifyOnChanges(): void { throw new Error('Method not implemented.'); }
|
||||||
setDirty(): void { throw new Error('Method not implemented.'); }
|
setDirty(): void { (this as{dirty: boolean}).dirty = true; }
|
||||||
destroy(): void { throw new Error('Method not implemented.'); }
|
destroy(): void { throw new Error('Method not implemented.'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,3 +358,27 @@ class QueryList_<T>/* implements viewEngine_QueryList<T> */ {
|
||||||
// it can't be implemented only extended.
|
// it can't be implemented only extended.
|
||||||
export type QueryList<T> = viewEngine_QueryList<T>;
|
export type QueryList<T> = viewEngine_QueryList<T>;
|
||||||
export const QueryList: typeof viewEngine_QueryList = QueryList_ as any;
|
export const QueryList: typeof viewEngine_QueryList = QueryList_ as any;
|
||||||
|
|
||||||
|
export function query<T>(
|
||||||
|
predicate: Type<any>| string[], descend?: boolean,
|
||||||
|
read?: QueryReadType<T>| Type<T>): QueryList<T> {
|
||||||
|
ngDevMode && assertPreviousIsParent();
|
||||||
|
const queryList = new QueryList<T>();
|
||||||
|
const query = getCurrentQuery(LQuery_);
|
||||||
|
query.track(queryList, predicate, descend, read);
|
||||||
|
return queryList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes a query by combining matches from all active views and removing matches from deleted
|
||||||
|
* views.
|
||||||
|
* Returns true if a query got dirty during change detection, false otherwise.
|
||||||
|
*/
|
||||||
|
export function queryRefresh(query: QueryList<any>): boolean {
|
||||||
|
const queryImpl = (query as any as QueryList_<any>);
|
||||||
|
if (query.dirty) {
|
||||||
|
query.reset(queryImpl._valuesTree);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -31,3 +31,28 @@ export function stringify(value: any): string {
|
||||||
export function notImplemented(): Error {
|
export function notImplemented(): Error {
|
||||||
return new Error('NotImplemented');
|
return new Error('NotImplemented');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flattens an array in non-recursive way. Input arrays are not modified.
|
||||||
|
*/
|
||||||
|
export function flatten(list: any[]): any[] {
|
||||||
|
const result: any[] = [];
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < list.length) {
|
||||||
|
const item = list[i];
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
if (item.length > 0) {
|
||||||
|
list = item.concat(list.slice(i + 1));
|
||||||
|
i = 0;
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(item);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -6,8 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF} from '../../src/render3/di';
|
import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF} from '../../src/render3/di';
|
||||||
import {C, E, Q, QueryList, e, m, qR} from '../../src/render3/index';
|
import {C, E, Q, QueryList, V, cR, cr, detectChanges, e, m, qR, v} from '../../src/render3/index';
|
||||||
import {QueryReadType} from '../../src/render3/interfaces/query';
|
|
||||||
|
|
||||||
import {createComponent, createDirective, renderComponent} from './render_util';
|
import {createComponent, createDirective, renderComponent} from './render_util';
|
||||||
|
|
||||||
|
@ -511,7 +510,6 @@ describe('query', () => {
|
||||||
it('should not add results to query if a requested token cant be read', () => {
|
it('should not add results to query if a requested token cant be read', () => {
|
||||||
const Child = createDirective();
|
const Child = createDirective();
|
||||||
|
|
||||||
let childInstance, div;
|
|
||||||
/**
|
/**
|
||||||
* <div #foo></div>
|
* <div #foo></div>
|
||||||
* class Cmpt {
|
* class Cmpt {
|
||||||
|
@ -522,7 +520,7 @@ describe('query', () => {
|
||||||
let tmp: any;
|
let tmp: any;
|
||||||
if (cm) {
|
if (cm) {
|
||||||
m(0, Q(['foo'], false, Child));
|
m(0, Q(['foo'], false, Child));
|
||||||
div = E(1, 'div', null, null, ['foo', '']);
|
E(1, 'div', null, null, ['foo', '']);
|
||||||
e();
|
e();
|
||||||
}
|
}
|
||||||
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
|
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
|
||||||
|
@ -534,4 +532,299 @@ describe('query', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('view boundaries', () => {
|
||||||
|
|
||||||
|
it('should report results in embedded views', () => {
|
||||||
|
let firstEl;
|
||||||
|
/**
|
||||||
|
* <ng-template [ngIf]="exp">
|
||||||
|
* <div #foo></div>
|
||||||
|
* </ng-template>
|
||||||
|
* class Cmpt {
|
||||||
|
* @ViewChildren('foo') query;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
|
||||||
|
let tmp: any;
|
||||||
|
if (cm) {
|
||||||
|
m(0, Q(['foo'], true, QUERY_READ_FROM_NODE));
|
||||||
|
C(1);
|
||||||
|
}
|
||||||
|
cR(1);
|
||||||
|
{
|
||||||
|
if (ctx.exp) {
|
||||||
|
let cm1 = V(1);
|
||||||
|
{
|
||||||
|
if (cm1) {
|
||||||
|
firstEl = E(0, 'div', null, null, ['foo', '']);
|
||||||
|
e();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cr();
|
||||||
|
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
|
||||||
|
});
|
||||||
|
|
||||||
|
const cmptInstance = renderComponent(Cmpt);
|
||||||
|
const query = (cmptInstance.query as any);
|
||||||
|
expect(query.length).toBe(0);
|
||||||
|
|
||||||
|
cmptInstance.exp = true;
|
||||||
|
detectChanges(cmptInstance);
|
||||||
|
expect(query.length).toBe(1);
|
||||||
|
expect(query.first.nativeElement).toBe(firstEl);
|
||||||
|
|
||||||
|
cmptInstance.exp = false;
|
||||||
|
detectChanges(cmptInstance);
|
||||||
|
expect(query.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add results from embedded views in the correct order - views and elements mix',
|
||||||
|
() => {
|
||||||
|
let firstEl, lastEl, viewEl;
|
||||||
|
/**
|
||||||
|
* <span #foo></span>
|
||||||
|
* <ng-template [ngIf]="exp">
|
||||||
|
* <div #foo></div>
|
||||||
|
* </ng-template>
|
||||||
|
* <span #foo></span>
|
||||||
|
* class Cmpt {
|
||||||
|
* @ViewChildren('foo') query;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
|
||||||
|
let tmp: any;
|
||||||
|
if (cm) {
|
||||||
|
m(0, Q(['foo'], true, QUERY_READ_FROM_NODE));
|
||||||
|
firstEl = E(1, 'b', null, null, ['foo', '']);
|
||||||
|
e();
|
||||||
|
C(2);
|
||||||
|
lastEl = E(3, 'i', null, null, ['foo', '']);
|
||||||
|
e();
|
||||||
|
}
|
||||||
|
cR(2);
|
||||||
|
{
|
||||||
|
if (ctx.exp) {
|
||||||
|
let cm1 = V(1);
|
||||||
|
{
|
||||||
|
if (cm1) {
|
||||||
|
viewEl = E(0, 'div', null, null, ['foo', '']);
|
||||||
|
e();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cr();
|
||||||
|
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
|
||||||
|
});
|
||||||
|
|
||||||
|
const cmptInstance = renderComponent(Cmpt);
|
||||||
|
const query = (cmptInstance.query as any);
|
||||||
|
expect(query.length).toBe(2);
|
||||||
|
expect(query.first.nativeElement).toBe(firstEl);
|
||||||
|
expect(query.last.nativeElement).toBe(lastEl);
|
||||||
|
|
||||||
|
cmptInstance.exp = true;
|
||||||
|
detectChanges(cmptInstance);
|
||||||
|
expect(query.length).toBe(3);
|
||||||
|
expect(query.toArray()[0].nativeElement).toBe(firstEl);
|
||||||
|
expect(query.toArray()[1].nativeElement).toBe(viewEl);
|
||||||
|
expect(query.toArray()[2].nativeElement).toBe(lastEl);
|
||||||
|
|
||||||
|
cmptInstance.exp = false;
|
||||||
|
detectChanges(cmptInstance);
|
||||||
|
expect(query.length).toBe(2);
|
||||||
|
expect(query.first.nativeElement).toBe(firstEl);
|
||||||
|
expect(query.last.nativeElement).toBe(lastEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add results from embedded views in the correct order - views side by side', () => {
|
||||||
|
let firstEl, lastEl;
|
||||||
|
/**
|
||||||
|
* <ng-template [ngIf]="exp1">
|
||||||
|
* <div #foo></div>
|
||||||
|
* </ng-template>
|
||||||
|
* <ng-template [ngIf]="exp2">
|
||||||
|
* <span #foo></span>
|
||||||
|
* </ng-template>
|
||||||
|
* class Cmpt {
|
||||||
|
* @ViewChildren('foo') query;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
|
||||||
|
let tmp: any;
|
||||||
|
if (cm) {
|
||||||
|
m(0, Q(['foo'], true, QUERY_READ_FROM_NODE));
|
||||||
|
C(1);
|
||||||
|
}
|
||||||
|
cR(1);
|
||||||
|
{
|
||||||
|
if (ctx.exp1) {
|
||||||
|
let cm1 = V(0);
|
||||||
|
{
|
||||||
|
if (cm1) {
|
||||||
|
firstEl = E(0, 'div', null, null, ['foo', '']);
|
||||||
|
e();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v();
|
||||||
|
}
|
||||||
|
if (ctx.exp2) {
|
||||||
|
let cm1 = V(1);
|
||||||
|
{
|
||||||
|
if (cm1) {
|
||||||
|
lastEl = E(0, 'span', null, null, ['foo', '']);
|
||||||
|
e();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cr();
|
||||||
|
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
|
||||||
|
});
|
||||||
|
|
||||||
|
const cmptInstance = renderComponent(Cmpt);
|
||||||
|
const query = (cmptInstance.query as any);
|
||||||
|
expect(query.length).toBe(0);
|
||||||
|
|
||||||
|
cmptInstance.exp2 = true;
|
||||||
|
detectChanges(cmptInstance);
|
||||||
|
expect(query.length).toBe(1);
|
||||||
|
expect(query.last.nativeElement).toBe(lastEl);
|
||||||
|
|
||||||
|
cmptInstance.exp1 = true;
|
||||||
|
detectChanges(cmptInstance);
|
||||||
|
expect(query.length).toBe(2);
|
||||||
|
expect(query.first.nativeElement).toBe(firstEl);
|
||||||
|
expect(query.last.nativeElement).toBe(lastEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add results from embedded views in the correct order - nested views', () => {
|
||||||
|
let firstEl, lastEl;
|
||||||
|
/**
|
||||||
|
* <ng-template [ngIf]="exp1">
|
||||||
|
* <div #foo></div>
|
||||||
|
* <ng-template [ngIf]="exp2">
|
||||||
|
* <span #foo></span>
|
||||||
|
* </ng-template>
|
||||||
|
* </ng-template>
|
||||||
|
* class Cmpt {
|
||||||
|
* @ViewChildren('foo') query;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
|
||||||
|
let tmp: any;
|
||||||
|
if (cm) {
|
||||||
|
m(0, Q(['foo'], true, QUERY_READ_FROM_NODE));
|
||||||
|
C(1);
|
||||||
|
}
|
||||||
|
cR(1);
|
||||||
|
{
|
||||||
|
if (ctx.exp1) {
|
||||||
|
let cm1 = V(0);
|
||||||
|
{
|
||||||
|
if (cm1) {
|
||||||
|
firstEl = E(0, 'div', null, null, ['foo', '']);
|
||||||
|
e();
|
||||||
|
C(1);
|
||||||
|
}
|
||||||
|
cR(1);
|
||||||
|
{
|
||||||
|
if (ctx.exp2) {
|
||||||
|
let cm2 = V(0);
|
||||||
|
{
|
||||||
|
if (cm2) {
|
||||||
|
lastEl = E(0, 'span', null, null, ['foo', '']);
|
||||||
|
e();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cr();
|
||||||
|
}
|
||||||
|
v();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cr();
|
||||||
|
qR(tmp = m<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
|
||||||
|
});
|
||||||
|
|
||||||
|
const cmptInstance = renderComponent(Cmpt);
|
||||||
|
const query = (cmptInstance.query as any);
|
||||||
|
expect(query.length).toBe(0);
|
||||||
|
|
||||||
|
cmptInstance.exp1 = true;
|
||||||
|
detectChanges(cmptInstance);
|
||||||
|
expect(query.length).toBe(1);
|
||||||
|
expect(query.first.nativeElement).toBe(firstEl);
|
||||||
|
|
||||||
|
cmptInstance.exp2 = true;
|
||||||
|
detectChanges(cmptInstance);
|
||||||
|
expect(query.length).toBe(2);
|
||||||
|
expect(query.first.nativeElement).toBe(firstEl);
|
||||||
|
expect(query.last.nativeElement).toBe(lastEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support combination of deep and shallow queries', () => {
|
||||||
|
/**
|
||||||
|
* <ng-template [ngIf]="exp">
|
||||||
|
* <div #foo></div>
|
||||||
|
* </ng-template>
|
||||||
|
* <span #foo></span>
|
||||||
|
* class Cmpt {
|
||||||
|
* @ViewChildren('foo') query;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
const Cmpt = createComponent('cmpt', function(ctx: any, cm: boolean) {
|
||||||
|
let tmp: any;
|
||||||
|
if (cm) {
|
||||||
|
m(0, Q(['foo'], true));
|
||||||
|
m(1, Q(['foo'], false));
|
||||||
|
C(2);
|
||||||
|
E(3, 'span', null, null, ['foo', '']);
|
||||||
|
e();
|
||||||
|
}
|
||||||
|
cR(2);
|
||||||
|
{
|
||||||
|
if (ctx.exp) {
|
||||||
|
let cm1 = V(0);
|
||||||
|
{
|
||||||
|
if (cm1) {
|
||||||
|
E(0, 'div', null, null, ['foo', '']);
|
||||||
|
e();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cr();
|
||||||
|
qR(tmp = m<QueryList<any>>(0)) && (ctx.deep = tmp as QueryList<any>);
|
||||||
|
qR(tmp = m<QueryList<any>>(1)) && (ctx.shallow = tmp as QueryList<any>);
|
||||||
|
});
|
||||||
|
|
||||||
|
const cmptInstance = renderComponent(Cmpt);
|
||||||
|
const deep = (cmptInstance.deep as any);
|
||||||
|
const shallow = (cmptInstance.shallow as any);
|
||||||
|
expect(deep.length).toBe(1);
|
||||||
|
expect(shallow.length).toBe(1);
|
||||||
|
|
||||||
|
|
||||||
|
cmptInstance.exp = true;
|
||||||
|
detectChanges(cmptInstance);
|
||||||
|
expect(deep.length).toBe(2);
|
||||||
|
expect(shallow.length).toBe(1);
|
||||||
|
|
||||||
|
cmptInstance.exp = false;
|
||||||
|
detectChanges(cmptInstance);
|
||||||
|
expect(deep.length).toBe(1);
|
||||||
|
expect(shallow.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 {isDifferent} from '../../src/render3/util';
|
import {flatten, isDifferent} from '../../src/render3/util';
|
||||||
|
|
||||||
describe('util', () => {
|
describe('util', () => {
|
||||||
|
|
||||||
|
@ -32,4 +32,20 @@ describe('util', () => {
|
||||||
expect(isDifferent(5, NaN)).toBeTruthy();
|
expect(isDifferent(5, NaN)).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
describe('flatten', () => {
|
||||||
|
|
||||||
|
it('should flatten an empty array', () => { expect(flatten([])).toEqual([]); });
|
||||||
|
|
||||||
|
it('should flatten a flat array', () => { expect(flatten([1, 2, 3])).toEqual([1, 2, 3]); });
|
||||||
|
|
||||||
|
it('should flatten a nested array', () => {
|
||||||
|
expect(flatten([1, [2], 3])).toEqual([1, 2, 3]);
|
||||||
|
expect(flatten([[1], 2, [3]])).toEqual([1, 2, 3]);
|
||||||
|
expect(flatten([1, [2, [3]], 4])).toEqual([1, 2, 3, 4]);
|
||||||
|
expect(flatten([1, [2, [3]], [4]])).toEqual([1, 2, 3, 4]);
|
||||||
|
expect(flatten([1, [2, [3]], [[[4]]]])).toEqual([1, 2, 3, 4]);
|
||||||
|
expect(flatten([1, [], 2])).toEqual([1, 2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue