feat(query): adds initial implementation of the query api.

Queries allow a directive to inject a live list of directives of a given
type from its LightDom. The injected list is Iterable (in JS and Dart).
It will be Observable when Observables are support in JS, for now it
maintains a simple list of onChange callbacks API.

To support queries, element injectors now maintain a list of
child injectors in the correct DOM order (dynamically updated by
viewports).

For performance reasons we allow only 3 active queries in an injector
subtree. The feature adds no overhead to the application when not
used. Queries walk the injector tree only during dynamic view
addition/removal as triggered by viewport directives.

Syncs changes between viewContainer on the render and logic sides.

Closes #792
This commit is contained in:
Rado Kirov
2015-04-09 19:07:19 -07:00
parent 61cb99ea42
commit e9f70293ac
9 changed files with 840 additions and 26 deletions
+12
View File
@@ -54,3 +54,15 @@ export class Attribute extends DependencyAnnotation {
return this;
}
}
/**
* The directive can inject an query that would reflect a list of ancestor directives
*/
export class Query extends DependencyAnnotation {
directive;
@CONST()
constructor(directive) {
super();
this.directive = directive;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,47 @@
library angular2.src.core.compiler.query_list;
import 'package:angular2/src/core/annotations/annotations.dart';
import 'dart:collection';
/**
* Injectable Objects that contains a live list of child directives in the light Dom of a directive.
* The directives are kept in depth-first pre-order traversal of the DOM.
*
* In the future this class will implement an Observable interface.
* For now it uses a plain list of observable callbacks.
*/
class QueryList extends Object with IterableMixin<Directive> {
List<Directive> _results;
List _callbacks;
bool _dirty;
QueryList(): _results = [], _callbacks = [], _dirty = false;
Iterator<Directive> get iterator => _results.iterator;
reset(newList) {
_results = newList;
_dirty = true;
}
add(obj) {
_results.add(obj);
_dirty = true;
}
// TODO(rado): hook up with change detection after #995.
fireCallbacks() {
if (_dirty) {
_callbacks.forEach((c) => c());
_dirty = false;
}
}
onChange(callback) {
this._callbacks.add(callback);
}
removeCallback(callback) {
this._callbacks.remove(callback);
}
}
@@ -0,0 +1,51 @@
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {Directive} from 'angular2/src/core/annotations/annotations';
/**
* Injectable Objects that contains a live list of child directives in the light Dom of a directive.
* The directives are kept in depth-first pre-order traversal of the DOM.
*
* In the future this class will implement an Observable interface.
* For now it uses a plain list of observable callbacks.
*/
export class QueryList {
_results: List<Directive>;
_callbacks;
_dirty;
constructor() {
this._results = [];
this._callbacks = [];
this._dirty = false;
}
[Symbol.iterator]() {
return this._results[Symbol.iterator]();
}
reset(newList) {
this._results = newList;
this._dirty = true;
}
add(obj) {
ListWrapper.push(this._results, obj);
this._dirty = true;
}
// TODO(rado): hook up with change detection after #995.
fireCallbacks() {
if (this._dirty) {
ListWrapper.forEach(this._callbacks, (c) => c());
this._dirty = false;
}
}
onChange(callback) {
ListWrapper.push(this._callbacks, callback);
}
removeCallback(callback) {
ListWrapper.remove(this._callbacks, callback);
}
}
+14 -5
View File
@@ -75,6 +75,11 @@ export class ViewContainer {
return this._views.length;
}
_siblingInjectorToLinkAfter(index: number) {
if (index == 0) return null;
return ListWrapper.last(this._views[index - 1].rootElementInjectors)
}
hydrated() {
return isPresent(this.appInjector);
}
@@ -106,7 +111,7 @@ export class ViewContainer {
if (atIndex == -1) atIndex = this._views.length;
ListWrapper.insert(this._views, atIndex, view);
this.parentView.changeDetector.addChild(view.changeDetector);
this._linkElementInjectors(view);
this._linkElementInjectors(this._siblingInjectorToLinkAfter(atIndex), view);
return view;
}
@@ -133,15 +138,19 @@ export class ViewContainer {
return detachedView;
}
_linkElementInjectors(view) {
for (var i = 0; i < view.rootElementInjectors.length; ++i) {
view.rootElementInjectors[i].parent = this.elementInjector;
contentTagContainers() {
return this._views;
}
_linkElementInjectors(sibling, view) {
for (var i = view.rootElementInjectors.length - 1; i >= 0; i--) {
view.rootElementInjectors[i].linkAfter(this.elementInjector, sibling);
}
}
_unlinkElementInjectors(view) {
for (var i = 0; i < view.rootElementInjectors.length; ++i) {
view.rootElementInjectors[i].parent = null;
view.rootElementInjectors[i].unlink();
}
}
}
@@ -122,6 +122,7 @@ export class ViewContainer {
return r;
}
static moveViewNodesAfterSibling(sibling, view) {
for (var i = view.rootNodes.length - 1; i >= 0; --i) {
DOM.insertAfter(sibling, view.rootNodes[i]);