feat(query): adds support for descendants and more list apis.
Additional clean up of query code. Closes: #1935 BREAKING CHANGE: By default Query only queries direct children.
This commit is contained in:
parent
ca09701343
commit
355ab5b3a6
|
@ -231,14 +231,13 @@ import {DEFAULT} from 'angular2/change_detection';
|
||||||
*
|
*
|
||||||
* ### Injecting a live collection of descendant directives
|
* ### Injecting a live collection of descendant directives
|
||||||
*
|
*
|
||||||
* Note: This is will be implemented in later release. ()
|
* By passing the descendant flag to `@Query` above, we can include the children of the child
|
||||||
*
|
* elements.
|
||||||
* Similar to `@Query` above, but also includes the children of the child elements.
|
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* @Directive({ selector: '[my-directive]' })
|
* @Directive({ selector: '[my-directive]' })
|
||||||
* class MyDirective {
|
* class MyDirective {
|
||||||
* constructor(@QueryDescendents(Dependency) dependencies:QueryList<Dependency>) {
|
* constructor(@Query(Dependency, {descendants: true}) dependencies:QueryList<Dependency>) {
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {CONST, stringify} from 'angular2/src/facade/lang';
|
import {CONST, stringify, isPresent} from 'angular2/src/facade/lang';
|
||||||
import {DependencyAnnotation} from 'angular2/src/di/annotations_impl';
|
import {DependencyAnnotation} from 'angular2/src/di/annotations_impl';
|
||||||
|
import {resolveForwardRef} from 'angular2/di';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies that a constant attribute value should be injected.
|
* Specifies that a constant attribute value should be injected.
|
||||||
|
@ -53,6 +54,13 @@ export class Attribute extends DependencyAnnotation {
|
||||||
*/
|
*/
|
||||||
@CONST()
|
@CONST()
|
||||||
export class Query extends DependencyAnnotation {
|
export class Query extends DependencyAnnotation {
|
||||||
constructor(public directive: any) { super(); }
|
descendants: boolean;
|
||||||
|
constructor(private _directive: any, {descendants = false}: {descendants?: boolean} = {}) {
|
||||||
|
super();
|
||||||
|
this.descendants = descendants;
|
||||||
|
}
|
||||||
|
|
||||||
|
get directive() { return resolveForwardRef(this._directive); }
|
||||||
|
|
||||||
toString() { return `@Query(${stringify(this.directive)})`; }
|
toString() { return `@Query(${stringify(this.directive)})`; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,4 +46,8 @@ class BaseQueryList<T> extends Object with IterableMixin<T> {
|
||||||
removeCallback(callback) {
|
removeCallback(callback) {
|
||||||
this._callbacks.remove(callback);
|
this._callbacks.remove(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get length => this._results.length;
|
||||||
|
get first => this._results.first;
|
||||||
|
get last => this._results.last;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injectable Objects that contains a live list of child directives in the light Dom of a directive.
|
* Injectable Objects that contains a live list of child directives in the light Dom of a directive.
|
||||||
|
@ -10,9 +10,9 @@ import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
* @exportedAs angular2/view
|
* @exportedAs angular2/view
|
||||||
*/
|
*/
|
||||||
export class BaseQueryList<T> {
|
export class BaseQueryList<T> {
|
||||||
_results: List<T>;
|
protected _results: List<T>;
|
||||||
_callbacks;
|
protected _callbacks;
|
||||||
_dirty;
|
protected _dirty;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._results = [];
|
this._results = [];
|
||||||
|
@ -43,4 +43,10 @@ export class BaseQueryList<T> {
|
||||||
onChange(callback) { ListWrapper.push(this._callbacks, callback); }
|
onChange(callback) { ListWrapper.push(this._callbacks, callback); }
|
||||||
|
|
||||||
removeCallback(callback) { ListWrapper.remove(this._callbacks, callback); }
|
removeCallback(callback) { ListWrapper.remove(this._callbacks, callback); }
|
||||||
|
|
||||||
|
get length() { return this._results.length; }
|
||||||
|
|
||||||
|
get first() { return ListWrapper.first(this._results); }
|
||||||
|
|
||||||
|
get last() { return ListWrapper.last(this._results); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,35 +82,6 @@ export class TreeNode<T extends TreeNode<any>> {
|
||||||
if (isPresent(parent)) parent.addChild(this);
|
if (isPresent(parent)) parent.addChild(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_assertConsistency(): void {
|
|
||||||
this._assertHeadBeforeTail();
|
|
||||||
this._assertTailReachable();
|
|
||||||
this._assertPresentInParentList();
|
|
||||||
}
|
|
||||||
|
|
||||||
_assertHeadBeforeTail(): void {
|
|
||||||
if (isBlank(this._tail) && isPresent(this._head))
|
|
||||||
throw new BaseException('null tail but non-null head');
|
|
||||||
}
|
|
||||||
|
|
||||||
_assertTailReachable(): void {
|
|
||||||
if (isBlank(this._tail)) return;
|
|
||||||
if (isPresent(this._tail._next)) throw new BaseException('node after tail');
|
|
||||||
var p = this._head;
|
|
||||||
while (isPresent(p) && p != this._tail) p = p._next;
|
|
||||||
if (isBlank(p) && isPresent(this._tail)) throw new BaseException('tail not reachable.')
|
|
||||||
}
|
|
||||||
|
|
||||||
_assertPresentInParentList(): void {
|
|
||||||
var p = this._parent;
|
|
||||||
if (isBlank(p)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var cur = p._head;
|
|
||||||
while (isPresent(cur) && cur != this) cur = cur._next;
|
|
||||||
if (isBlank(cur)) throw new BaseException('node not reachable through parent.')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a child to the parent node. The child MUST NOT be a part of a tree.
|
* Adds a child to the parent node. The child MUST NOT be a part of a tree.
|
||||||
*/
|
*/
|
||||||
|
@ -123,7 +94,6 @@ export class TreeNode<T extends TreeNode<any>> {
|
||||||
}
|
}
|
||||||
child._next = null;
|
child._next = null;
|
||||||
child._parent = this;
|
child._parent = this;
|
||||||
this._assertConsistency();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,7 +101,6 @@ export class TreeNode<T extends TreeNode<any>> {
|
||||||
* The child MUST NOT be a part of a tree and the sibling must be present.
|
* The child MUST NOT be a part of a tree and the sibling must be present.
|
||||||
*/
|
*/
|
||||||
addChildAfter(child: T, prevSibling: T): void {
|
addChildAfter(child: T, prevSibling: T): void {
|
||||||
this._assertConsistency();
|
|
||||||
if (isBlank(prevSibling)) {
|
if (isBlank(prevSibling)) {
|
||||||
var prevHead = this._head;
|
var prevHead = this._head;
|
||||||
this._head = child;
|
this._head = child;
|
||||||
|
@ -141,19 +110,16 @@ export class TreeNode<T extends TreeNode<any>> {
|
||||||
this.addChild(child);
|
this.addChild(child);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
prevSibling._assertPresentInParentList();
|
|
||||||
child._next = prevSibling._next;
|
child._next = prevSibling._next;
|
||||||
prevSibling._next = child;
|
prevSibling._next = child;
|
||||||
}
|
}
|
||||||
child._parent = this;
|
child._parent = this;
|
||||||
this._assertConsistency();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detaches a node from the parent's tree.
|
* Detaches a node from the parent's tree.
|
||||||
*/
|
*/
|
||||||
remove(): void {
|
remove(): void {
|
||||||
this._assertConsistency();
|
|
||||||
if (isBlank(this.parent)) return;
|
if (isBlank(this.parent)) return;
|
||||||
var nextSibling = this._next;
|
var nextSibling = this._next;
|
||||||
var prevSibling = this._findPrev();
|
var prevSibling = this._findPrev();
|
||||||
|
@ -165,10 +131,8 @@ export class TreeNode<T extends TreeNode<any>> {
|
||||||
if (isBlank(nextSibling)) {
|
if (isBlank(nextSibling)) {
|
||||||
this._parent._tail = prevSibling;
|
this._parent._tail = prevSibling;
|
||||||
}
|
}
|
||||||
this._parent._assertConsistency();
|
|
||||||
this._parent = null;
|
this._parent = null;
|
||||||
this._next = null;
|
this._next = null;
|
||||||
this._assertConsistency();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -217,14 +181,14 @@ export class DependencyWithVisibility extends Dependency {
|
||||||
|
|
||||||
export class DirectiveDependency extends DependencyWithVisibility {
|
export class DirectiveDependency extends DependencyWithVisibility {
|
||||||
constructor(key: Key, asPromise: boolean, lazy: boolean, optional: boolean, properties: List<any>,
|
constructor(key: Key, asPromise: boolean, lazy: boolean, optional: boolean, properties: List<any>,
|
||||||
visibility: Visibility, public attributeName: string, public queryDirective) {
|
visibility: Visibility, public attributeName: string, public queryDecorator: Query) {
|
||||||
super(key, asPromise, lazy, optional, properties, visibility);
|
super(key, asPromise, lazy, optional, properties, visibility);
|
||||||
this._verify();
|
this._verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
_verify(): void {
|
_verify(): void {
|
||||||
var count = 0;
|
var count = 0;
|
||||||
if (isPresent(this.queryDirective)) count++;
|
if (isPresent(this.queryDecorator)) count++;
|
||||||
if (isPresent(this.attributeName)) count++;
|
if (isPresent(this.attributeName)) count++;
|
||||||
if (count > 1)
|
if (count > 1)
|
||||||
throw new BaseException(
|
throw new BaseException(
|
||||||
|
@ -243,10 +207,7 @@ export class DirectiveDependency extends DependencyWithVisibility {
|
||||||
return isPresent(p) ? p.attributeName : null;
|
return isPresent(p) ? p.attributeName : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static _query(properties) {
|
static _query(properties) { return ListWrapper.find(properties, (p) => p instanceof Query); }
|
||||||
var p = ListWrapper.find(properties, (p) => p instanceof Query);
|
|
||||||
return isPresent(p) ? resolveForwardRef(p.directive) : null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DirectiveBinding extends ResolvedBinding {
|
export class DirectiveBinding extends ResolvedBinding {
|
||||||
|
@ -706,6 +667,8 @@ export class ElementInjector extends TreeNode<ElementInjector> {
|
||||||
private _query1: QueryRef;
|
private _query1: QueryRef;
|
||||||
private _query2: QueryRef;
|
private _query2: QueryRef;
|
||||||
|
|
||||||
|
hydrated: boolean;
|
||||||
|
|
||||||
_strategy: _ElementInjectorStrategy;
|
_strategy: _ElementInjectorStrategy;
|
||||||
|
|
||||||
constructor(public _proto: ProtoElementInjector, parent: ElementInjector) {
|
constructor(public _proto: ProtoElementInjector, parent: ElementInjector) {
|
||||||
|
@ -713,12 +676,14 @@ export class ElementInjector extends TreeNode<ElementInjector> {
|
||||||
this._strategy = _proto._strategy.createElementInjectorStrategy(this);
|
this._strategy = _proto._strategy.createElementInjectorStrategy(this);
|
||||||
|
|
||||||
this._constructionCounter = 0;
|
this._constructionCounter = 0;
|
||||||
|
this.hydrated = false;
|
||||||
|
|
||||||
this._inheritQueries(parent);
|
|
||||||
this._buildQueries();
|
this._buildQueries();
|
||||||
|
this._addParentQueries();
|
||||||
}
|
}
|
||||||
|
|
||||||
dehydrate(): void {
|
dehydrate(): void {
|
||||||
|
this.hydrated = false;
|
||||||
this._host = null;
|
this._host = null;
|
||||||
this._preBuiltObjects = null;
|
this._preBuiltObjects = null;
|
||||||
this._lightDomAppInjector = null;
|
this._lightDomAppInjector = null;
|
||||||
|
@ -755,6 +720,7 @@ export class ElementInjector extends TreeNode<ElementInjector> {
|
||||||
this._checkShadowDomAppInjector(this._shadowDomAppInjector);
|
this._checkShadowDomAppInjector(this._shadowDomAppInjector);
|
||||||
|
|
||||||
this._strategy.hydrate();
|
this._strategy.hydrate();
|
||||||
|
this.hydrated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createShadowDomAppInjector(componentDirective: DirectiveBinding,
|
private _createShadowDomAppInjector(componentDirective: DirectiveBinding,
|
||||||
|
@ -906,7 +872,7 @@ export class ElementInjector extends TreeNode<ElementInjector> {
|
||||||
var dirDep = <DirectiveDependency>dep;
|
var dirDep = <DirectiveDependency>dep;
|
||||||
|
|
||||||
if (isPresent(dirDep.attributeName)) return this._buildAttribute(dirDep);
|
if (isPresent(dirDep.attributeName)) return this._buildAttribute(dirDep);
|
||||||
if (isPresent(dirDep.queryDirective)) return this._findQuery(dirDep.queryDirective).list;
|
if (isPresent(dirDep.queryDecorator)) return this._findQuery(dirDep.queryDecorator).list;
|
||||||
if (dirDep.key.id === StaticKeys.instance().changeDetectorRefId) {
|
if (dirDep.key.id === StaticKeys.instance().changeDetectorRefId) {
|
||||||
var componentView = this._preBuiltObjects.view.componentChildViews[this._proto.index];
|
var componentView = this._preBuiltObjects.view.componentChildViews[this._proto.index];
|
||||||
return componentView.changeDetector.ref;
|
return componentView.changeDetector.ref;
|
||||||
|
@ -942,67 +908,57 @@ export class ElementInjector extends TreeNode<ElementInjector> {
|
||||||
_buildQueriesForDeps(deps: List<DirectiveDependency>): void {
|
_buildQueriesForDeps(deps: List<DirectiveDependency>): void {
|
||||||
for (var i = 0; i < deps.length; i++) {
|
for (var i = 0; i < deps.length; i++) {
|
||||||
var dep = deps[i];
|
var dep = deps[i];
|
||||||
if (isPresent(dep.queryDirective)) {
|
if (isPresent(dep.queryDecorator)) {
|
||||||
this._createQueryRef(dep.queryDirective);
|
this._createQueryRef(dep.queryDecorator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createQueryRef(directive): void {
|
private _createQueryRef(query: Query): void {
|
||||||
var queryList = new QueryList<any>();
|
var queryList = new QueryList<any>();
|
||||||
if (isBlank(this._query0)) {
|
if (isBlank(this._query0)) {
|
||||||
this._query0 = new QueryRef(directive, queryList, this);
|
this._query0 = new QueryRef(query, queryList, this);
|
||||||
} else if (isBlank(this._query1)) {
|
} else if (isBlank(this._query1)) {
|
||||||
this._query1 = new QueryRef(directive, queryList, this);
|
this._query1 = new QueryRef(query, queryList, this);
|
||||||
} else if (isBlank(this._query2)) {
|
} else if (isBlank(this._query2)) {
|
||||||
this._query2 = new QueryRef(directive, queryList, this);
|
this._query2 = new QueryRef(query, queryList, this);
|
||||||
} else
|
} else
|
||||||
throw new QueryError();
|
throw new QueryError();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addToQueries(obj, token): void {
|
private _addToQueries(obj, token): void {
|
||||||
if (isPresent(this._query0) && (this._query0.directive === token)) {
|
if (isPresent(this._query0) && (this._query0.query.directive === token)) {
|
||||||
this._query0.list.add(obj);
|
this._query0.list.add(obj);
|
||||||
}
|
}
|
||||||
if (isPresent(this._query1) && (this._query1.directive === token)) {
|
if (isPresent(this._query1) && (this._query1.query.directive === token)) {
|
||||||
this._query1.list.add(obj);
|
this._query1.list.add(obj);
|
||||||
}
|
}
|
||||||
if (isPresent(this._query2) && (this._query2.directive === token)) {
|
if (isPresent(this._query2) && (this._query2.query.directive === token)) {
|
||||||
this._query2.list.add(obj);
|
this._query2.list.add(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(rado): unify with _addParentQueries.
|
|
||||||
private _inheritQueries(parent: ElementInjector): void {
|
|
||||||
if (isBlank(parent)) return;
|
|
||||||
if (isPresent(parent._query0)) {
|
|
||||||
this._query0 = parent._query0;
|
|
||||||
}
|
|
||||||
if (isPresent(parent._query1)) {
|
|
||||||
this._query1 = parent._query1;
|
|
||||||
}
|
|
||||||
if (isPresent(parent._query2)) {
|
|
||||||
this._query2 = parent._query2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _buildQueries(): void {
|
private _buildQueries(): void {
|
||||||
if (isPresent(this._proto)) {
|
if (isPresent(this._proto)) {
|
||||||
this._strategy.buildQueries();
|
this._strategy.buildQueries();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _findQuery(token): QueryRef {
|
private _findQuery(query): QueryRef {
|
||||||
if (isPresent(this._query0) && this._query0.directive === token) {
|
if (isPresent(this._query0) && this._query0.query === query) {
|
||||||
return this._query0;
|
return this._query0;
|
||||||
}
|
}
|
||||||
if (isPresent(this._query1) && this._query1.directive === token) {
|
if (isPresent(this._query1) && this._query1.query === query) {
|
||||||
return this._query1;
|
return this._query1;
|
||||||
}
|
}
|
||||||
if (isPresent(this._query2) && this._query2.directive === token) {
|
if (isPresent(this._query2) && this._query2.query === query) {
|
||||||
return this._query2;
|
return this._query2;
|
||||||
}
|
}
|
||||||
throw new BaseException(`Cannot find query for directive ${token}.`);
|
throw new BaseException(`Cannot find query for directive ${query}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasQuery(query: QueryRef): boolean {
|
||||||
|
return this._query0 == query || this._query1 == query || this._query2 == query;
|
||||||
}
|
}
|
||||||
|
|
||||||
link(parent: ElementInjector): void {
|
link(parent: ElementInjector): void {
|
||||||
|
@ -1016,38 +972,39 @@ export class ElementInjector extends TreeNode<ElementInjector> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addParentQueries(): void {
|
private _addParentQueries(): void {
|
||||||
|
if (isBlank(this.parent)) return;
|
||||||
if (isPresent(this.parent._query0)) {
|
if (isPresent(this.parent._query0)) {
|
||||||
this._addQueryToTree(this.parent._query0);
|
this._addQueryToTree(this.parent._query0);
|
||||||
this.parent._query0.update();
|
if (this.hydrated) this.parent._query0.update();
|
||||||
}
|
}
|
||||||
if (isPresent(this.parent._query1)) {
|
if (isPresent(this.parent._query1)) {
|
||||||
this._addQueryToTree(this.parent._query1);
|
this._addQueryToTree(this.parent._query1);
|
||||||
this.parent._query1.update();
|
if (this.hydrated) this.parent._query1.update();
|
||||||
}
|
}
|
||||||
if (isPresent(this.parent._query2)) {
|
if (isPresent(this.parent._query2)) {
|
||||||
this._addQueryToTree(this.parent._query2);
|
this._addQueryToTree(this.parent._query2);
|
||||||
this.parent._query2.update();
|
if (this.hydrated) this.parent._query2.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unlink(): void {
|
unlink(): void {
|
||||||
var queriesToUpDate = [];
|
var queriesToUpdate = [];
|
||||||
if (isPresent(this.parent._query0)) {
|
if (isPresent(this.parent._query0)) {
|
||||||
this._pruneQueryFromTree(this.parent._query0);
|
this._pruneQueryFromTree(this.parent._query0);
|
||||||
ListWrapper.push(queriesToUpDate, this.parent._query0);
|
ListWrapper.push(queriesToUpdate, this.parent._query0);
|
||||||
}
|
}
|
||||||
if (isPresent(this.parent._query1)) {
|
if (isPresent(this.parent._query1)) {
|
||||||
this._pruneQueryFromTree(this.parent._query1);
|
this._pruneQueryFromTree(this.parent._query1);
|
||||||
ListWrapper.push(queriesToUpDate, this.parent._query1);
|
ListWrapper.push(queriesToUpdate, this.parent._query1);
|
||||||
}
|
}
|
||||||
if (isPresent(this.parent._query2)) {
|
if (isPresent(this.parent._query2)) {
|
||||||
this._pruneQueryFromTree(this.parent._query2);
|
this._pruneQueryFromTree(this.parent._query2);
|
||||||
ListWrapper.push(queriesToUpDate, this.parent._query2);
|
ListWrapper.push(queriesToUpdate, this.parent._query2);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.remove();
|
this.remove();
|
||||||
|
|
||||||
ListWrapper.forEach(queriesToUpDate, (q) => q.update());
|
ListWrapper.forEach(queriesToUpdate, (q) => q.update());
|
||||||
}
|
}
|
||||||
|
|
||||||
private _pruneQueryFromTree(query: QueryRef): void {
|
private _pruneQueryFromTree(query: QueryRef): void {
|
||||||
|
@ -1060,12 +1017,24 @@ export class ElementInjector extends TreeNode<ElementInjector> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addQueryToTree(query: QueryRef): void {
|
private _addQueryToTree(queryRef: QueryRef): void {
|
||||||
this._assignQueryRef(query);
|
if (queryRef.query.descendants == false) {
|
||||||
|
if (this == queryRef.originator) {
|
||||||
|
this._addQueryToTreeSelfAndRecurse(queryRef);
|
||||||
|
} else if (this.parent == queryRef.originator && this._proto.distanceToParent == 1) {
|
||||||
|
this._assignQueryRef(queryRef);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._addQueryToTreeSelfAndRecurse(queryRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addQueryToTreeSelfAndRecurse(queryRef: QueryRef): void {
|
||||||
|
this._assignQueryRef(queryRef);
|
||||||
|
|
||||||
var child = this._head;
|
var child = this._head;
|
||||||
while (isPresent(child)) {
|
while (isPresent(child)) {
|
||||||
child._addQueryToTree(query);
|
child._addQueryToTree(queryRef);
|
||||||
child = child._next;
|
child = child._next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1507,15 +1476,8 @@ class QueryError extends BaseException {
|
||||||
}
|
}
|
||||||
|
|
||||||
class QueryRef {
|
class QueryRef {
|
||||||
directive;
|
constructor(public query: Query, public list: QueryList<any>,
|
||||||
list: QueryList<any>;
|
public originator: ElementInjector) {}
|
||||||
originator: ElementInjector;
|
|
||||||
|
|
||||||
constructor(directive, list: QueryList<any>, originator: ElementInjector) {
|
|
||||||
this.directive = directive;
|
|
||||||
this.list = list;
|
|
||||||
this.originator = originator;
|
|
||||||
}
|
|
||||||
|
|
||||||
update(): void {
|
update(): void {
|
||||||
var aggregator = [];
|
var aggregator = [];
|
||||||
|
@ -1524,9 +1486,9 @@ class QueryRef {
|
||||||
}
|
}
|
||||||
|
|
||||||
visit(inj: ElementInjector, aggregator): void {
|
visit(inj: ElementInjector, aggregator): void {
|
||||||
if (isBlank(inj)) return;
|
if (isBlank(inj) || !inj._hasQuery(this)) return;
|
||||||
if (inj.hasDirective(this.directive)) {
|
if (inj.hasDirective(this.query.directive)) {
|
||||||
ListWrapper.push(aggregator, inj.get(this.directive));
|
ListWrapper.push(aggregator, inj.get(this.query.directive));
|
||||||
}
|
}
|
||||||
var child = inj._head;
|
var child = inj._head;
|
||||||
while (isPresent(child)) {
|
while (isPresent(child)) {
|
||||||
|
|
|
@ -35,9 +35,9 @@ import {BaseQueryList} from './base_query_list';
|
||||||
* with `<tabs>`
|
* with `<tabs>`
|
||||||
* component's on `hydrate` and deregister on `dehydrate` event. While a reasonable approach, this
|
* component's on `hydrate` and deregister on `dehydrate` event. While a reasonable approach, this
|
||||||
* would only work
|
* would only work
|
||||||
* partialy since `*ng-for` could rearange the list of `<pane>` components which would not be
|
* partialy since `*ng-for` could rearrange the list of `<pane>` components which would not be
|
||||||
* reported to `<tabs>`
|
* reported to `<tabs>`
|
||||||
* component and thus the list of `<pane>` componets would be out of sync with respect to the list
|
* component and thus the list of `<pane>` components would be out of sync with respect to the list
|
||||||
* of `<pane>` elements.
|
* of `<pane>` elements.
|
||||||
*
|
*
|
||||||
* A preferred solution is to inject a `QueryList` which is a live list of directives in the
|
* A preferred solution is to inject a `QueryList` which is a live list of directives in the
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
|
|
||||||
import {TestBed} from 'angular2/src/test_lib/test_bed';
|
import {TestBed} from 'angular2/src/test_lib/test_bed';
|
||||||
|
|
||||||
import {Injectable} from 'angular2/di';
|
import {Injectable, Optional} from 'angular2/di';
|
||||||
import {QueryList} from 'angular2/core';
|
import {QueryList} from 'angular2/core';
|
||||||
import {Query, Component, Directive, View} from 'angular2/annotations';
|
import {Query, Component, Directive, View} from 'angular2/annotations';
|
||||||
|
|
||||||
|
@ -25,10 +25,12 @@ export function main() {
|
||||||
BrowserDomAdapter.makeCurrent();
|
BrowserDomAdapter.makeCurrent();
|
||||||
describe('Query API', () => {
|
describe('Query API', () => {
|
||||||
|
|
||||||
it('should contain all directives in the light dom',
|
it('should contain all direct child directives in the light dom',
|
||||||
inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||||
var template = '<div text="1"></div>' +
|
var template = '<div text="1"></div>' +
|
||||||
'<needs-query text="2"><div text="3"></div></needs-query>' +
|
'<needs-query text="2"><div text="3">' +
|
||||||
|
'<div text="too-deep"></div>' +
|
||||||
|
'</div></needs-query>' +
|
||||||
'<div text="4"></div>';
|
'<div text="4"></div>';
|
||||||
|
|
||||||
tb.createView(MyComp, {html: template})
|
tb.createView(MyComp, {html: template})
|
||||||
|
@ -40,11 +42,46 @@ export function main() {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should contain all directives in the light dom when descendants flag is used',
|
||||||
|
inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||||
|
var template = '<div text="1"></div>' +
|
||||||
|
'<needs-query-desc text="2"><div text="3">' +
|
||||||
|
'<div text="4"></div>' +
|
||||||
|
'</div></needs-query-desc>' +
|
||||||
|
'<div text="5"></div>';
|
||||||
|
|
||||||
|
tb.createView(MyComp, {html: template})
|
||||||
|
.then((view) => {
|
||||||
|
view.detectChanges();
|
||||||
|
expect(view.rootNodes).toHaveText('2|3|4|');
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should contain all directives in the light dom',
|
||||||
|
inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||||
|
var template = '<div text="1"></div>' +
|
||||||
|
'<needs-query text="2"><div text="3"></div></needs-query>' +
|
||||||
|
'<div text="4"></div>';
|
||||||
|
|
||||||
|
tb.createView(MyComp, {html: template})
|
||||||
|
.then((view) => {
|
||||||
|
view.detectChanges();
|
||||||
|
expect(view.rootNodes).toHaveText('2|3|');
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
// TODO(rado): The test below should be using descendants: false,
|
||||||
|
// but due to a bug with how injectors are hooked up query considers the
|
||||||
|
// directives to be distances 2 instead of direct children.
|
||||||
it('should reflect dynamically inserted directives',
|
it('should reflect dynamically inserted directives',
|
||||||
inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||||
var template =
|
var template =
|
||||||
'<div text="1"></div>' +
|
'<div text="1"></div>' +
|
||||||
'<needs-query text="2"><div *ng-if="shouldShow" [text]="\'3\'"></div></needs-query>' +
|
'<needs-query-desc text="2"><div *ng-if="shouldShow" [text]="\'3\'"></div></needs-query-desc>' +
|
||||||
'<div text="4"></div>';
|
'<div text="4"></div>';
|
||||||
|
|
||||||
tb.createView(MyComp, {html: template})
|
tb.createView(MyComp, {html: template})
|
||||||
|
@ -55,8 +92,6 @@ export function main() {
|
||||||
|
|
||||||
view.context.shouldShow = true;
|
view.context.shouldShow = true;
|
||||||
view.detectChanges();
|
view.detectChanges();
|
||||||
// TODO(rado): figure out why the second tick is necessary.
|
|
||||||
view.detectChanges();
|
|
||||||
expect(view.rootNodes).toHaveText('2|3|');
|
expect(view.rootNodes).toHaveText('2|3|');
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
|
@ -66,7 +101,7 @@ export function main() {
|
||||||
it('should reflect moved directives', inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
it('should reflect moved directives', inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||||
var template =
|
var template =
|
||||||
'<div text="1"></div>' +
|
'<div text="1"></div>' +
|
||||||
'<needs-query text="2"><div *ng-for="var i of list" [text]="i"></div></needs-query>' +
|
'<needs-query-desc text="2"><div *ng-for="var i of list" [text]="i"></div></needs-query-desc>' +
|
||||||
'<div text="4"></div>';
|
'<div text="4"></div>';
|
||||||
|
|
||||||
tb.createView(MyComp, {html: template})
|
tb.createView(MyComp, {html: template})
|
||||||
|
@ -102,10 +137,16 @@ class NeedsQuery {
|
||||||
constructor(@Query(TextDirective) query: QueryList<TextDirective>) { this.query = query; }
|
constructor(@Query(TextDirective) query: QueryList<TextDirective>) { this.query = query; }
|
||||||
}
|
}
|
||||||
|
|
||||||
var _constructiontext = 0;
|
@Component({selector: 'needs-query-desc'})
|
||||||
|
@View({directives: [NgFor], template: '<div *ng-for="var dir of query">{{dir.text}}|</div>'})
|
||||||
|
@Injectable()
|
||||||
|
class NeedsQueryDesc {
|
||||||
|
query: QueryList<TextDirective>;
|
||||||
|
constructor(@Query(TextDirective, {descendants: true}) query: QueryList<TextDirective>) { this.query = query; }
|
||||||
|
}
|
||||||
|
|
||||||
@Component({selector: 'my-comp'})
|
@Component({selector: 'my-comp'})
|
||||||
@View({directives: [NeedsQuery, TextDirective, NgIf, NgFor]})
|
@View({directives: [NeedsQuery, NeedsQueryDesc, TextDirective, NgIf, NgFor]})
|
||||||
@Injectable()
|
@Injectable()
|
||||||
class MyComp {
|
class MyComp {
|
||||||
shouldShow: boolean;
|
shouldShow: boolean;
|
||||||
|
|
|
@ -30,6 +30,20 @@ export function main() {
|
||||||
expect(log).toEqual('one again, two again');
|
expect(log).toEqual('one again, two again');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support length', () => {
|
||||||
|
queryList.add('one');
|
||||||
|
queryList.add('two');
|
||||||
|
expect(queryList.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support first and last', () => {
|
||||||
|
queryList.add('one');
|
||||||
|
queryList.add('two');
|
||||||
|
queryList.add('three');
|
||||||
|
expect(queryList.first).toEqual('one');
|
||||||
|
expect(queryList.last).toEqual('three');
|
||||||
|
});
|
||||||
|
|
||||||
describe('simple observable interface', () => {
|
describe('simple observable interface', () => {
|
||||||
it('should fire callbacks on change', () => {
|
it('should fire callbacks on change', () => {
|
||||||
var fires = 0;
|
var fires = 0;
|
||||||
|
|
Loading…
Reference in New Issue