diff --git a/modules/angular2/angular2.js b/modules/angular2/angular2.js index 2764cf0982..afc531b739 100644 --- a/modules/angular2/angular2.js +++ b/modules/angular2/angular2.js @@ -3,5 +3,6 @@ */ export * from './change_detection'; export * from './core'; +export * from './annotations'; export * from './directives'; export * from './forms'; diff --git a/modules/angular2/annotations.js b/modules/angular2/annotations.js new file mode 100644 index 0000000000..d95c56a360 --- /dev/null +++ b/modules/angular2/annotations.js @@ -0,0 +1,4 @@ +/** + * Define public API for Angular here. + */ +export * from './src/core/annotations/annotations'; diff --git a/modules/angular2/core.js b/modules/angular2/core.js index 19d6774686..db2da0c6f8 100644 --- a/modules/angular2/core.js +++ b/modules/angular2/core.js @@ -1,4 +1,3 @@ -export * from './src/core/annotations/annotations'; export * from './src/core/annotations/visibility'; export * from './src/core/compiler/interfaces'; export * from './src/core/annotations/template'; diff --git a/modules/angular2/src/core/annotations/annotations.js b/modules/angular2/src/core/annotations/annotations.js index 85e596844e..49ae0a63ac 100644 --- a/modules/angular2/src/core/annotations/annotations.js +++ b/modules/angular2/src/core/annotations/annotations.js @@ -5,10 +5,203 @@ import {Injectable} from 'angular2/di'; // type StringMap = {[idx: string]: string}; /** - * Directives allow you to attach behavior to the DOM elements. + * Directives allow you to attach behavior to elements in the DOM. * * Directive is an abstract concept, instead use concrete directives such as: [Component], [Decorator] or [Viewport]. - * @publicModule angular2/angular2 + * + * 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] resolves the directive's constructor arguments. + * 2. Angular instantiates directives for each matched element using [ElementInjector]. + * + * Angular guarantees the following constraints: + * - Directives are instantiated in a depth-first order, according to the order in which they appear in the [View]. + * - Injection cannot cross a Shadow DOM boundary. Angular looks for directives in the current template only. + * + * The constructor arguments for a directive may be: + * - other directives, as annotated by: + * - `@Ancestor() d:Type`: any directive that matches the type between the current element (excluded) and the Shadow DOM root. + * - `@Parent() d:Type`: any directive that matches the type on a direct parent element only. + * - `d:Type`: a directive on the current element only. + * - `@Children query:Query`: A live collection of direct child directives. + * - `@Descendants query:Query`: A live collection of any child directives. + * - element specific special objects: + * - [NgElement] (DEPRECATED: replacment coming) + * - [ViewContainer] (Only for [Viewport]) + * - [BindingPropagation] Used for controlling change detection + * - Component level injectables as declared by [Component.services] of a parent compontent. + * + * + * + * ## Example + * + * Assuming this HTML structure: + * + * ``` + *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ``` + * + * With the following `Marker` decorator and `SomeService` class: + * + * ``` + * @Injectable() + * class SomeService { + * } + * + * @Decorator({ + * selector: '[marker]', + * bind: { + * 'id':'marker' + * } + * }) + * class Marker { + * id:string; + * } + * ``` + * + * We would like to demonstrate how we can inject different instances into a directive. In each case injecting + * is as simple as asking for the type in the constructor. + * + * + * ### No injection + * + * A directive can have a constructor with no arguments in which case nothing is injected into it. + * + * ``` + * @Decorator({ selector: '[example]' }) + * class Example { + * constructor() { + * } + * } + * ``` + * + * + * ### Injecting from application injector + * + * Directives can inject any injectable instance from the closest component injector or any of its parents. + * To inject from component injector the directive list the dependency as such: + * + * ``` + * @Decorator({ selector: '[example]' }) + * class Example { + * constructor(someService:SomeService) { + * } + * } + * ``` + * + * + * ### Injecting directive from current element + * + * Directives can inject other directives declared on the current element. If no such type is found the injection will + * delegate to component injector which will throw an error (or optionally return null as described below). + * + * ``` + * @Decorator({ selector: '[example]' }) + * class Example { + * constructor(marker:Marker) { + * expect(marker.id).toEqual(2); + * } + * } + * ``` + * + * + * ### Injecting directive from parent element + * + * Directives can inject other directives declared on parent element. If no such type is found the injection will + * delegate to component injector which will throw. + * + * ``` + * @Decorator({ selector: '[example]' }) + * class Example { + * constructor(@Parent() marker:Marker) { + * expect(marker.id).toEqual(2); + * } + * } + * ``` + * + * The `@Parent` annotation will explicitly skip the current element, even if the current element could satisfy + * the dependency. + * + * + * ### Injecting directive from ancestor element. + * + * Directives can inject other directives declared on ancestor (parent plus its parents) elements. If no such type is + * found the injection will delegate to component injector which will throw. + * + * ``` + * @Decorator({ selector: '[example]' }) + * class Example { + * constructor(@Ancestor() marker:Marker) { + * expect(marker.id).toEqual(2); + * } + * } + * ``` + * + * The `@Ancestor` annotation will explicitly skip the current element, even if the current element could satisfy + * the dependency. Unlike the `@Parent` which only checks the parent `@Ancestor` checks the parent, as well as its + * parents recursivly. If `marker="2"` would not be preset this injection would return `marker="1"`. + * + * + * ### Injecting query of child directives. [PENDING IMPLEMENTATION] + * + * 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. + * + * ``` + * @Decorator({ selector: '[example]' }) + * class Example { + * constructor(@Children() markers:Query) { + * // markers will eventuall contain: [4, 6] + * // this will upbate if children are added/removed/moved, + * // for example by having for or if. + * } + * } + * ``` + * + * + * ### Injecting query of descendant directives. [PENDING IMPLEMENTATION] + * + * Similar to `@Children` but also includ childre of those children. + * + * ``` + * @Decorator({ selector: '[example]' }) + * class Example { + * constructor(@Children() markers:Query) { + * // markers will eventuall contain: [4, 5, 6] + * // this will upbate if children are added/removed/moved, + * // for example by having for or if. + * } + * } + * ``` + * + * + * ### 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. + * + * ``` + * @Decorator({ selector: '[example]' }) + * class Example { + * constructor(@Optional() @Ancestor() form:Form) { + * // this will search for a Form directive above itself, + * // and inject null if not found + * } + * } + * ``` + * + * @publicModule angular2/annotations */ @ABSTRACT() export class Directive extends Injectable { @@ -134,7 +327,7 @@ export class Directive extends Injectable { bind:any; // StringMap /** - * Specifies which DOM events the directive listens to and what the action should be. + * Specifies which DOM events the directive listens to and what the action should be when they occur. * * The `events` property defines a set of `event` to `method` key-value pairs: * @@ -174,8 +367,10 @@ export class Directive extends Injectable { /** * Specifies a set of lifecycle events in which the directive participates. + * + * See: [onChange], [onDestroy] for details. */ - lifecycle:any; //List + lifecycle:List; //List @CONST() constructor({ @@ -197,17 +392,109 @@ export class Directive extends Injectable { this.lifecycle = lifecycle; } + /** + * Returns true if a directive participates in a given [LifecycleEvent]. + */ hasLifecycleHook(hook:string):boolean { return isPresent(this.lifecycle) ? ListWrapper.contains(this.lifecycle, hook) : false; } } /** - * @publicModule angular2/angular2 + * Components are angular directives with Shadow DOM views. + * + * 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. + * + * Restrictions: + * - Thre can anly be one component per DOM element. + * + * ## Example + * @Component({ + * selector: 'greet' + * }) + * @Template({ + * inline: 'Hello {{name}}' + * }) + * class Greet { + * name: string; + * + * constructor() { + * this.name = 'World'; + * } + * } + * + * @publicModule angular2/annotations */ export class Component extends Directive { - //TODO: vsavkin: uncomment it once the issue with defining fields in a sublass works - services:any; //List; + /** + * Defines the set of injectables 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). + * + * ## 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; + * } + * } + * + * + * Let's look at the [services] part of the example above. + * + * services: [ + * bind(String).toValue('Hello'), + * Greeter + * ] + * + * Here the `Greeter` is a short hand for `bind(Greeter).toClass(Greeter)`. See [bind] DSL for more details. + */ + services:List; @CONST() constructor({ @@ -236,7 +523,59 @@ export class Component extends Directive { } /** - * @publicModule angular2/angular2 + * Decorators allow attaching behavior to DOM elements in a composable manner. + * + * Decorators: + * - are simplest form of [Directive]s. + * - are besed used as compostinion pattern () + * + * Decoraters differ from [Component]s in that they: + * - can have any number of 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: + * + * ``` + * @Decorator({ + * selector: '[tooltip]', + * bind: { + * 'text': 'tooltip' + * }, + * event: { + * 'onmouseenter': 'onMouseEnter', + * 'onmouseleave': 'onMouseLeave' + * } + * }) + * class Tooltip{ + * text:string; + * overlay:Overlay; // NOT YET IMPLEMENTED + * overlayManager:OverlayManager; // NOT YET IMPLEMENTED + * + * constructor(overlayManager:OverlayManager) { + * this.overlay = overlay; + * } + * + * onMouseEnter() { + * // exact signature to be determined + * this.overlay = this.overlayManager.open(text, ...); + * } + * + * onMouseLeave() { + * this.overlay.close(); + * this.overlay = null; + * } + * } + * ``` + * @publicModule angular2/annotations */ export class DynamicComponent extends Directive { services:any; //List; @@ -297,7 +636,53 @@ export class Decorator extends Directive { } /** - * @publicModule angular2/angular2 + * Viewport is used for controlling the instatiation of inline templates. + * + * Viewport consist of a controller which can inject [ViewContainer]. A [ViewContainer] rerpsents a location in the + * current view where child views can be inserted. + * + * ## Example + * + * Given folowing inline template, let's implement the `unless` behavior. + * + * ``` + *
    + *
  • + *
