diff --git a/modules/angular2/src/core/compiler/query_list.dart b/modules/angular2/src/core/compiler/query_list.dart index f2a22bde61..64f4d6ec01 100644 --- a/modules/angular2/src/core/compiler/query_list.dart +++ b/modules/angular2/src/core/compiler/query_list.dart @@ -3,73 +3,7 @@ library angular2.src.core.compiler.query_list; import 'dart:collection'; /** - * An iterable and observable live list of components in the DOM. - * - * A QueryList contains a live list of child directives in the DOM of a directive. - * The directives are kept in depth-first pre-order traversal of the DOM. - * - * The `QueryList` is iterable, therefore it can be used in both javascript code with `for..of` loop - * as well as in template with `*ng-for="of"` directive. - * - * QueryList is updated as part of the change-detection cycle of a directive. Since change detection - * happens after construction of a directive, QueryList will always be empty when observed in the - * constructor. - * - * - * NOTE: In the future this class will implement an `Observable` interface. For now it uses a plain - * list of observable callbacks. - * - * # Example: - * - * Assume that `` component would like to get a list its children which are `` - * components as shown in this example: - * - * ```html - * - * ... - * {{o.text}} - * - * ``` - * - * In the above example the list of `` elements needs to get a list of `` elements so - * that it could render tabs with the correct titles and in the correct order. - * - * A possible solution would be for a `` to inject `` component and then register itself - * with `` component's on `hydrate` and deregister on `dehydrate` event. While a reasonable - * approach, this would only work partialy since `*ng-for` could rearrange the list of `` - * components which would not be reported to `` component and thus the list of `` - * components would be out of sync with respect to the list of `` elements. - * - * A preferred solution is to inject a `QueryList` which is a live list of directives in the - * component`s light DOM. - * - * ```javascript - * @Component( - * selector: 'tabs' - * ) - * @View( - * template: ` - *
    - *
  • {{pane.title}}
  • - *
- * - * ` - * ) - * class Tabs { - * QueryList panes; - * - * constructor(@Query(Pane) QueryList this.panes) { } - * } - * - * @Component( - * selector: 'pane', - * properties: ['title'] - * ) - * @View(...) - * class Pane { - * String title; - * } - * ``` + * See query_list.ts */ class QueryList extends Object with IterableMixin { @@ -79,6 +13,7 @@ class QueryList extends Object Iterator get iterator => _results.iterator; + /** @private */ void reset(List newList) { _results = newList; _dirty = true; @@ -89,7 +24,6 @@ class QueryList extends Object _dirty = true; } - void onChange(callback) { _callbacks.add(callback); } @@ -114,7 +48,7 @@ class QueryList extends Object return this._results.map(fn).toList(); } - // Internal to the framework. + /** @private */ void fireCallbacks() { if (_dirty) { _callbacks.forEach((c) => c()); diff --git a/modules/angular2/src/core/compiler/query_list.ts b/modules/angular2/src/core/compiler/query_list.ts index 4b4f3315c4..371669295f 100644 --- a/modules/angular2/src/core/compiler/query_list.ts +++ b/modules/angular2/src/core/compiler/query_list.ts @@ -3,74 +3,27 @@ import {getSymbolIterator} from 'angular2/src/core/facade/lang'; /** - * An iterable and observable live list of components in the DOM. + * An unmodifiable list of items that Angular keeps up to date when the state + * of the application changes. * - * A QueryList contains a live list of child directives in the DOM of a directive. - * The directives are kept in depth-first pre-order traversal of the DOM. + * The type of object that {@link QueryMetadata} and {@link ViewQueryMetadata} provide. * - * The `QueryList` is iterable, therefore it can be used in both javascript code with `for..of` loop - * as well as in template with `*ng-for="of"` directive. + * Implements an iterable interface, therefore it can be used in both ES6 + * javascript `for (var i of items)` loops as well as in Angular templates with + * `*ng-for="#i of myList"`. * - * QueryList is updated as part of the change-detection cycle of a directive. Since change detection - * happens after construction of a directive, QueryList will always be empty when observed in the - * constructor. + * Changes can be observed by attaching callbacks. * + * NOTE: In the future this class will implement an `Observable` interface. * - * NOTE: In the future this class will implement an `Observable` interface. For now it uses a plain - * list of observable callbacks. - * - * # Example: - * - * Assume that `` component would like to get a list its children which are `` - * components as shown in this example: - * - * ```html - * - * ... - * {{o.text}} - * - * ``` - * - * In the above example the list of `` elements needs to get a list of `` elements so - * that it could render tabs with the correct titles and in the correct order. - * - * A possible solution would be for a `` to inject `` component and then register itself - * with `` component's on `hydrate` and deregister on `dehydrate` event. While a reasonable - * approach, this would only work partially since `*ng-for` could rearrange the list of `` - * components which would not be reported to `` component and thus the list of `` - * components would be out of sync with respect to the list of `` elements. - * - * A preferred solution is to inject a `QueryList` which is a live list of directives in the - * component`s light DOM. - * + * ### Example ([live demo](http://plnkr.co/edit/RX8sJnQYl9FWuSCWme5z?p=preview)) * ```javascript - * @Component({ - * selector: 'tabs' - * }) - * @View({ - * template: ` - *
    - *
  • {{pane.title}}
  • - *
