docs: annotations

This commit is contained in:
Misko Hevery 2015-03-17 23:29:27 +00:00
parent 08b56e1c53
commit 81312e4b3e
5 changed files with 402 additions and 12 deletions

View File

@ -3,5 +3,6 @@
*/
export * from './change_detection';
export * from './core';
export * from './annotations';
export * from './directives';
export * from './forms';

4
modules/angular2/annotations.js vendored Normal file
View File

@ -0,0 +1,4 @@
/**
* Define public API for Angular here.
*/
export * from './src/core/annotations/annotations';

View File

@ -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';

View File

@ -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<Type>`: A live collection of direct child directives.
* - `@Descendants query:Query<Type>`: 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:
*
* ```
* <div marker="1">
* <div marker="2">
* <div marker="3" example>
* <div marker="4">
* <div marker="5"></div>
* </div>
* <div marker="6"></div>
* </div>
* </div>
* </div>
* ```
*
* 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<Maker>) {
* // 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<Maker>) {
* // 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<LifecycleEvent>
lifecycle:List; //List<LifecycleEvent>
@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: '<child></child>',
* 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.
*
* ```
* <div tooltip="some text here"></div>
* ```
*
* 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.
*
* ```
* <ul>
* <li *unless="expr"></li>
* </ul>
* ```
*
* 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";

View File

@ -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;