+ * ``` + * + * Can be implemented using: + * + * ``` + * @Viewport({ + * selector: '[unless]', + * bind: { + * 'condition': 'unless' + * } + * }) + * export class If { + * viewContainer: ViewContainer; + * prevCondition: boolean; + * + * constructor(viewContainer: ViewContainer) { + * this.viewContainer = viewContainer; + * this.prevCondition = null; + * } + * + * set condition(newCondition) { + * if (newCondition && (isBlank(this.prevCondition) || !this.prevCondition)) { + * this.prevCondition = true; + * this.viewContainer.clear(); + * } else if (!newCondition && (isBlank(this.prevCondition) || this.prevCondition)) { + * this.prevCondition = false; + * this.viewContainer.create(); + * } + * } + * } + * ``` + * + * + * @publicModule angular2/annotations */ export class Viewport extends Directive { @CONST() @@ -339,7 +724,7 @@ export class Viewport extends Directive { * } * } * ``` - * @publicModule angular2/angular2 + * @publicModule angular2/annotations */ export const onDestroy = "onDestroy"; @@ -371,6 +756,6 @@ export const onDestroy = "onDestroy"; * } * } * ``` - * @publicModule angular2/angular2 + * @publicModule angular2/annotations */ export const onChange = "onChange"; diff --git a/modules/angular2/src/core/compiler/element_injector.js b/modules/angular2/src/core/compiler/element_injector.js index 4c7d169781..97547971cf 100644 --- a/modules/angular2/src/core/compiler/element_injector.js +++ b/modules/angular2/src/core/compiler/element_injector.js @@ -587,6 +587,7 @@ export class ElementInjector extends TreeNode { _getPreBuiltObjectByKeyId(keyId:int) { var staticKeys = StaticKeys.instance(); + // TODO: View should not be injectable. Remove it. if (keyId === staticKeys.viewId) return this._preBuiltObjects.view; if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element; if (keyId === staticKeys.viewContainerId) return this._preBuiltObjects.viewContainer;