- * - * ` - * }) - * class Tabs { - * panes: QueryList - * - * constructor(@Query(Pane) panes:QueryList) { - * this.panes = panes; + * @Component({...}) + * class Container { + * constructor(@Query(Item) items: QueryList) { + * items.onChange(() => console.log(items.length)); * } * } - * - * @Component({ - * selector: 'pane', - * properties: ['title'] - * }) - * @View(...) - * class Pane { - * title:string; - * } * ``` */ export class QueryList { @@ -78,21 +31,31 @@ export class QueryList { protected _callbacks: Array < () => void >= []; protected _dirty: boolean = false; + /** @private */ reset(newList: T[]): void { this._results = newList; this._dirty = true; } + /** @private */ add(obj: T): void { this._results.push(obj); this._dirty = true; } - + /** + * registers a callback that is called upon each change. + */ onChange(callback: () => void): void { this._callbacks.push(callback); } + /** + * removes a given callback. + */ removeCallback(callback: () => void): void { ListWrapper.remove(this._callbacks, callback); } + /** + * removes all callback that have been attached. + */ removeAllCallbacks(): void { this._callbacks = []; } toString(): string { return this._results.toString(); } @@ -101,11 +64,14 @@ export class QueryList { get first(): T { return ListWrapper.first(this._results); } get last(): T { return ListWrapper.last(this._results); } + /** + * returns a new list with the passsed in function applied to each element. + */ map(fn: (item: T) => U): U[] { return this._results.map(fn); } [getSymbolIterator()](): any { return this._results[getSymbolIterator()](); } - // Internal to the framework. + /** @private */ fireCallbacks(): void { if (this._dirty) { ListWrapper.forEach(this._callbacks, (c) => c()); diff --git a/modules/angular2/src/core/metadata.ts b/modules/angular2/src/core/metadata.ts index d115dc88f1..02d4e30d76 100644 --- a/modules/angular2/src/core/metadata.ts +++ b/modules/angular2/src/core/metadata.ts @@ -335,7 +335,7 @@ export interface AttributeFactory { /** * {@link QueryMetadata} factory for creating annotations, decorators or DSL. * - * ## Example as TypeScript Decorator + * ### Example as TypeScript Decorator * * ``` * import {Query, QueryList, Component, View} from "angular2/angular2"; @@ -343,13 +343,13 @@ export interface AttributeFactory { * @Component({...}) * @View({...}) * class MyComponent { - * constructor(@Query(SomeType) queryList: QueryList) { + * constructor(@Query(SomeType) queryList: QueryList) { * ... * } * } * ``` * - * ## Example as ES5 DSL + * ### Example as ES5 DSL * * ``` * var MyComponent = ng @@ -362,7 +362,7 @@ export interface AttributeFactory { * }) * ``` * - * ## Example as ES5 annotation + * ### Example as ES5 annotation * * ``` * var MyComponent = function(queryList) { @@ -513,7 +513,7 @@ export var Query: QueryFactory = makeParamDecorator(QueryMetadata); /** - * {@link di/ViewQueryMetadata} factory function. + * {@link ViewQueryMetadata} factory function. */ export var ViewQuery: QueryFactory = makeParamDecorator(ViewQueryMetadata); diff --git a/modules/angular2/src/core/metadata/di.ts b/modules/angular2/src/core/metadata/di.ts index c61dd4ea0b..8b2ea0a502 100644 --- a/modules/angular2/src/core/metadata/di.ts +++ b/modules/angular2/src/core/metadata/di.ts @@ -50,34 +50,191 @@ export class AttributeMetadata extends DependencyMetadata { } /** - * Specifies that a {@link QueryList} should be injected. + * Declares an injectable parameter to be a live list of directives or variable + * bindings from the content children of a directive. * - * See {@link QueryList} for usage and example. + * ### Example ([live demo](http://plnkr.co/edit/lY9m8HLy7z06vDoUaSN2?p=preview)) + * + * Assume that `` component would like to get a list its children `` + * components as shown in this example: + * + * ```html + * + * ... + * {{o.text}} + * + * ``` + * + * The preferred solution is to query for `Pane` directives using this decorator. + * + * ```javascript + * @Component({ + * selector: 'pane', + * properties: ['title'] + * }) + * @View(...) + * class Pane { + * title:string; + * } + * + * @Component({ + * selector: 'tabs' + * }) + * @View({ + * template: ` + *
    + *
  • {{pane.title}}
  • + *
+ * + * ` + * }) + * class Tabs { + * panes: QueryList; + * constructor(@Query(Pane) panes:QueryList) { + * this.panes = panes; + * } + * } + * ``` + * + * A query can look for variable bindinds by passing in a string with desired binding symbol. + * + * ### Example ([live demo](http://plnkr.co/edit/sT2j25cH1dURAyBRCKx1?p=preview)) + * ```html + * + *
...
+ *
+ * + * @Component({ + * selector: 'foo' + * }) + * @View(...) + * class seeker { + * constructor(@Query('findme') elList: QueryList) {...} + * } + * ``` + * + * In this case the object that is injected depend on the type of the variable + * binding. It can be an ElementRef, a directive or a component. + * + * Passing in a comma separated list of variable bindings will query for all of them. + * + * ```html + * + *
...
+ *
...
+ *
+ * + * @Component({ + * selector: 'foo' + * }) + * @View(...) + * class Seeker { + * constructor(@Query('findMe, findMeToo') elList: QueryList) {...} + * } + * ``` + * + * Configure whether query looks for direct children or all descendants + * of the querying element, by using the `descendants` parameter. + * It is set to `false` by default. + * + * ### Example ([live demo](http://plnkr.co/edit/wtGeB977bv7qvA5FTYl9?p=preview)) + * ```html + * + * a + * b + * + * c + * + * + * ``` + * + * When querying for items, the first container will see only `a` and `b` by default, + * but with `Query(TextDirective, {descendants: true})` it will see `c` too. + * + * The queried directives are kept in a depth-first pre-order with respect to their + * positions in the DOM. + * + * Query does not look deep into any subcomponent views. + * + * Query is updated as part of the change-detection cycle. Since change detection + * happens after construction of a directive, QueryList will always be empty when observed in the + * constructor. + * + * The injected object is an unmodifiable live list. + * See {@link QueryList} for more details. */ @CONST() export class QueryMetadata extends DependencyMetadata { + /** + * whether we want to query only direct children (false) or all + * children (true). + */ descendants: boolean; + constructor(private _selector: Type | string, {descendants = false}: {descendants?: boolean} = {}) { super(); this.descendants = descendants; } - get isViewQuery() { return false; } + /** + * always `false` to differentiate it with {@link ViewQueryMetadata}. + */ + get isViewQuery(): boolean { return false; } + /** + * what this is querying for. + */ get selector() { return resolveForwardRef(this._selector); } + /** + * whether this is querying for a variable binding or a directive. + */ get isVarBindingQuery(): boolean { return isString(this.selector); } + /** + * returns a list of variable bindings this is querying for. + * Only applicable if this is a variable bindings query. + */ get varBindings(): string[] { return StringWrapper.split(this.selector, new RegExp(",")); } toString(): string { return `@Query(${stringify(this.selector)})`; } } /** + * Similar to {@link QueryMetadata}, but querying the component view, instead of + * the content children. + * + * ### Example ([live demo](http://plnkr.co/edit/eNsFHDf7YjyM6IzKxM1j?p=preview)) + * + * ```javascript + * @Component({...}) + * @View({ + * template: ` + * a + * b + * c + * ` + * }) + * class MyComponent { + * shown: boolean; + * + * constructor(private @Query(Item) items:QueryList) { + * items.onChange(() => console.log(items.length)); + * } + * } + * ``` + * + * Supports the same querying parameters as {@link QueryMetadata}, except + * `descendants`. This always queries the whole view. + * + * As `shown` is flipped between true and false, items will contain zero of one + * items. + * * Specifies that a {@link QueryList} should be injected. * - * See {@link QueryList} for usage and example. + * The injected object is an iterable and observable live list. + * See {@link QueryList} for more details. */ @CONST() export class ViewQueryMetadata extends QueryMetadata { @@ -85,6 +242,9 @@ export class ViewQueryMetadata extends QueryMetadata { super(_selector, {descendants: descendants}); } + /** + * always `true` to differentiate it with {@link QueryMetadata}. + */ get isViewQuery() { return true; } toString(): string { return `@ViewQuery(${stringify(this.selector)})`; } }