feat(query): implement query update mechanism based on views.
Instead of working with finer grained element injectors, queries now iterate through the views as static units of modification of the application structure. Views already contain element injectors in the correct depth-first preorder. This allows us to remove children linked lists on element injectors and a lot of book keeping that is already present at the view level. Queries are recalculated using the afterContentChecked and afterViewChecked hooks, only during init and after a view container has changed. BREAKING CHANGE: ViewQuery no longer supports the descendants flag. It queries the whole component view by default. Closes #3973
This commit is contained in:
parent
9d42b52d2c
commit
5ebeaf7c9b
|
@ -76,90 +76,19 @@ export class StaticKeys {
|
||||||
|
|
||||||
export class TreeNode<T extends TreeNode<any>> {
|
export class TreeNode<T extends TreeNode<any>> {
|
||||||
_parent: T;
|
_parent: T;
|
||||||
_head: T = null;
|
|
||||||
_tail: T = null;
|
|
||||||
_next: T = null;
|
|
||||||
constructor(parent: T) {
|
constructor(parent: T) {
|
||||||
if (isPresent(parent)) parent.addChild(this);
|
if (isPresent(parent)) {
|
||||||
}
|
parent.addChild(this);
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a child to the parent node. The child MUST NOT be a part of a tree.
|
|
||||||
*/
|
|
||||||
addChild(child: T): void {
|
|
||||||
if (isPresent(this._tail)) {
|
|
||||||
this._tail._next = child;
|
|
||||||
this._tail = child;
|
|
||||||
} else {
|
} else {
|
||||||
this._tail = this._head = child;
|
this._parent = null;
|
||||||
}
|
}
|
||||||
child._next = null;
|
|
||||||
child._parent = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
addChild(child: T): void { child._parent = this; }
|
||||||
* Adds a child to the parent node after a given sibling.
|
|
||||||
* The child MUST NOT be a part of a tree and the sibling must be present.
|
|
||||||
*/
|
|
||||||
addChildAfter(child: T, prevSibling: T): void {
|
|
||||||
if (isBlank(prevSibling)) {
|
|
||||||
var prevHead = this._head;
|
|
||||||
this._head = child;
|
|
||||||
child._next = prevHead;
|
|
||||||
if (isBlank(this._tail)) this._tail = child;
|
|
||||||
} else if (isBlank(prevSibling._next)) {
|
|
||||||
this.addChild(child);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
child._next = prevSibling._next;
|
|
||||||
prevSibling._next = child;
|
|
||||||
}
|
|
||||||
child._parent = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
remove(): void { this._parent = null; }
|
||||||
* Detaches a node from the parent's tree.
|
|
||||||
*/
|
|
||||||
remove(): void {
|
|
||||||
if (isBlank(this.parent)) return;
|
|
||||||
var nextSibling = this._next;
|
|
||||||
var prevSibling = this._findPrev();
|
|
||||||
if (isBlank(prevSibling)) {
|
|
||||||
this.parent._head = this._next;
|
|
||||||
} else {
|
|
||||||
prevSibling._next = this._next;
|
|
||||||
}
|
|
||||||
if (isBlank(nextSibling)) {
|
|
||||||
this._parent._tail = prevSibling;
|
|
||||||
}
|
|
||||||
this._parent = null;
|
|
||||||
this._next = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds a previous sibling or returns null if first child.
|
|
||||||
* Assumes the node has a parent.
|
|
||||||
* TODO(rado): replace with DoublyLinkedList to avoid O(n) here.
|
|
||||||
*/
|
|
||||||
_findPrev() {
|
|
||||||
var node = this.parent._head;
|
|
||||||
if (node == this) return null;
|
|
||||||
while (node._next !== this) node = node._next;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
get parent() { return this._parent; }
|
get parent() { return this._parent; }
|
||||||
|
|
||||||
// TODO(rado): replace with a function call, does too much work for a getter.
|
|
||||||
get children(): T[] {
|
|
||||||
var res = [];
|
|
||||||
var child = this._head;
|
|
||||||
while (child != null) {
|
|
||||||
res.push(child);
|
|
||||||
child = child._next;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DirectiveDependency extends Dependency {
|
export class DirectiveDependency extends Dependency {
|
||||||
|
@ -395,8 +324,7 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||||
private _host: ElementInjector;
|
private _host: ElementInjector;
|
||||||
private _preBuiltObjects: PreBuiltObjects = null;
|
private _preBuiltObjects: PreBuiltObjects = null;
|
||||||
|
|
||||||
// Queries are added during construction or linking with a new parent.
|
// QueryRefs are added during construction. They are never removed.
|
||||||
// They are removed only through unlinking.
|
|
||||||
private _query0: QueryRef;
|
private _query0: QueryRef;
|
||||||
private _query1: QueryRef;
|
private _query1: QueryRef;
|
||||||
private _query2: QueryRef;
|
private _query2: QueryRef;
|
||||||
|
@ -421,7 +349,6 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||||
this.hydrated = false;
|
this.hydrated = false;
|
||||||
|
|
||||||
this._buildQueries();
|
this._buildQueries();
|
||||||
this._addParentQueries();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dehydrate(): void {
|
dehydrate(): void {
|
||||||
|
@ -433,49 +360,44 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||||
this._clearQueryLists();
|
this._clearQueryLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
afterContentChecked(): void {
|
|
||||||
if (isPresent(this._query0) && this._query0.originator === this) {
|
|
||||||
this._query0.list.fireCallbacks();
|
|
||||||
}
|
|
||||||
if (isPresent(this._query1) && this._query1.originator === this) {
|
|
||||||
this._query1.list.fireCallbacks();
|
|
||||||
}
|
|
||||||
if (isPresent(this._query2) && this._query2.originator === this) {
|
|
||||||
this._query2.list.fireCallbacks();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hydrate(imperativelyCreatedInjector: Injector, host: ElementInjector,
|
hydrate(imperativelyCreatedInjector: Injector, host: ElementInjector,
|
||||||
preBuiltObjects: PreBuiltObjects): void {
|
preBuiltObjects: PreBuiltObjects): void {
|
||||||
this._host = host;
|
this._host = host;
|
||||||
this._preBuiltObjects = preBuiltObjects;
|
this._preBuiltObjects = preBuiltObjects;
|
||||||
|
|
||||||
if (isPresent(host)) {
|
|
||||||
this._addViewQueries(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._reattachInjectors(imperativelyCreatedInjector);
|
this._reattachInjectors(imperativelyCreatedInjector);
|
||||||
this._strategy.hydrate();
|
this._strategy.hydrate();
|
||||||
|
|
||||||
this._addDirectivesToQueries();
|
|
||||||
this._addVarBindingsToQueries();
|
|
||||||
|
|
||||||
this.hydrated = true;
|
this.hydrated = true;
|
||||||
|
|
||||||
// TODO(rado): optimize this call, if view queries are not moved around,
|
|
||||||
// simply appending to the query list is faster than updating.
|
|
||||||
this._updateViewQueries();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateViewQueries() {
|
updateLocalQueries() {
|
||||||
|
if (isPresent(this._query0) && !this._query0.isViewQuery) {
|
||||||
|
this._query0.update();
|
||||||
|
this._query0.list.fireCallbacks();
|
||||||
|
}
|
||||||
|
if (isPresent(this._query1) && !this._query1.isViewQuery) {
|
||||||
|
this._query1.update();
|
||||||
|
this._query1.list.fireCallbacks();
|
||||||
|
}
|
||||||
|
if (isPresent(this._query2) && !this._query2.isViewQuery) {
|
||||||
|
this._query2.update();
|
||||||
|
this._query2.list.fireCallbacks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLocalViewQueries() {
|
||||||
if (isPresent(this._query0) && this._query0.isViewQuery) {
|
if (isPresent(this._query0) && this._query0.isViewQuery) {
|
||||||
this._query0.update();
|
this._query0.update();
|
||||||
|
this._query0.list.fireCallbacks();
|
||||||
}
|
}
|
||||||
if (isPresent(this._query1) && this._query1.isViewQuery) {
|
if (isPresent(this._query1) && this._query1.isViewQuery) {
|
||||||
this._query1.update();
|
this._query1.update();
|
||||||
|
this._query1.list.fireCallbacks();
|
||||||
}
|
}
|
||||||
if (isPresent(this._query2) && this._query2.isViewQuery) {
|
if (isPresent(this._query2) && this._query2.isViewQuery) {
|
||||||
this._query2.update();
|
this._query2.update();
|
||||||
|
this._query2.list.fireCallbacks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,6 +476,8 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||||
return new ViewContainerRef(this._preBuiltObjects.viewManager, this.getElementRef());
|
return new ViewContainerRef(this._preBuiltObjects.viewManager, this.getElementRef());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getView(): viewModule.AppView { return this._preBuiltObjects.view; }
|
||||||
|
|
||||||
directParent(): ElementInjector { return this._proto.distanceToParent < 2 ? this.parent : null; }
|
directParent(): ElementInjector { return this._proto.distanceToParent < 2 ? this.parent : null; }
|
||||||
|
|
||||||
isComponentKey(key: Key): boolean { return this._strategy.isComponentKey(key); }
|
isComponentKey(key: Key): boolean { return this._strategy.isComponentKey(key); }
|
||||||
|
@ -633,54 +557,6 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addViewQueries(host: ElementInjector): void {
|
|
||||||
this._addViewQuery(host._query0, host);
|
|
||||||
this._addViewQuery(host._query1, host);
|
|
||||||
this._addViewQuery(host._query2, host);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addViewQuery(queryRef: QueryRef, host: ElementInjector): void {
|
|
||||||
if (isBlank(queryRef) || !queryRef.isViewQuery || this._hasQuery(queryRef)) return;
|
|
||||||
if (queryRef.originator == host) {
|
|
||||||
// TODO(rado): Replace this.parent check with distanceToParent = 1 when
|
|
||||||
// https://github.com/angular/angular/issues/2707 is fixed.
|
|
||||||
if (!queryRef.query.descendants && isPresent(this.parent)) return;
|
|
||||||
this._assignQueryRef(queryRef);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addVarBindingsToQueries(): void {
|
|
||||||
this._addVarBindingsToQuery(this._query0);
|
|
||||||
this._addVarBindingsToQuery(this._query1);
|
|
||||||
this._addVarBindingsToQuery(this._query2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addDirectivesToQueries(): void {
|
|
||||||
this._addDirectivesToQuery(this._query0);
|
|
||||||
this._addDirectivesToQuery(this._query1);
|
|
||||||
this._addDirectivesToQuery(this._query2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addVarBindingsToQuery(queryRef: QueryRef): void {
|
|
||||||
if (isBlank(queryRef) || !queryRef.query.isVarBindingQuery) return;
|
|
||||||
|
|
||||||
var vb = queryRef.query.varBindings;
|
|
||||||
for (var i = 0; i < vb.length; ++i) {
|
|
||||||
if (this.hasVariableBinding(vb[i])) {
|
|
||||||
queryRef.list.add(this.getVariableBinding(vb[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addDirectivesToQuery(queryRef: QueryRef): void {
|
|
||||||
if (isBlank(queryRef) || queryRef.query.isVarBindingQuery) return;
|
|
||||||
if (queryRef.isViewQuery && queryRef.originator == this) return;
|
|
||||||
|
|
||||||
var matched = [];
|
|
||||||
this.addDirectivesMatchingQuery(queryRef.query, matched);
|
|
||||||
matched.forEach(s => queryRef.list.add(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createQueryRef(query: QueryMetadata): void {
|
private _createQueryRef(query: QueryMetadata): void {
|
||||||
var queryList = new QueryList<any>();
|
var queryList = new QueryList<any>();
|
||||||
if (isBlank(this._query0)) {
|
if (isBlank(this._query0)) {
|
||||||
|
@ -695,7 +571,7 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||||
}
|
}
|
||||||
|
|
||||||
addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void {
|
addDirectivesMatchingQuery(query: QueryMetadata, list: any[]): void {
|
||||||
var templateRef = this._preBuiltObjects.templateRef;
|
var templateRef = isBlank(this._preBuiltObjects) ? null : this._preBuiltObjects.templateRef;
|
||||||
if (query.selector === TemplateRef && isPresent(templateRef)) {
|
if (query.selector === TemplateRef && isPresent(templateRef)) {
|
||||||
list.push(templateRef);
|
list.push(templateRef);
|
||||||
}
|
}
|
||||||
|
@ -721,105 +597,9 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||||
throw new BaseException(`Cannot find query for directive ${query}.`);
|
throw new BaseException(`Cannot find query for directive ${query}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
_hasQuery(query: QueryRef): boolean {
|
link(parent: ElementInjector): void { parent.addChild(this); }
|
||||||
return this._query0 == query || this._query1 == query || this._query2 == query;
|
|
||||||
}
|
|
||||||
|
|
||||||
link(parent: ElementInjector): void {
|
unlink(): void { this.remove(); }
|
||||||
parent.addChild(this);
|
|
||||||
this._addParentQueries();
|
|
||||||
}
|
|
||||||
|
|
||||||
linkAfter(parent: ElementInjector, prevSibling: ElementInjector): void {
|
|
||||||
parent.addChildAfter(this, prevSibling);
|
|
||||||
this._addParentQueries();
|
|
||||||
}
|
|
||||||
|
|
||||||
unlink(): void {
|
|
||||||
var parent = this.parent;
|
|
||||||
this.remove();
|
|
||||||
this._removeParentQueries(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addParentQueries(): void {
|
|
||||||
if (isBlank(this.parent)) return;
|
|
||||||
this._addParentQuery(this.parent._query0);
|
|
||||||
this._addParentQuery(this.parent._query1);
|
|
||||||
this._addParentQuery(this.parent._query2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addParentQuery(query): void {
|
|
||||||
if (isPresent(query) && !this._hasQuery(query)) {
|
|
||||||
this._addQueryToTree(query);
|
|
||||||
if (this.hydrated) query.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _removeParentQueries(parent: ElementInjector): void {
|
|
||||||
this._removeParentQuery(parent._query0);
|
|
||||||
this._removeParentQuery(parent._query1);
|
|
||||||
this._removeParentQuery(parent._query2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _removeParentQuery(query: QueryRef) {
|
|
||||||
if (isPresent(query)) {
|
|
||||||
this._pruneQueryFromTree(query);
|
|
||||||
query.update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _pruneQueryFromTree(query: QueryRef): void {
|
|
||||||
this._removeQueryRef(query);
|
|
||||||
|
|
||||||
var child = this._head;
|
|
||||||
while (isPresent(child)) {
|
|
||||||
child._pruneQueryFromTree(query);
|
|
||||||
child = child._next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addQueryToTree(queryRef: QueryRef): void {
|
|
||||||
if (queryRef.query.descendants == false) {
|
|
||||||
if (this == queryRef.originator) {
|
|
||||||
this._addQueryToTreeSelfAndRecurse(queryRef);
|
|
||||||
// TODO(rado): add check for distance to parent = 1 when issue #2707 is fixed.
|
|
||||||
} else if (this.parent == queryRef.originator) {
|
|
||||||
this._assignQueryRef(queryRef);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this._addQueryToTreeSelfAndRecurse(queryRef);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addQueryToTreeSelfAndRecurse(queryRef: QueryRef): void {
|
|
||||||
this._assignQueryRef(queryRef);
|
|
||||||
|
|
||||||
var child = this._head;
|
|
||||||
while (isPresent(child)) {
|
|
||||||
child._addQueryToTree(queryRef);
|
|
||||||
child = child._next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _assignQueryRef(query: QueryRef): void {
|
|
||||||
if (isBlank(this._query0)) {
|
|
||||||
this._query0 = query;
|
|
||||||
return;
|
|
||||||
} else if (isBlank(this._query1)) {
|
|
||||||
this._query1 = query;
|
|
||||||
return;
|
|
||||||
} else if (isBlank(this._query2)) {
|
|
||||||
this._query2 = query;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new QueryError();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _removeQueryRef(query: QueryRef): void {
|
|
||||||
if (this._query0 == query) this._query0 = null;
|
|
||||||
if (this._query1 == query) this._query1 = null;
|
|
||||||
if (this._query2 == query) this._query2 = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDirectiveAtIndex(index: number): any { return this._injector.getAt(index); }
|
getDirectiveAtIndex(index: number): any { return this._injector.getAt(index); }
|
||||||
|
|
||||||
|
@ -837,9 +617,34 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||||
}
|
}
|
||||||
|
|
||||||
private _clearQueryLists(): void {
|
private _clearQueryLists(): void {
|
||||||
if (isPresent(this._query0) && this._query0.originator === this) this._query0.reset();
|
if (isPresent(this._query0)) this._query0.reset();
|
||||||
if (isPresent(this._query1) && this._query1.originator === this) this._query1.reset();
|
if (isPresent(this._query1)) this._query1.reset();
|
||||||
if (isPresent(this._query2) && this._query2.originator === this) this._query2.reset();
|
if (isPresent(this._query2)) this._query2.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
afterViewChecked(): void { this.updateLocalViewQueries(); }
|
||||||
|
|
||||||
|
afterContentChecked(): void { this.updateLocalQueries(); }
|
||||||
|
|
||||||
|
traverseAndSetQueriesAsDirty(): void {
|
||||||
|
var inj = this;
|
||||||
|
while (isPresent(inj)) {
|
||||||
|
inj._setQueriesAsDirty();
|
||||||
|
inj = inj.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setQueriesAsDirty(): void {
|
||||||
|
if (isPresent(this._query0) && !this._query0.isViewQuery) this._query0.dirty = true;
|
||||||
|
if (isPresent(this._query1) && !this._query1.isViewQuery) this._query1.dirty = true;
|
||||||
|
if (isPresent(this._query2) && !this._query2.isViewQuery) this._query2.dirty = true;
|
||||||
|
if (isPresent(this._host)) this._host._setViewQueriesAsDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setViewQueriesAsDirty(): void {
|
||||||
|
if (isPresent(this._query0) && this._query0.isViewQuery) this._query0.dirty = true;
|
||||||
|
if (isPresent(this._query1) && this._query1.isViewQuery) this._query1.dirty = true;
|
||||||
|
if (isPresent(this._query2) && this._query2.isViewQuery) this._query2.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1113,7 +918,7 @@ export class QueryError extends BaseException {
|
||||||
// TODO(rado): pass the names of the active directives.
|
// TODO(rado): pass the names of the active directives.
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.message = 'Only 3 queries can be concurrently active in a template.';
|
this.message = 'Only 3 queries can be concurrently active on an element.';
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string { return this.message; }
|
toString(): string { return this.message; }
|
||||||
|
@ -1121,37 +926,80 @@ export class QueryError extends BaseException {
|
||||||
|
|
||||||
export class QueryRef {
|
export class QueryRef {
|
||||||
constructor(public query: QueryMetadata, public list: QueryList<any>,
|
constructor(public query: QueryMetadata, public list: QueryList<any>,
|
||||||
public originator: ElementInjector) {}
|
public originator: ElementInjector, public dirty: boolean = true) {}
|
||||||
|
|
||||||
get isViewQuery(): boolean { return this.query.isViewQuery; }
|
get isViewQuery(): boolean { return this.query.isViewQuery; }
|
||||||
|
|
||||||
update(): void {
|
update(): void {
|
||||||
var aggregator = [];
|
if (!this.dirty) return;
|
||||||
if (this.query.isViewQuery) {
|
this._update();
|
||||||
// intentionally skipping originator for view queries.
|
this.dirty = false;
|
||||||
var rootViewInjectors = this.originator.getRootViewInjectors();
|
|
||||||
for (var i = 0; i < rootViewInjectors.length; i++) {
|
|
||||||
this.visit(rootViewInjectors[i], aggregator);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.visit(this.originator, aggregator);
|
|
||||||
}
|
|
||||||
this.list.reset(aggregator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(inj: ElementInjector, aggregator: any[]): void {
|
private _update(): void {
|
||||||
if (isBlank(inj) || !inj._hasQuery(this) || !inj.hydrated) return;
|
var aggregator = [];
|
||||||
|
if (this.query.isViewQuery) {
|
||||||
|
var view = this.originator.getView();
|
||||||
|
// intentionally skipping originator for view queries.
|
||||||
|
var nestedView =
|
||||||
|
view.getNestedView(view.elementOffset + this.originator.getBoundElementIndex());
|
||||||
|
if (isPresent(nestedView)) this._visitView(nestedView, aggregator);
|
||||||
|
} else {
|
||||||
|
this._visit(this.originator, aggregator);
|
||||||
|
}
|
||||||
|
this.list.reset(aggregator);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _visit(inj: ElementInjector, aggregator: any[]): void {
|
||||||
|
var view = inj.getView();
|
||||||
|
var startIdx = view.elementOffset + inj._proto.index;
|
||||||
|
for (var i = startIdx; i < view.elementOffset + view.ownBindersCount; i++) {
|
||||||
|
var curInj = view.elementInjectors[i];
|
||||||
|
if (isBlank(curInj)) continue;
|
||||||
|
// The first injector after inj, that is outside the subtree rooted at
|
||||||
|
// inj has to have a null parent or a parent that is an ancestor of inj.
|
||||||
|
if (i > startIdx && (isBlank(curInj) || isBlank(curInj.parent) ||
|
||||||
|
view.elementOffset + curInj.parent._proto.index < startIdx)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.query.descendants &&
|
||||||
|
!(curInj.parent == this.originator || curInj == this.originator))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// We visit the view container(VC) views right after the injector that contains
|
||||||
|
// the VC. Theoretically, that might not be the right order if there are
|
||||||
|
// child injectors of said injector. Not clear whether if such case can
|
||||||
|
// even be constructed with the current apis.
|
||||||
|
this._visitInjector(curInj, aggregator);
|
||||||
|
var vc = view.viewContainers[i];
|
||||||
|
if (isPresent(vc)) this._visitViewContainer(vc, aggregator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _visitInjector(inj: ElementInjector, aggregator: any[]) {
|
||||||
if (this.query.isVarBindingQuery) {
|
if (this.query.isVarBindingQuery) {
|
||||||
this._aggregateVariableBindings(inj, aggregator);
|
this._aggregateVariableBindings(inj, aggregator);
|
||||||
} else {
|
} else {
|
||||||
this._aggregateDirective(inj, aggregator);
|
this._aggregateDirective(inj, aggregator);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var child = inj._head;
|
private _visitViewContainer(vc: viewModule.AppViewContainer, aggregator: any[]) {
|
||||||
while (isPresent(child)) {
|
for (var j = 0; j < vc.views.length; j++) {
|
||||||
this.visit(child, aggregator);
|
this._visitView(vc.views[j], aggregator);
|
||||||
child = child._next;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _visitView(view: viewModule.AppView, aggregator: any[]) {
|
||||||
|
for (var i = view.elementOffset; i < view.elementOffset + view.ownBindersCount; i++) {
|
||||||
|
var inj = view.elementInjectors[i];
|
||||||
|
if (isBlank(inj)) continue;
|
||||||
|
|
||||||
|
this._visitInjector(inj, aggregator);
|
||||||
|
|
||||||
|
var vc = view.viewContainers[i];
|
||||||
|
if (isPresent(vc)) this._visitViewContainer(vc, aggregator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1171,5 +1019,6 @@ export class QueryRef {
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this.list.reset([]);
|
this.list.reset([]);
|
||||||
this.list.removeAllCallbacks();
|
this.list.removeAllCallbacks();
|
||||||
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,7 +211,11 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyAfterViewChecked(): void {
|
notifyAfterViewChecked(): void {
|
||||||
// required for query
|
var eiCount = this.proto.elementBinders.length;
|
||||||
|
var ei = this.elementInjectors;
|
||||||
|
for (var i = eiCount - 1; i >= 0; i--) {
|
||||||
|
if (isPresent(ei[i + this.elementOffset])) ei[i + this.elementOffset].afterViewChecked();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getDirectiveFor(directive: DirectiveIndex): any {
|
getDirectiveFor(directive: DirectiveIndex): any {
|
||||||
|
@ -289,6 +293,8 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||||
throw new EventEvaluationError(eventName, e, e.stack, context);
|
throw new EventEvaluationError(eventName, e, e.stack, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get ownBindersCount(): number { return this.proto.elementBinders.length; }
|
||||||
}
|
}
|
||||||
|
|
||||||
function _localsToStringMap(locals: Locals): StringMap<string, any> {
|
function _localsToStringMap(locals: Locals): StringMap<string, any> {
|
||||||
|
|
|
@ -122,40 +122,30 @@ export class AppViewManagerUtils {
|
||||||
ListWrapper.insert(viewContainer.views, atIndex, view);
|
ListWrapper.insert(viewContainer.views, atIndex, view);
|
||||||
var elementInjector = contextView.elementInjectors[contextBoundElementIndex];
|
var elementInjector = contextView.elementInjectors[contextBoundElementIndex];
|
||||||
|
|
||||||
var sibling;
|
|
||||||
if (atIndex == 0) {
|
|
||||||
sibling = elementInjector;
|
|
||||||
} else {
|
|
||||||
sibling = ListWrapper.last(viewContainer.views[atIndex - 1].rootElementInjectors);
|
|
||||||
}
|
|
||||||
for (var i = view.rootElementInjectors.length - 1; i >= 0; i--) {
|
for (var i = view.rootElementInjectors.length - 1; i >= 0; i--) {
|
||||||
if (isPresent(elementInjector.parent)) {
|
if (isPresent(elementInjector.parent)) {
|
||||||
view.rootElementInjectors[i].linkAfter(elementInjector.parent, sibling);
|
view.rootElementInjectors[i].link(elementInjector.parent);
|
||||||
} else {
|
|
||||||
contextView.rootElementInjectors.push(view.rootElementInjectors[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
elementInjector.traverseAndSetQueriesAsDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
detachViewInContainer(parentView: viewModule.AppView, boundElementIndex: number,
|
detachViewInContainer(parentView: viewModule.AppView, boundElementIndex: number,
|
||||||
atIndex: number) {
|
atIndex: number) {
|
||||||
var viewContainer = parentView.viewContainers[boundElementIndex];
|
var viewContainer = parentView.viewContainers[boundElementIndex];
|
||||||
var view = viewContainer.views[atIndex];
|
var view = viewContainer.views[atIndex];
|
||||||
|
|
||||||
|
parentView.elementInjectors[boundElementIndex].traverseAndSetQueriesAsDirty();
|
||||||
|
|
||||||
view.changeDetector.remove();
|
view.changeDetector.remove();
|
||||||
ListWrapper.removeAt(viewContainer.views, atIndex);
|
ListWrapper.removeAt(viewContainer.views, atIndex);
|
||||||
for (var i = 0; i < view.rootElementInjectors.length; ++i) {
|
for (var i = 0; i < view.rootElementInjectors.length; ++i) {
|
||||||
var inj = view.rootElementInjectors[i];
|
var inj = view.rootElementInjectors[i];
|
||||||
if (isPresent(inj.parent)) {
|
inj.unlink();
|
||||||
inj.unlink();
|
|
||||||
} else {
|
|
||||||
var removeIdx = ListWrapper.indexOf(parentView.rootElementInjectors, inj);
|
|
||||||
if (removeIdx >= 0) {
|
|
||||||
ListWrapper.removeAt(parentView.rootElementInjectors, removeIdx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
hydrateViewInContainer(parentView: viewModule.AppView, boundElementIndex: number,
|
hydrateViewInContainer(parentView: viewModule.AppView, boundElementIndex: number,
|
||||||
contextView: viewModule.AppView, contextBoundElementIndex: number,
|
contextView: viewModule.AppView, contextBoundElementIndex: number,
|
||||||
atIndex: number, imperativelyCreatedBindings: ResolvedBinding[]) {
|
atIndex: number, imperativelyCreatedBindings: ResolvedBinding[]) {
|
||||||
|
|
|
@ -93,8 +93,8 @@ class Query extends QueryMetadata {
|
||||||
* See: [ViewQueryMetadata] for docs.
|
* See: [ViewQueryMetadata] for docs.
|
||||||
*/
|
*/
|
||||||
class ViewQuery extends ViewQueryMetadata {
|
class ViewQuery extends ViewQueryMetadata {
|
||||||
const ViewQuery(dynamic /*Type | string*/ selector, {bool descendants: false})
|
const ViewQuery(dynamic /*Type | string*/ selector)
|
||||||
: super(selector, descendants: descendants);
|
: super(selector, descendants: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -111,4 +111,4 @@ class Property extends PropertyMetadata {
|
||||||
class Event extends EventMetadata {
|
class Event extends EventMetadata {
|
||||||
const Event([String bindingPropertyName])
|
const Event([String bindingPropertyName])
|
||||||
: super(bindingPropertyName);
|
: super(bindingPropertyName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,12 +44,24 @@ import {TemplateRef} from 'angular2/src/core/compiler/template_ref';
|
||||||
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
|
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
|
||||||
import {DynamicChangeDetector, ChangeDetectorRef, Parser, Lexer} from 'angular2/src/core/change_detection/change_detection';
|
import {DynamicChangeDetector, ChangeDetectorRef, Parser, Lexer} from 'angular2/src/core/change_detection/change_detection';
|
||||||
import {QueryList} from 'angular2/src/core/compiler/query_list';
|
import {QueryList} from 'angular2/src/core/compiler/query_list';
|
||||||
|
import {AppView, AppViewContainer} from "angular2/src/core/compiler/view";
|
||||||
|
|
||||||
function createDummyView(detector = null) {
|
function createDummyView(detector = null): AppView {
|
||||||
var res = new SpyView();
|
var res = new SpyView();
|
||||||
res.prop("changeDetector", detector);
|
res.prop("changeDetector", detector);
|
||||||
res.prop("elementOffset", 0);
|
res.prop("elementOffset", 0);
|
||||||
return res;
|
res.prop("elementInjectors", []);
|
||||||
|
res.prop("viewContainers", []);
|
||||||
|
res.prop("ownBindersCount", 0);
|
||||||
|
return <any> res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addInj(view, inj) {
|
||||||
|
var injs: ElementInjector[] = view.elementInjectors;
|
||||||
|
injs.push(inj);
|
||||||
|
var containers: AppViewContainer[] = view.viewContainers;
|
||||||
|
containers.push(null);
|
||||||
|
view.prop("ownBindersCount", view.ownBindersCount + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -63,7 +75,7 @@ class SomeOtherDirective {}
|
||||||
var _constructionCount = 0;
|
var _constructionCount = 0;
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class CountingDirective {
|
class CountingDirective {
|
||||||
count;
|
count: number;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.count = _constructionCount;
|
this.count = _constructionCount;
|
||||||
_constructionCount += 1;
|
_constructionCount += 1;
|
||||||
|
@ -213,17 +225,8 @@ class DirectiveWithDestroy {
|
||||||
onDestroy() { this.onDestroyCounter++; }
|
onDestroy() { this.onDestroyCounter++; }
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestNode extends TreeNode<TestNode> {
|
|
||||||
message: string;
|
|
||||||
constructor(parent: TestNode, message) {
|
|
||||||
super(parent);
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
toString() { return this.message; }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
var defaultPreBuiltObjects = new PreBuiltObjects(null, <any>createDummyView(), <any>new SpyElementRef(), null);
|
var defaultPreBuiltObjects = new PreBuiltObjects(null, createDummyView(), <any>new SpyElementRef(), null);
|
||||||
|
|
||||||
// An injector with more than 10 bindings will switch to the dynamic strategy
|
// An injector with more than 10 bindings will switch to the dynamic strategy
|
||||||
var dynamicBindings = [];
|
var dynamicBindings = [];
|
||||||
|
@ -241,15 +244,6 @@ export function main() {
|
||||||
return ProtoElementInjector.create(parent, index, directiveBinding, hasShadowRoot, distance, dirVariableBindings);
|
return ProtoElementInjector.create(parent, index, directiveBinding, hasShadowRoot, distance, dirVariableBindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
function humanize(tree: TreeNode<any>, names: any[][]) {
|
|
||||||
var lookupName = (item) =>
|
|
||||||
ListWrapper.last(ListWrapper.find(names, (pair) => pair[0] === item));
|
|
||||||
|
|
||||||
if (tree.children.length == 0) return lookupName(tree);
|
|
||||||
var children = tree.children.map(m => humanize(m, names));
|
|
||||||
return [lookupName(tree), children];
|
|
||||||
}
|
|
||||||
|
|
||||||
function injector(bindings, imperativelyCreatedInjector = null, isComponent: boolean = false,
|
function injector(bindings, imperativelyCreatedInjector = null, isComponent: boolean = false,
|
||||||
preBuiltObjects = null, attributes = null, dirVariableBindings = null) {
|
preBuiltObjects = null, attributes = null, dirVariableBindings = null) {
|
||||||
var proto = createPei(null, 0, bindings, 0, isComponent, dirVariableBindings);
|
var proto = createPei(null, 0, bindings, 0, isComponent, dirVariableBindings);
|
||||||
|
@ -290,74 +284,19 @@ export function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('TreeNodes', () => {
|
describe('TreeNodes', () => {
|
||||||
var root, firstParent, lastParent, node;
|
var root, child;
|
||||||
|
|
||||||
/*
|
|
||||||
Build a tree of the following shape:
|
|
||||||
root
|
|
||||||
- p1
|
|
||||||
- c1
|
|
||||||
- c2
|
|
||||||
- p2
|
|
||||||
- c3
|
|
||||||
*/
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
root = new TestNode(null, 'root');
|
root = new TreeNode(null);
|
||||||
var p1 = firstParent = new TestNode(root, 'p1');
|
child = new TreeNode(root);
|
||||||
var p2 = lastParent = new TestNode(root, 'p2');
|
|
||||||
node = new TestNode(p1, 'c1');
|
|
||||||
new TestNode(p1, 'c2');
|
|
||||||
new TestNode(p2, 'c3');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// depth-first pre-order.
|
it('should support removing and adding the parent', () => {
|
||||||
function walk(node, f) {
|
expect(child.parent).toEqual(root);
|
||||||
if (isBlank(node)) return f;
|
child.remove();
|
||||||
f(node);
|
expect(child.parent).toEqual(null);
|
||||||
ListWrapper.forEach(node.children, (n) => walk(n, f));
|
root.addChild(child);
|
||||||
}
|
expect(child.parent).toEqual(root);
|
||||||
|
|
||||||
function logWalk(node) {
|
|
||||||
var log = '';
|
|
||||||
walk(node, (n) => { log += (log.length != 0 ? ', ' : '') + n.toString(); });
|
|
||||||
return log;
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should support listing children',
|
|
||||||
() => { expect(logWalk(root)).toEqual('root, p1, c1, c2, p2, c3'); });
|
|
||||||
|
|
||||||
it('should support removing the first child node', () => {
|
|
||||||
firstParent.remove();
|
|
||||||
|
|
||||||
expect(firstParent.parent).toEqual(null);
|
|
||||||
expect(logWalk(root)).toEqual('root, p2, c3');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support removing the last child node', () => {
|
|
||||||
lastParent.remove();
|
|
||||||
|
|
||||||
expect(logWalk(root)).toEqual('root, p1, c1, c2');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support moving a node at the end of children', () => {
|
|
||||||
node.remove();
|
|
||||||
root.addChild(node);
|
|
||||||
|
|
||||||
expect(logWalk(root)).toEqual('root, p1, c2, p2, c3, c1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support moving a node in the beginning of children', () => {
|
|
||||||
node.remove();
|
|
||||||
lastParent.addChildAfter(node, null);
|
|
||||||
|
|
||||||
expect(logWalk(root)).toEqual('root, p1, c2, p2, c1, c3');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support moving a node in the middle of children', () => {
|
|
||||||
node.remove();
|
|
||||||
lastParent.addChildAfter(node, firstParent);
|
|
||||||
|
|
||||||
expect(logWalk(root)).toEqual('root, p1, c2, c1, p2, c3');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -493,8 +432,9 @@ export function main() {
|
||||||
var c1 = protoChild1.instantiate(p);
|
var c1 = protoChild1.instantiate(p);
|
||||||
var c2 = protoChild2.instantiate(p);
|
var c2 = protoChild2.instantiate(p);
|
||||||
|
|
||||||
expect(humanize(p, [[p, 'parent'], [c1, 'child1'], [c2, 'child2']]))
|
expect(c1.parent).toEqual(p);
|
||||||
.toEqual(["parent", ["child1", "child2"]]);
|
expect(c2.parent).toEqual(p);
|
||||||
|
expect(isBlank(p.parent)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("direct parent", () => {
|
describe("direct parent", () => {
|
||||||
|
@ -906,38 +846,6 @@ export function main() {
|
||||||
extraBindings));
|
extraBindings));
|
||||||
inj.dehydrate();
|
inj.dehydrate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should notify queries", inject([AsyncTestCompleter], (async) => {
|
|
||||||
var inj = injector(ListWrapper.concat([NeedsQuery], extraBindings));
|
|
||||||
var query = inj.get(NeedsQuery).query;
|
|
||||||
query.add(new CountingDirective()); // this marks the query as dirty
|
|
||||||
|
|
||||||
query.onChange(() => async.done());
|
|
||||||
|
|
||||||
inj.afterContentChecked();
|
|
||||||
}));
|
|
||||||
|
|
||||||
it("should not notify inherited queries", inject([AsyncTestCompleter], (async) => {
|
|
||||||
var child = parentChildInjectors(ListWrapper.concat([NeedsQuery], extraBindings), []);
|
|
||||||
|
|
||||||
var query = child.parent.get(NeedsQuery).query;
|
|
||||||
|
|
||||||
var calledOnChange = false;
|
|
||||||
query.onChange(() => {
|
|
||||||
// make sure the callback is called only once
|
|
||||||
expect(calledOnChange).toEqual(false);
|
|
||||||
expect(query.length).toEqual(2);
|
|
||||||
|
|
||||||
calledOnChange = true;
|
|
||||||
async.done()
|
|
||||||
});
|
|
||||||
|
|
||||||
query.add(new CountingDirective());
|
|
||||||
child.afterContentChecked(); // this does not notify the query
|
|
||||||
|
|
||||||
query.add(new CountingDirective());
|
|
||||||
child.parent.afterContentChecked();
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('static attributes', () => {
|
describe('static attributes', () => {
|
||||||
|
@ -987,7 +895,7 @@ export function main() {
|
||||||
|
|
||||||
it("should inject ChangeDetectorRef of the containing component into directives", () => {
|
it("should inject ChangeDetectorRef of the containing component into directives", () => {
|
||||||
var cd = new DynamicChangeDetector(null, null, 0, [], [], null, [], [], [], null);
|
var cd = new DynamicChangeDetector(null, null, 0, [], [], null, [], [], [], null);
|
||||||
var view = <any>createDummyView(cd);
|
var view = createDummyView(cd);
|
||||||
var binding = DirectiveBinding.createFromType(DirectiveNeedsChangeDetectorRef, new DirectiveMetadata());
|
var binding = DirectiveBinding.createFromType(DirectiveNeedsChangeDetectorRef, new DirectiveMetadata());
|
||||||
var inj = injector(ListWrapper.concat([binding], extraBindings), null, false,
|
var inj = injector(ListWrapper.concat([binding], extraBindings), null, false,
|
||||||
new PreBuiltObjects(null, view, <any>new SpyElementRef(), null));
|
new PreBuiltObjects(null, view, <any>new SpyElementRef(), null));
|
||||||
|
@ -1022,11 +930,17 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('queries', () => {
|
describe('queries', () => {
|
||||||
var preBuildObjects = defaultPreBuiltObjects;
|
var dummyView;
|
||||||
beforeEach(() => { _constructionCount = 0; });
|
var preBuildObjects;
|
||||||
|
|
||||||
function expectDirectives(query, type, expectedIndex) {
|
beforeEach(() => { _constructionCount = 0;
|
||||||
|
dummyView = createDummyView();
|
||||||
|
preBuildObjects = new PreBuiltObjects(null, dummyView, <any>new SpyElementRef(), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
function expectDirectives(query: QueryList<any>, type, expectedIndex) {
|
||||||
var currentCount = 0;
|
var currentCount = 0;
|
||||||
|
expect(query.length).toEqual(expectedIndex.length);
|
||||||
iterateListLike(query, (i) => {
|
iterateListLike(query, (i) => {
|
||||||
expect(i).toBeAnInstanceOf(type);
|
expect(i).toBeAnInstanceOf(type);
|
||||||
expect(i.count).toBe(expectedIndex[currentCount]);
|
expect(i.count).toBe(expectedIndex[currentCount]);
|
||||||
|
@ -1047,51 +961,25 @@ export function main() {
|
||||||
], extraBindings), null,
|
], extraBindings), null,
|
||||||
false, preBuildObjects);
|
false, preBuildObjects);
|
||||||
|
|
||||||
|
addInj(dummyView, inj);
|
||||||
|
inj.afterContentChecked();
|
||||||
|
|
||||||
expectDirectives(inj.get(NeedsQuery).query, CountingDirective, [0]);
|
expectDirectives(inj.get(NeedsQuery).query, CountingDirective, [0]);
|
||||||
})
|
});
|
||||||
|
|
||||||
it('should contain PreBuiltObjects on the same injector', () => {
|
it('should contain PreBuiltObjects on the same injector', () => {
|
||||||
var preBuiltObjects = new PreBuiltObjects(null, null, null, new TemplateRef(<any>new SpyElementRef()));
|
var preBuiltObjects = new PreBuiltObjects(null, dummyView, null, new TemplateRef(<any>new SpyElementRef()));
|
||||||
var inj = injector(ListWrapper.concat([
|
var inj = injector(ListWrapper.concat([
|
||||||
NeedsTemplateRefQuery
|
NeedsTemplateRefQuery
|
||||||
], extraBindings), null,
|
], extraBindings), null,
|
||||||
false, preBuiltObjects);
|
false, preBuiltObjects);
|
||||||
|
addInj(dummyView, inj);
|
||||||
|
|
||||||
|
inj.afterContentChecked();
|
||||||
|
|
||||||
expect(inj.get(NeedsTemplateRefQuery).query.first).toBe(preBuiltObjects.templateRef);
|
expect(inj.get(NeedsTemplateRefQuery).query.first).toBe(preBuiltObjects.templateRef);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain multiple directives from the same injector', () => {
|
|
||||||
var inj = injector(ListWrapper.concat([
|
|
||||||
NeedsQuery,
|
|
||||||
CountingDirective,
|
|
||||||
FancyCountingDirective,
|
|
||||||
bind(CountingDirective).toAlias(FancyCountingDirective)
|
|
||||||
], extraBindings), null,
|
|
||||||
false, preBuildObjects);
|
|
||||||
|
|
||||||
expect(inj.get(NeedsQuery).query.length).toEqual(2);
|
|
||||||
expect(inj.get(NeedsQuery).query.first).toBeAnInstanceOf(CountingDirective);
|
|
||||||
expect(inj.get(NeedsQuery).query.last).toBeAnInstanceOf(FancyCountingDirective);
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should contain multiple directives from the same injector after linking', () => {
|
|
||||||
var inj = parentChildInjectors([], ListWrapper.concat([
|
|
||||||
NeedsQuery,
|
|
||||||
CountingDirective,
|
|
||||||
FancyCountingDirective,
|
|
||||||
bind(CountingDirective).toAlias(FancyCountingDirective)
|
|
||||||
], extraBindings));
|
|
||||||
|
|
||||||
var parent = inj.parent;
|
|
||||||
|
|
||||||
inj.unlink();
|
|
||||||
inj.link(parent);
|
|
||||||
|
|
||||||
expect(inj.get(NeedsQuery).query.length).toEqual(2);
|
|
||||||
expect(inj.get(NeedsQuery).query.first).toBeAnInstanceOf(CountingDirective);
|
|
||||||
expect(inj.get(NeedsQuery).query.last).toBeAnInstanceOf(FancyCountingDirective);
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should contain the element when no directives are bound to the var binding', () => {
|
it('should contain the element when no directives are bound to the var binding', () => {
|
||||||
var dirs = [NeedsQueryByVarBindings];
|
var dirs = [NeedsQueryByVarBindings];
|
||||||
|
|
||||||
|
@ -1102,7 +990,10 @@ export function main() {
|
||||||
var inj = injector(dirs.concat(extraBindings), null,
|
var inj = injector(dirs.concat(extraBindings), null,
|
||||||
false, preBuildObjects, null, dirVariableBindings);
|
false, preBuildObjects, null, dirVariableBindings);
|
||||||
|
|
||||||
expect(inj.get(NeedsQueryByVarBindings).query.first).toBe(defaultPreBuiltObjects.elementRef);
|
addInj(dummyView, inj);
|
||||||
|
inj.afterContentChecked();
|
||||||
|
|
||||||
|
expect(inj.get(NeedsQueryByVarBindings).query.first).toBe(preBuildObjects.elementRef);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain directives on the same injector when querying by variable bindings' +
|
it('should contain directives on the same injector when querying by variable bindings' +
|
||||||
|
@ -1117,21 +1008,14 @@ export function main() {
|
||||||
var inj = injector(dirs.concat(extraBindings), null,
|
var inj = injector(dirs.concat(extraBindings), null,
|
||||||
false, preBuildObjects, null, dirVariableBindings);
|
false, preBuildObjects, null, dirVariableBindings);
|
||||||
|
|
||||||
|
addInj(dummyView, inj);
|
||||||
|
inj.afterContentChecked();
|
||||||
|
|
||||||
// NeedsQueryByVarBindings queries "one,two", so SimpleDirective should be before NeedsDirective
|
// NeedsQueryByVarBindings queries "one,two", so SimpleDirective should be before NeedsDirective
|
||||||
expect(inj.get(NeedsQueryByVarBindings).query.first).toBeAnInstanceOf(SimpleDirective);
|
expect(inj.get(NeedsQueryByVarBindings).query.first).toBeAnInstanceOf(SimpleDirective);
|
||||||
expect(inj.get(NeedsQueryByVarBindings).query.last).toBeAnInstanceOf(NeedsDirective);
|
expect(inj.get(NeedsQueryByVarBindings).query.last).toBeAnInstanceOf(NeedsDirective);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dart's restriction on static types in (a is A) makes this feature hard to implement.
|
|
||||||
// Current proposal is to add second parameter the Query constructor to take a
|
|
||||||
// comparison function to support user-defined definition of matching.
|
|
||||||
|
|
||||||
//it('should support super class directives', () => {
|
|
||||||
// var inj = injector([NeedsQuery, FancyCountingDirective], null, null, preBuildObjects);
|
|
||||||
//
|
|
||||||
// expectDirectives(inj.get(NeedsQuery).query, FancyCountingDirective, [0]);
|
|
||||||
//});
|
|
||||||
|
|
||||||
it('should contain directives on the same and a child injector in construction order', () => {
|
it('should contain directives on the same and a child injector in construction order', () => {
|
||||||
var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]);
|
var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]);
|
||||||
var protoChild =
|
var protoChild =
|
||||||
|
@ -1142,90 +1026,12 @@ export function main() {
|
||||||
parent.hydrate(null, null, preBuildObjects);
|
parent.hydrate(null, null, preBuildObjects);
|
||||||
child.hydrate(null, null, preBuildObjects);
|
child.hydrate(null, null, preBuildObjects);
|
||||||
|
|
||||||
|
addInj(dummyView, parent);
|
||||||
|
addInj(dummyView, child);
|
||||||
|
parent.afterContentChecked();
|
||||||
|
|
||||||
expectDirectives(parent.get(NeedsQuery).query, CountingDirective, [0, 1]);
|
expectDirectives(parent.get(NeedsQuery).query, CountingDirective, [0, 1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reflect unlinking an injector', () => {
|
|
||||||
var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]);
|
|
||||||
var protoChild =
|
|
||||||
createPei(protoParent, 1, ListWrapper.concat([CountingDirective], extraBindings));
|
|
||||||
|
|
||||||
var parent = protoParent.instantiate(null);
|
|
||||||
var child = protoChild.instantiate(parent);
|
|
||||||
parent.hydrate(null, null, preBuildObjects);
|
|
||||||
child.hydrate(null, null, preBuildObjects);
|
|
||||||
|
|
||||||
child.unlink();
|
|
||||||
|
|
||||||
expectDirectives(parent.get(NeedsQuery).query, CountingDirective, [0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reflect moving an injector as a last child', () => {
|
|
||||||
var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]);
|
|
||||||
var protoChild1 = createPei(protoParent, 1, [CountingDirective]);
|
|
||||||
var protoChild2 =
|
|
||||||
createPei(protoParent, 1, ListWrapper.concat([CountingDirective], extraBindings));
|
|
||||||
|
|
||||||
var parent = protoParent.instantiate(null);
|
|
||||||
var child1 = protoChild1.instantiate(parent);
|
|
||||||
var child2 = protoChild2.instantiate(parent);
|
|
||||||
|
|
||||||
parent.hydrate(null, null, preBuildObjects);
|
|
||||||
child1.hydrate(null, null, preBuildObjects);
|
|
||||||
child2.hydrate(null, null, preBuildObjects);
|
|
||||||
|
|
||||||
child1.unlink();
|
|
||||||
child1.link(parent);
|
|
||||||
|
|
||||||
var queryList = parent.get(NeedsQuery).query;
|
|
||||||
expectDirectives(queryList, CountingDirective, [0, 2, 1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reflect moving an injector as a first child', () => {
|
|
||||||
var protoParent = createPei(null, 0, [NeedsQuery, CountingDirective]);
|
|
||||||
var protoChild1 = createPei(protoParent, 1, [CountingDirective]);
|
|
||||||
var protoChild2 =
|
|
||||||
createPei(protoParent, 1, ListWrapper.concat([CountingDirective], extraBindings));
|
|
||||||
|
|
||||||
var parent = protoParent.instantiate(null);
|
|
||||||
var child1 = protoChild1.instantiate(parent);
|
|
||||||
var child2 = protoChild2.instantiate(parent);
|
|
||||||
|
|
||||||
parent.hydrate(null, null, preBuildObjects);
|
|
||||||
child1.hydrate(null, null, preBuildObjects);
|
|
||||||
child2.hydrate(null, null, preBuildObjects);
|
|
||||||
|
|
||||||
child2.unlink();
|
|
||||||
child2.linkAfter(parent, null);
|
|
||||||
|
|
||||||
var queryList = parent.get(NeedsQuery).query;
|
|
||||||
expectDirectives(queryList, CountingDirective, [0, 2, 1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support two concurrent queries for the same directive', () => {
|
|
||||||
var protoGrandParent = createPei(null, 0, [NeedsQuery]);
|
|
||||||
var protoParent = createPei(null, 0, [NeedsQuery]);
|
|
||||||
var protoChild =
|
|
||||||
createPei(protoParent, 1, ListWrapper.concat([CountingDirective], extraBindings));
|
|
||||||
|
|
||||||
var grandParent = protoGrandParent.instantiate(null);
|
|
||||||
var parent = protoParent.instantiate(grandParent);
|
|
||||||
var child = protoChild.instantiate(parent);
|
|
||||||
|
|
||||||
grandParent.hydrate(null, null, preBuildObjects);
|
|
||||||
parent.hydrate(null, null, preBuildObjects);
|
|
||||||
child.hydrate(null, null, preBuildObjects);
|
|
||||||
|
|
||||||
var queryList1 = grandParent.get(NeedsQuery).query;
|
|
||||||
var queryList2 = parent.get(NeedsQuery).query;
|
|
||||||
|
|
||||||
expectDirectives(queryList1, CountingDirective, [0]);
|
|
||||||
expectDirectives(queryList2, CountingDirective, [0]);
|
|
||||||
|
|
||||||
child.unlink();
|
|
||||||
expectDirectives(queryList1, CountingDirective, []);
|
|
||||||
expectDirectives(queryList2, CountingDirective, []);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -382,30 +382,13 @@ export function main() {
|
||||||
|
|
||||||
view.detectChanges();
|
view.detectChanges();
|
||||||
|
|
||||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(["1", "2", "3"]);
|
|
||||||
|
|
||||||
async.done();
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should query descendants in the view when the flag is used',
|
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
|
||||||
var template = '<needs-view-query-desc #q></needs-view-query-desc>';
|
|
||||||
|
|
||||||
tcb.overrideTemplate(MyComp, template)
|
|
||||||
.createAsync(MyComp)
|
|
||||||
.then((view) => {
|
|
||||||
var q: NeedsViewQueryDesc = view.componentViewChildren[0].getLocal("q");
|
|
||||||
|
|
||||||
view.detectChanges();
|
|
||||||
|
|
||||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(["1", "2", "3", "4"]);
|
expect(q.query.map((d: TextDirective) => d.text)).toEqual(["1", "2", "3", "4"]);
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should include directive present on the host element',
|
it('should not include directive present on the host element',
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
var template = '<needs-view-query #q text="self"></needs-view-query>';
|
var template = '<needs-view-query #q text="self"></needs-view-query>';
|
||||||
|
|
||||||
|
@ -416,7 +399,7 @@ export function main() {
|
||||||
|
|
||||||
view.detectChanges();
|
view.detectChanges();
|
||||||
|
|
||||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(["1", "2", "3"]);
|
expect(q.query.map((d: TextDirective) => d.text)).toEqual(["1", "2", "3", "4"]);
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -437,6 +420,7 @@ export function main() {
|
||||||
|
|
||||||
q.show = true;
|
q.show = true;
|
||||||
view.detectChanges();
|
view.detectChanges();
|
||||||
|
expect(q.query.length).toBe(1);
|
||||||
|
|
||||||
expect(q.query.first.text).toEqual("1");
|
expect(q.query.first.text).toEqual("1");
|
||||||
|
|
||||||
|
@ -486,6 +470,29 @@ export function main() {
|
||||||
view.detectChanges();
|
view.detectChanges();
|
||||||
|
|
||||||
|
|
||||||
|
expect(q.query.map((d: TextDirective) => d.text)).toEqual(["1", "-3", "2", "4"]);
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should maintain directives in pre-order depth-first DOM order after dynamic insertion',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||||
|
var template = '<needs-view-query-order-with-p #q></needs-view-query-order-with-p>';
|
||||||
|
|
||||||
|
tcb.overrideTemplate(MyComp, template)
|
||||||
|
.createAsync(MyComp)
|
||||||
|
.then((view) => {
|
||||||
|
var q: NeedsViewQueryOrderWithParent = view.componentViewChildren[0].getLocal("q");
|
||||||
|
|
||||||
|
view.detectChanges();
|
||||||
|
|
||||||
|
expect(q.query.map((d: TextDirective) => d.text)).toEqual(["1", "2", "3", "4"]);
|
||||||
|
|
||||||
|
q.list = ["-3", "2"];
|
||||||
|
view.detectChanges();
|
||||||
|
|
||||||
|
|
||||||
expect(q.query.map((d: TextDirective) => d.text)).toEqual(["1", "-3", "2", "4"]);
|
expect(q.query.map((d: TextDirective) => d.text)).toEqual(["1", "-3", "2", "4"]);
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
|
@ -566,9 +573,7 @@ class NeedsQueryByLabel {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class NeedsViewQueryByLabel {
|
class NeedsViewQueryByLabel {
|
||||||
query: QueryList<any>;
|
query: QueryList<any>;
|
||||||
constructor(@ViewQuery("textLabel", {descendants: true}) query: QueryList<any>) {
|
constructor(@ViewQuery("textLabel") query: QueryList<any>) { this.query = query; }
|
||||||
this.query = query;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'needs-query-by-var-bindings'})
|
@Component({selector: 'needs-query-by-var-bindings'})
|
||||||
|
@ -595,8 +600,8 @@ class NeedsQueryAndProject {
|
||||||
@Component({selector: 'needs-view-query'})
|
@Component({selector: 'needs-view-query'})
|
||||||
@View({
|
@View({
|
||||||
directives: [TextDirective],
|
directives: [TextDirective],
|
||||||
template: '<div text="1"><div text="need descendants"></div></div>' +
|
template: '<div text="1"><div text="2"></div></div>' +
|
||||||
'<div text="2"></div><div text="3"></div>'
|
'<div text="3"></div><div text="4"></div>'
|
||||||
})
|
})
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class NeedsViewQuery {
|
class NeedsViewQuery {
|
||||||
|
@ -604,20 +609,6 @@ class NeedsViewQuery {
|
||||||
constructor(@ViewQuery(TextDirective) query: QueryList<TextDirective>) { this.query = query; }
|
constructor(@ViewQuery(TextDirective) query: QueryList<TextDirective>) { this.query = query; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'needs-view-query-desc'})
|
|
||||||
@View({
|
|
||||||
directives: [TextDirective],
|
|
||||||
template: '<div text="1"><div text="2"></div></div>' +
|
|
||||||
'<div text="3"></div><div text="4"></div>'
|
|
||||||
})
|
|
||||||
@Injectable()
|
|
||||||
class NeedsViewQueryDesc {
|
|
||||||
query: QueryList<TextDirective>;
|
|
||||||
constructor(@ViewQuery(TextDirective, {descendants: true}) query: QueryList<TextDirective>) {
|
|
||||||
this.query = query;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({selector: 'needs-view-query-if'})
|
@Component({selector: 'needs-view-query-if'})
|
||||||
@View({directives: [NgIf, TextDirective], template: '<div *ng-if="show" text="1"></div>'})
|
@View({directives: [NgIf, TextDirective], template: '<div *ng-if="show" text="1"></div>'})
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -646,10 +637,24 @@ class NeedsViewQueryNestedIf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO(rado): once https://github.com/angular/angular/issues/3438 is resolved
|
|
||||||
// duplicate the test without InertDirective.
|
|
||||||
@Component({selector: 'needs-view-query-order'})
|
@Component({selector: 'needs-view-query-order'})
|
||||||
|
@View({
|
||||||
|
directives: [NgFor, TextDirective, InertDirective],
|
||||||
|
template: '<div text="1"></div>' +
|
||||||
|
'<div *ng-for="var i of list" [text]="i"></div>' +
|
||||||
|
'<div text="4"></div>'
|
||||||
|
})
|
||||||
|
@Injectable()
|
||||||
|
class NeedsViewQueryOrder {
|
||||||
|
query: QueryList<TextDirective>;
|
||||||
|
list: string[];
|
||||||
|
constructor(@ViewQuery(TextDirective) query: QueryList<TextDirective>) {
|
||||||
|
this.query = query;
|
||||||
|
this.list = ['2', '3'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'needs-view-query-order-with-p'})
|
||||||
@View({
|
@View({
|
||||||
directives: [NgFor, TextDirective, InertDirective],
|
directives: [NgFor, TextDirective, InertDirective],
|
||||||
template: '<div dir><div text="1"></div>' +
|
template: '<div dir><div text="1"></div>' +
|
||||||
|
@ -657,10 +662,10 @@ class NeedsViewQueryNestedIf {
|
||||||
'<div text="4"></div></div>'
|
'<div text="4"></div></div>'
|
||||||
})
|
})
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class NeedsViewQueryOrder {
|
class NeedsViewQueryOrderWithParent {
|
||||||
query: QueryList<TextDirective>;
|
query: QueryList<TextDirective>;
|
||||||
list: string[];
|
list: string[];
|
||||||
constructor(@ViewQuery(TextDirective, {descendants: true}) query: QueryList<TextDirective>) {
|
constructor(@ViewQuery(TextDirective) query: QueryList<TextDirective>) {
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.list = ['2', '3'];
|
this.list = ['2', '3'];
|
||||||
}
|
}
|
||||||
|
@ -687,11 +692,11 @@ class NeedsTpl {
|
||||||
NeedsQueryByTwoLabels,
|
NeedsQueryByTwoLabels,
|
||||||
NeedsQueryAndProject,
|
NeedsQueryAndProject,
|
||||||
NeedsViewQuery,
|
NeedsViewQuery,
|
||||||
NeedsViewQueryDesc,
|
|
||||||
NeedsViewQueryIf,
|
NeedsViewQueryIf,
|
||||||
NeedsViewQueryNestedIf,
|
NeedsViewQueryNestedIf,
|
||||||
NeedsViewQueryOrder,
|
NeedsViewQueryOrder,
|
||||||
NeedsViewQueryByLabel,
|
NeedsViewQueryByLabel,
|
||||||
|
NeedsViewQueryOrderWithParent,
|
||||||
NeedsTpl,
|
NeedsTpl,
|
||||||
TextDirective,
|
TextDirective,
|
||||||
InertDirective,
|
InertDirective,
|
||||||
|
|
|
@ -131,24 +131,23 @@ export function main() {
|
||||||
var binders = [];
|
var binders = [];
|
||||||
for (var i = 0; i < numInj; i++) {
|
for (var i = 0; i < numInj; i++) {
|
||||||
binders.push(createEmptyElBinder(i > 0 ? binders[i - 1] : null))
|
binders.push(createEmptyElBinder(i > 0 ? binders[i - 1] : null))
|
||||||
};
|
}
|
||||||
var contextPv = createHostPv(binders);
|
var contextPv = createHostPv(binders);
|
||||||
contextView = createViewWithChildren(contextPv);
|
contextView = createViewWithChildren(contextPv);
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should link the views rootElementInjectors at the given context', () => {
|
it('should not modify the rootElementInjectors at the given context view', () => {
|
||||||
createViews();
|
createViews();
|
||||||
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
|
utils.attachViewInContainer(parentView, 0, contextView, 0, 0, childView);
|
||||||
expect(contextView.rootElementInjectors.length).toEqual(2);
|
expect(contextView.rootElementInjectors.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should link the views rootElementInjectors after the elementInjector at the given context',
|
it('should link the views rootElementInjectors after the elementInjector at the given context',
|
||||||
() => {
|
() => {
|
||||||
createViews(2);
|
createViews(2);
|
||||||
utils.attachViewInContainer(parentView, 0, contextView, 1, 0, childView);
|
utils.attachViewInContainer(parentView, 0, contextView, 1, 0, childView);
|
||||||
expect(childView.rootElementInjectors[0].spy('linkAfter'))
|
expect(childView.rootElementInjectors[0].spy('link'))
|
||||||
.toHaveBeenCalledWith(contextView.elementInjectors[0],
|
.toHaveBeenCalledWith(contextView.elementInjectors[0]);
|
||||||
contextView.elementInjectors[1]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue