From f822066e2ab968c29dfe551a84fe2999b773a545 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Fri, 20 Mar 2015 21:11:58 +0000 Subject: [PATCH] docs: annotations --- .../src/core/annotations/annotations.js | 483 ++++++++++-------- .../angular2/src/core/annotations/template.js | 2 +- .../src/core/annotations/visibility.js | 80 ++- 3 files changed, 349 insertions(+), 216 deletions(-) diff --git a/modules/angular2/src/core/annotations/annotations.js b/modules/angular2/src/core/annotations/annotations.js index 8283802810..ddfd500f53 100644 --- a/modules/angular2/src/core/annotations/annotations.js +++ b/modules/angular2/src/core/annotations/annotations.js @@ -7,58 +7,62 @@ import {Injectable} from 'angular2/di'; /** * Directives allow you to attach behavior to elements in the DOM. * - * Directive is an abstract concept, instead use concrete directives: [Component], [DynamicComponent], [Decorator] + * Directive is an abstract concept, instead use concrete directives: [Component], [DynamicComponent], [Decorator] * or [Viewport]. * * A directive consists of a single directive annotation and a controller class. When the directive's [selector] matches * elements in the DOM, the following steps occur: * * 1. For each directive, the [ElementInjector] attempts to resolve the directive's constructor arguments. - * 2. Angular instantiates directives for each matched element using [ElementInjector]. + * 2. Angular instantiates directives for each matched element using [ElementInjector] in a depth-first order, + * as declared in the HTML. * * ## Understanding How Injection Works - * + * * There are three stages of injection resolution. - * - *Pre-existing Injectors*: - * - The terminal [Injector] cannot resolve dependencies. It either throws an error or, if the dependency was + * - *Pre-existing Injectors*: + * - The terminal [Injector] cannot resolve dependencies. It either throws an error or, if the dependency was * specified as `@Optional`, returns `null`. * - The primordial injector resolves browser singleton resources, such as: cookies, title, location, and others. - * - *Component Injectors*: Each `@Component` has its own [Injector], and they follow the same parent-child hierachy + * - *Component Injectors*: Each `@Component` has its own [Injector], and they follow the same parent-child hierachy * as the components in the DOM. - * - *Element Injectors*: Each component has a Shadow DOM. Within the Shadow DOM each element has an [ElementInjector] + * - *Element Injectors*: Each component has a Shadow DOM. Within the Shadow DOM each element has an [ElementInjector] * which follow the same parent-child hiercachy as the DOM elements themselves. - * - * When resolving dependencies, the current injector is asked to resolve the dependency first, and if it does not - * have it, it delegates to the parent injector. - * + * + * When a template is instantiated, it also must instantiate the corresponding directives in a depth-first order. The + * current [ElementInjector] resolves the constructor dependencies for each directive. + * * Angular then resolves dependencies as follows, according to the order in which they appear in the [View]: - * - * 1. Dependencies on element injectors and their parents until it encounters a Shadow DOM boundary - * 2. Dependencies on component injectors and their parents until it encounters the root component - * 3. Dependencies on pre-existing injectors - * - * - * The [ElementInjector] can inject other directives, element-specific special objects, or can delegate to the parent + * + * 1. Dependencies on the current element + * 2. Dependencies on element injectors and their parents until it encounters a Shadow DOM boundary + * 3. Dependencies on component injectors and their parents until it encounters the root component + * 4. Dependencies on pre-existing injectors + * + * + * The [ElementInjector] can inject other directives, element-specific special objects, or it can delegate to the parent * injector. - * + * * To inject other directives, declare the constructor parameter as: - * - `directive:DirectiveType`: a directive on the current element only - * - `@Ancestor() d:Type`: any directive that matches the type between the current element (excluded) and the Shadow DOM root [TODO: what does (excluded) mean? Does this apply to the @Parent annotation also?] - * - `@Parent() d:Type`: any directive that matches the type on a direct parent element only - * - `@Children query:Query`: A live collection of direct child directives - * - `@Descendants query:Query`: A live collection of any child directives - * + * - `directive:DirectiveType`: a directive on the current element only + * - `@Ancestor() directive:DirectiveType`: any directive that matches the type between the current element and the + * Shadow DOM root. Current Element is not included in the resolution, therefor even if it could resolve it, it will + * be ignored. + * - `@Parent() directive:DirectiveType`: any directive that matches the type on a direct parent element only. + * - `@Children query:Query`: A live collection of direct child directives [TO BE IMPLEMENTED]. + * - `@Descendants query:Query`: A live collection of any child directives [TO BE IMPLEMENTED]. + * * To inject element-specific special objects, declare the constructor parameter as: - * - `element: NgElement` to obtain a DOM element (DEPRECATED: replacment coming) - * - `viewContainer: ViewContainer` to control child template instantiation, for [Viewport] directives only - * - `bindingPropagation: BindingPropagation` to control change detection in a more granular way - * + * - `element: NgElement` to obtain a DOM element (DEPRECATED: replacment coming) + * - `viewContainer: ViewContainer` to control child template instantiation, for [Viewport] directives only + * - `bindingPropagation: BindingPropagation` to control change detection in a more granular way. + * * ## Example * * The following example demonstrates how dependency injection resolves constructor arguments in practice. * * - * Assume this HTML structure: + * Assume this HTML template: * * ``` *
@@ -93,9 +97,10 @@ import {Injectable} from 'angular2/di'; * * Let's step through the different ways in which `MyDirective` could be declared... * + * * ### No injection * - * Here the constructor is declared with no arguments, so nothing is injected into `MyDirective`. + * Here the constructor is declared with no arguments, therefore nothing is injected into `MyDirective`. * * ``` * @Decorator({ selector: '[my-directive]' }) @@ -105,9 +110,8 @@ import {Injectable} from 'angular2/di'; * } * ``` * - * This directive would return nothing for the example code above. [TODO: True? We spent a lot of time talking about - * errors but in this case, there's nothing to error on, right? I don't understand the diff between "returns" and "injects" - * when the example is showing a directive not the template. Which is the correct verb?] + * This directive would be instantiated with no dependencies. + * * * ### Component-level injection * @@ -124,8 +128,9 @@ import {Injectable} from 'angular2/di'; * } * ``` * - * This directive would return `dependency=3` for the example code above. [TODO: True? Is "return" the right verb?] - * + * This directive would be instantiated with a dependency on `SomeService`. + * + * * ### Injecting a directive from the current element * * Directives can inject other directives declared on the current element. @@ -138,14 +143,14 @@ import {Injectable} from 'angular2/di'; * } * } * ``` - * This directive would also return `dependency=3` for the example code above. [TODO: True? Why is this the same?] - * + * This directive would be instantiated with `Dependency` declared at the same element, in this case `dependency="3"`. + * * * ### Injecting a directive from a direct parent element * - * Directives can inject other directives declared on a direct parent element. By definition, a directive with a + * Directives can inject other directives declared on a direct parent element. By definition, a directive with a * `@Parent` annotation does not attempt to resolve dependencies for the current element, even if this would satisfy - * the dependency. [TODO: did I get the subject/verb right?] + * the dependency. * * ``` * @Decorator({ selector: '[my-directive]' }) @@ -155,17 +160,15 @@ import {Injectable} from 'angular2/di'; * } * } * ``` - * This directive would return `dependency=2` for the example code above. [TODO: True?] - * + * This directive would be instantiated with `Dependency` declared at the parent element, in this case `dependency="2"`. + * + * * ### Injecting a directive from any ancestor elements * - * Directives can inject other directives declared on any ancestor element, i.e. on the parent element and its parents. - * By definition, a directive with an `@Ancestor` annotation does not attempt to resolve dependencies for the current - * element, even if this would satisfy the dependency. [TODO: did I get the subject/verb right? ] + * Directives can inject other directives declared on any ancestor element (in the current Shadow DOM), i.e. on the + * parent element and its parents. By definition, a directive with an `@Ancestor` annotation does not attempt to + * resolve dependencies for the current element, even if this would satisfy the dependency. * - * Unlike the `@Parent` which only checks the parent `@Ancestor` checks the parent, as well as its - * parents recursivly. If `dependency="2"` would not be present this injection would return `dependency="1"`. - * ``` * @Decorator({ selector: '[my-directive]' }) * class MyDirective { @@ -174,59 +177,61 @@ import {Injectable} from 'angular2/di'; * } * } * ``` - * - * This directive would also return `dependency=2` for the example code above. If `dependency=2` hadn't been declared - * on the parent `div`, this directive would return `d[TODO: True?] * - * ### Injecting query of child directives. [PENDING IMPLEMENTATION] + * Unlike the `@Parent` which only checks the parent, `@Ancestor` checks the parent, as well as its + * parents recursively. If `dependency="2"` didn't exist on the direct parent, this injection would have returned + * `dependency="1"`. * - * In some cases the directive may be interersted in injecting its child directives. This is not directly possible - * since parent directives are guarteed to be created before child directives. Instead we can injecto a container - * which can than be filled once the data is needed. + * + * ### Injecting a live collection of direct child directives [PENDING IMPLEMENTATION] + * + * A directive can also query for other child directives. Since parent directives are instantiated before child + * directives, a directive can't simply inject the list of child directives. Instead, the directive asynchronously + * injects a [Query], which updates as children are added, removed, or moved by any [ViewPort] directive such as a + * `for`, an `if`, or a `switch`. * * ``` * @Decorator({ selector: '[my-directive]' }) * class MyDirective { - * constructor(@Children() dependencys:Query) { - * // dependencys will eventuall contain: [4, 6] - * // this will upbate if children are added/removed/moved, - * // for example by having for or if. + * constructor(@Children() dependencies:Query) { * } * } * ``` * + * This directive would be instantiated with a [Query] which contains `Dependency` 4 and 6. Here, `Dependency` 5 would + * not be included, because it is not a direct child. * - * ### Injecting query of descendant directives. [PENDING IMPLEMENTATION] + * ### Injecting a live collection of direct descendant directives [PENDING IMPLEMENTATION] * - * Similar to `@Children` but also includ childre of those children. + * Similar to `@Children` above, but also includes the children of the child elements. * * ``` * @Decorator({ selector: '[my-directive]' }) * class MyDirective { - * constructor(@Children() dependencys:Query) { - * // dependencys will eventuall contain: [4, 5, 6] - * // this will upbate if children are added/removed/moved, - * // for example by having for or if. + * constructor(@Children() dependencies:Query) { * } * } * ``` * + * This directive would be instantiated with a Query which would contain `Dependency` 4, 5 and 6. * * ### Optional injection * - * Finally there may be times when we would like to inject a component which may or may not be there. For this - * use case angular supports `@Optional` injection. + * The normal behavior of directives is to return an error when a specified dependency cannot be resolved. If you + * would like to inject `null` on unresolved dependency instead, you can annotate that dependency with `@Optional()`. + * This explicitly permits the author of a template to treat some of the surrounding directives as optional. * * ``` * @Decorator({ selector: '[my-directive]' }) * class MyDirective { - * constructor(@Optional() @Ancestor() form:Form) { - * // this will search for a Form directive above itself, - * // and inject null if not found + * constructor(@Optional() dependency:Dependency) { * } * } * ``` * + * This directive would be instantiated with a `Dependency` directive found on the current element. If none can be + * found, the injector supplies `null` instead of throwing an error. + * * @publicModule angular2/annotations */ @ABSTRACT() @@ -235,13 +240,16 @@ export class Directive extends Injectable { * The CSS selector that triggers the instantiation of a directive. * * Angular only allows directives to trigger on CSS selectors that do not cross element boundaries. - * The supported selectors are: * - * - `element-name` select by element name. - * - `.class` select by class name. - * - `[attribute]` select by attribute name. - * - `[attribute=value]` select by attribute name and value. - * - `:not(sub_selector)` select only if the element does not match the `sub_selector`. + * `selector` may be declared as one of the following: + * + * - `element-name`: select by element name. + * - `.class`: select by class name. + * - `[attribute]`: select by attribute name. + * - `[attribute=value]`: select by attribute name and value. + * - `:not(sub_selector)`: select only if the element does not match the `sub_selector`. + * - `selector1, selector2`: select if either `selector1` or `selector2` matches. [TO BE IMPLMENTED] + * * * ## Example * @@ -270,7 +278,8 @@ export class Directive extends Injectable { * - `bindingProperty` specifies the DOM property where the value is read from. * * You can include [Pipes] when specifying a `bindingProperty` to allow for data transformation and structural - * change detection of the value. + * change detection of the value. These pipes will be evaluated in the context of this component. + * * * ## Syntax * @@ -285,43 +294,45 @@ export class Directive extends Injectable { * ``` * * - * ## Basic Property Binding: + * ## Basic Property Binding + * + * We can easily build a simple `Tooltip` directive that exposes a `tooltip` property, which can be used in templates + * with standard Angular syntax. For example: * * ``` * @Decorator({ * selector: '[tooltip]', * bind: { - * 'tooltipText': 'tooltip' + * 'text': 'tooltip' * } * }) * class Tooltip { - * set tooltipText(text) { + * set text(text) { * // This will get called every time the 'tooltip' binding changes with the new value. * } * } * ``` * - * As used in this example: + * We can then bind to the `tooltip' property as either an expression (`someExpression`) or as a string literal, as + * shown in the HTML template below: * * ```html - *
+ *
...
+ *
...
* ``` * * Whenever the `someExpression` expression changes, the `bind` declaration instructs Angular to update the * `Tooltip`'s `tooltipText` property. * * - * Similarly in this example: - * - * ```html - *
- * ``` - * - * The `Tooltip`'s `tooltipText` property gets initialized to the `Some Text` literal. - * * * ## Bindings With Pipes: * + * You can also use pipes when writing binding definitions for a directive. + * + * For example, we could write a binding that updates the directive on structural changes, rather than on reference + * changes, as normally occurs in change detection. (See: [Pipe] and [keyValueDiff] documentaition for more details.) + * * ``` * @Decorator({ * selector: '[class-set]', @@ -336,29 +347,30 @@ export class Directive extends Injectable { * } * ``` * - * As used in this example: + * The template that this directive is used in may also contain its own pipes. For example: * * ```html - *
+ *
* ``` * - * In the above example, the `ClassSet` uses the `keyValDiff` [Pipe] for watching structural changes. This means that - * the `classChanges` setter gets invoked if the expression changes to a different reference, or if the - * structure of the expression changes. (Shallow property watching of the object) - * - * NOTE: The `someExpression` can also contain its own [Pipe]s. In this case, the two pipes compose as if they were - * inlined. + * In this case, the two pipes compose as if they were inlined: `someExpression | somePipe | keyValDiff`. * */ bind:any; // StringMap /** - * Specifies which DOM events the directive listens to and what the action should be when they occur. + * Specifies which DOM events a directive listens to. * * The `events` property defines a set of `event` to `method` key-value pairs: * - * - `event1` specifies the DOM event that the directive listens to. - * - `onMethod1` specifies the method to execute when the event occurs. + * - `event1`: the DOM event that the directive listens to. + * - `statement`: the statment to execute when the event occurs. + * + * + * When writing a directive event binding, you can also refer to the following local variables: + * - `$event`: Current event object which triggerd the event. + * - `$target`: The source of the event. This will be either a DOM element or an Angular directive. + * [TO BE IMPLEMENTED] * * * ## Syntax @@ -366,7 +378,7 @@ export class Directive extends Injectable { * ``` * @Directive({ * events: { - * 'event1': 'onMethod1', + * 'event1': 'onMethod1(arguments)', * ... * } * } @@ -374,20 +386,24 @@ export class Directive extends Injectable { * * ## Basic Event Binding: * + * Suppose you want to write a directive that triggers on `change` events in the DOM. You would define the event + * binding as follows: + * * ``` * @Decorator({ * selector: 'input', * events: { - * 'change': 'onChange' + * 'change': 'onChange($event)' * } * }) * class InputDecorator { * onChange(event:Event) { - * // invoked whenever the DOM element fires the 'change' event. * } * } * ``` * + * Here `InputDecorator` is invoked whenever the DOM element fires the 'change' event. + * */ events:any; // StringMap @@ -420,6 +436,8 @@ export class Directive extends Injectable { /** * Returns true if a directive participates in a given [LifecycleEvent]. + * + * See: [onChange], [onDestroy] for details. */ hasLifecycleHook(hook:string):boolean { return isPresent(this.lifecycle) ? ListWrapper.contains(this.lifecycle, hook) : false; @@ -427,98 +445,87 @@ export class Directive extends Injectable { } /** - * Components are angular directives with Shadow DOM views. + * Declare template views for an Angular application. * - * Componests are used to encapsulate state and template into reusable building blocks. An angular component requires - * an `@Component` and at least one `@Template` annotation (see [Template] for more datails.) Components instances are - * used as the context for evaluation of the Shadow DOM view. + * Each angular component requires a single `@Component` and at least one `@Template` annotation. This allows Angular to + * encapsulate state information and templates. These form the fundamental reusable building blocks for developing an + * application. There can only be one component per DOM element. * - * Restrictions: - * - Thre can anly be one component per DOM element. + * When a component is instantiated, Angular + * - creates a shadow DOM for the component. + * - loads the selected template into the shadow DOM. + * - creates a child [Injector] which is configured with the [Component.services]. + * + * All template expressions and statments are then evaluted against the component instance. + * + * For details on the `@Template` annotation, see [Template]. * * ## Example - * @Component({ - * selector: 'greet' - * }) - * @Template({ - * inline: 'Hello {{name}}' - * }) - * class Greet { - * name: string; * - * constructor() { - * this.name = 'World'; - * } - * } + * ``` + * @Component({ + * selector: 'greet' + * }) + * @Template({ + * inline: 'Hello {{name}}!' + * }) + * class Greet { + * name: string; + * + * constructor() { + * this.name = 'World'; + * } + * } + * ``` * * @publicModule angular2/annotations */ export class Component extends Directive { /** - * Defines the set of injectables that are visible to a Component and its children. + * Defines the set of injectable objects that are visible to a Component and its children. * - * When a [Component] defines [injectables], Angular creates a new application-level [Injector] for the component - * and its children. Injectables are defined as a list of [Binding]s, (or as [Type]s as short hand). These bindings - * are passed to the [Injector] constructor when making a new child [Injector]. The injectables are available for - * all child directives of the Component (but not the declaring component's light DOM directives). + * The [services] defined in the Component annotation allow you to configure a set of bindings for the component's + * injector. * - * ## Example - * // Example of a class which we would like to inject. - * class Greeter { - * salutation:string; - * - * constructor(salutation:string) { - * this.salutation = salutation; - * } - * - * greet(name:string) { - * return this.salutation + ' ' + name + '!'; - * } - * } - * - * @Component({ - * selector: 'greet', - * services: [ - * bind(String).toValue('Hello'), // Configure injection of string - * Greeter // Make Greeter available for injection - * ] - * }) - * @Template({ - * inline: '', - * directives: Child - * }) - * class Greet { - * greeter: Greeter; - * - * constructor(greeter: Greeter) { - * // Greeter can be injected here becouse it was declared as injectable - * // in this component, or parent component. - * this.greeter = greeter; - * } - * } - * - * @Decorator({ - * selector: 'child' - * }) - * class Child { - * greeter: Greeter; - * - * constructor(greeter: Greeter) { - * // Greeter can be injected here becouse it was declared as injectable - * // in a an ancestor component. - * this.greeter = greeter; - * } - * } + * When a component is instantiated, Angular creates a new child Injector, which is configured with the bindings in + * the Component [services] annotation. The injectable objects then become available for injection to the component + * itself and any of the directives in the component's template, i.e. they are not available to the directives which + * are children in the component's light DOM. * * - * Let's look at the [services] part of the example above. + * The syntax for configuring the [services] injectable is identical to [Injector] injectable configuration. See + * [Injector] for additional detail. * - * services: [ - * bind(String).toValue('Hello'), - * Greeter - * ] * - * Here the `Greeter` is a short hand for `bind(Greeter).toClass(Greeter)`. See [bind] DSL for more details. + * ## Simple Example + * + * Here is an example of a class that can be injected: + * + * ``` + * class Greeter { + * greet(name:string) { + * return 'Hello ' + name + '!'; + * } + * } + * + * @Component({ + * selector: 'greet', + * services: [ + * Greeter + * ] + * }) + * @Template({ + * inline: `{{greeter.greet('world')}}!`, + * directives: Child + * }) + * class HelloWorld { + * greeter:Greeter; + * + * constructor(greeter:Greeter) { + * this.greeter = greeter; + * } + * } + * ``` */ services:List; @@ -549,27 +556,34 @@ export class Component extends Directive { } /** - * DynamicComponents allow loading child components impretivly. - * - * A Component can be made of other compontents. This recursive nature must be resolved synchronously during the - * component template processing. This means that all templates are resolved synchronously. This prevents lazy loading - * of code or delayed binding of views to the components. - * - * A DynamicComponent is a placeholder into which a regular component can be loaded imperativly and thus breaking - * the all components must be resolved synchronously restriction. Once loaded the component is premanent. - * - * + * A directive used for dynamically loading components. + * + * Regular angular components are statically resolved. DynamicComponent allows to you resolve a component at runtime + * instead by providing a placeholder into which a regular angular component can be dynamically loaded. Once loaded, + * the dynamically-loaded component becomes permanent and cannot be changed. + * + * * ## Example + * + * Here we have `DynamicComp` which acts as the placeholder for `HelloCmp`. At runtime, the dynamic component + * `DynamicComp` requests loading of the `HelloCmp` component. + * + * There is nothing special about `HelloCmp`, which is a regular angular component. It can also be used in other static + * locations. + * + * ``` * @DynamicComponent({ * selector: 'dynamic-comp' * }) * class DynamicComp { - * done; + * helloCmp:HelloCmp; * constructor(loader:PrivateComponentLoader, location:PrivateComponentLocation) { - * this.done = loader.load(HelloCmp, location); + * loader.load(HelloCmp, location).then((helloCmp) => { + * this.helloCmp = helloCmp; + * }); * } * } - * + * * @Component({ * selector: 'hello-cmp' * }) @@ -582,11 +596,17 @@ export class Component extends Directive { * this.greeting = "hello"; * } * } - * - * + * ``` + * + * + * * @publicModule angular2/annotations */ export class DynamicComponent extends Directive { + /** + * Same as [Component.services]. + */ + // TODO(vsankin): Please extract into AbstractComponent services:any; //List; @CONST() @@ -615,26 +635,24 @@ export class DynamicComponent extends Directive { } /** - * Decorators allow attaching behavior to DOM elements in a composable manner. + * Directive that attaches behavior to DOM elements. * + * A decorator directive attaches behavior to a DOM element in a composable manner. + * (see: http://en.wikipedia.org/wiki/Composition_over_inheritance) + * * Decorators: * - are simplest form of [Directive]s. - * - are besed used as compostinion pattern () + * - are best used as a composition pattern () * - * Decoraters differ from [Component]s in that they: - * - can have any number of decorators per element + * Decorators differ from [Component]s in that they: + * - can have multiple decorators per element * - do not create their own evaluation context * - do not have template (and therefor do not create Shadow DOM) * + * * ## Example * - * Let's say we would like to add tool-tip behavior to any alement. - * - * ``` - *
- * ``` - * - * We could have a decorator directive like so: + * Here we use a decorator directive to simply define basic tool-tip behavior. * * ``` * @Decorator({ @@ -643,8 +661,8 @@ export class DynamicComponent extends Directive { * 'text': 'tooltip' * }, * event: { - * 'onmouseenter': 'onMouseEnter', - * 'onmouseleave': 'onMouseLeave' + * 'onmouseenter': 'onMouseEnter()', + * 'onmouseleave': 'onMouseLeave()' * } * }) * class Tooltip{ @@ -667,10 +685,23 @@ export class DynamicComponent extends Directive { * } * } * ``` + * In our HTML template, we can then add this behavior to a `
` or any other element with the `tooltip` selector, + * like so: + * + * ``` + *
+ * ``` + * * @publicModule angular2/annotations */ export class Decorator extends Directive { + + /** + * If set to true the compiler does not compile the children of this directive. + */ + //TODO(vsavkin): This would better fall under the Macro directive concept. compileChildren: boolean; + @CONST() constructor({ selector, @@ -697,10 +728,12 @@ export class Decorator extends Directive { } /** - * Viewport is used for controlling the instatiation of inline templates. + * Viewport is used for controlling the instatiation of inline templates. [TODO: needs co-work :)] * - * Viewport consist of a controller which can inject [ViewContainer]. A [ViewContainer] rerpsents a location in the - * current view where child views can be inserted. + * Viewport consist of a controller which can inject [ViewContainer]. A [ViewContainer] represents a location in the + * current view where child views can be inserted. [ViewContainer] is created as a result of `