From 38b96ed7468073824cfdd5d2272f213fbb390edd Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 19 Mar 2015 16:56:45 +0000 Subject: [PATCH] docs: annotations --- .../src/core/annotations/annotations.js | 295 +++++++++++------- .../src/core/annotations/visibility.js | 6 +- 2 files changed, 182 insertions(+), 119 deletions(-) diff --git a/modules/angular2/src/core/annotations/annotations.js b/modules/angular2/src/core/annotations/annotations.js index 49ae0a63ac..8283802810 100644 --- a/modules/angular2/src/core/annotations/annotations.js +++ b/modules/angular2/src/core/annotations/annotations.js @@ -7,51 +7,73 @@ 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 such as: [Component], [Decorator] or [Viewport]. + * 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] resolves the directive's constructor arguments. + * 1. For each directive, the [ElementInjector] attempts to resolve 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. - * - * - * + * ## 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 + * 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 + * as the components in the DOM. + * - *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. + * + * 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 + * 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 + * + * 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 + * * ## Example * - * Assuming this HTML structure: + * The following example demonstrates how dependency injection resolves constructor arguments in practice. + * + * + * Assume this HTML structure: * * ``` - *
- *
- *
- *
- *
+ *
+ *
+ *
+ *
+ *
*
- *
+ *
*
*
*
* ``` * - * With the following `Marker` decorator and `SomeService` class: + * With the following `dependency` decorator and `SomeService` injectable class. * * ``` * @Injectable() @@ -59,98 +81,102 @@ import {Injectable} from 'angular2/di'; * } * * @Decorator({ - * selector: '[marker]', + * selector: '[dependency]', * bind: { - * 'id':'marker' + * 'id':'dependency' * } * }) - * class Marker { + * class Dependency { * 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. - * + * Let's step through the different ways in which `MyDirective` could be declared... * * ### No injection * - * A directive can have a constructor with no arguments in which case nothing is injected into it. + * Here the constructor is declared with no arguments, so nothing is injected into `MyDirective`. * * ``` - * @Decorator({ selector: '[example]' }) - * class Example { + * @Decorator({ selector: '[my-directive]' }) + * class MyDirective { * constructor() { * } * } * ``` * + * 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?] * - * ### Injecting from application injector + * ### Component-level injection * * 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: + * + * Here, the constructor declares a parameter, `someService`, and injects the `SomeService` type from the parent + * component's injector. * * ``` - * @Decorator({ selector: '[example]' }) - * class Example { - * constructor(someService:SomeService) { + * @Decorator({ selector: '[my-directive]' }) + * class MyDirective { + * constructor(someService: SomeService) { * } * } * ``` * + * This directive would return `dependency=3` for the example code above. [TODO: True? Is "return" the right verb?] + * + * ### Injecting a directive from the current element * - * ### 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). + * Directives can inject other directives declared on the current element. * * ``` - * @Decorator({ selector: '[example]' }) - * class Example { - * constructor(marker:Marker) { - * expect(marker.id).toEqual(2); + * @Decorator({ selector: '[my-directive]' }) + * class MyDirective { + * constructor(dependency: Dependency) { + * expect(dependency.id).toEqual(2); * } * } * ``` + * This directive would also return `dependency=3` for the example code above. [TODO: True? Why is this the same?] + * * + * ### Injecting a directive from a direct parent element * - * ### 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. + * 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?] * * ``` - * @Decorator({ selector: '[example]' }) - * class Example { - * constructor(@Parent() marker:Marker) { - * expect(marker.id).toEqual(2); + * @Decorator({ selector: '[my-directive]' }) + * class MyDirective { + * constructor(@Parent() dependency: Dependency) { + * expect(dependency.id).toEqual(2); * } * } * ``` + * This directive would return `dependency=2` for the example code above. [TODO: True?] + * + * ### Injecting a directive from any ancestor elements * - * 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. + * 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? ] * + * 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: '[example]' }) - * class Example { - * constructor(@Ancestor() marker:Marker) { - * expect(marker.id).toEqual(2); + * @Decorator({ selector: '[my-directive]' }) + * class MyDirective { + * constructor(@Ancestor() dependency: Dependency) { + * expect(dependency.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"`. - * + * + * 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] * @@ -159,10 +185,10 @@ import {Injectable} from 'angular2/di'; * 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] + * @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. * } @@ -175,10 +201,10 @@ import {Injectable} from 'angular2/di'; * 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] + * @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. * } @@ -192,8 +218,8 @@ import {Injectable} from 'angular2/di'; * use case angular supports `@Optional` injection. * * ``` - * @Decorator({ selector: '[example]' }) - * class Example { + * @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 @@ -522,6 +548,72 @@ 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. + * + * + * ## Example + * @DynamicComponent({ + * selector: 'dynamic-comp' + * }) + * class DynamicComp { + * done; + * constructor(loader:PrivateComponentLoader, location:PrivateComponentLocation) { + * this.done = loader.load(HelloCmp, location); + * } + * } + * + * @Component({ + * selector: 'hello-cmp' + * }) + * @Template({ + * inline: "{{greeting}}" + * }) + * class HelloCmp { + * greeting:string; + * constructor() { + * this.greeting = "hello"; + * } + * } + * + * + * @publicModule angular2/annotations + */ +export class DynamicComponent extends Directive { + services:any; //List; + + @CONST() + constructor({ + selector, + bind, + events, + services, + lifecycle + }:{ + selector:string, + bind:Object, + events:Object, + services:List, + lifecycle:List + }={}) { + super({ + selector: selector, + bind: bind, + events: events, + lifecycle: lifecycle + }); + + this.services = services; + } +} + /** * Decorators allow attaching behavior to DOM elements in a composable manner. * @@ -577,37 +669,6 @@ export class Component extends Directive { * ``` * @publicModule angular2/annotations */ -export class DynamicComponent extends Directive { - services:any; //List; - - @CONST() - constructor({ - selector, - bind, - events, - services, - lifecycle - }:{ - selector:string, - bind:Object, - events:Object, - services:List, - lifecycle:List - }={}) { - super({ - selector: selector, - bind: bind, - events: events, - lifecycle: lifecycle - }); - - this.services = services; - } -} - -/** - * @publicModule angular2/angular2 - */ export class Decorator extends Directive { compileChildren: boolean; @CONST() diff --git a/modules/angular2/src/core/annotations/visibility.js b/modules/angular2/src/core/annotations/visibility.js index 51321c5933..cc8bf43a80 100644 --- a/modules/angular2/src/core/annotations/visibility.js +++ b/modules/angular2/src/core/annotations/visibility.js @@ -4,7 +4,8 @@ import {DependencyAnnotation} from 'angular2/di'; /** * The directive can only be injected from the current element * or from its parent. - * @publicModule angular2/angular2 + * + * @publicModule angular2/annotations */ export class Parent extends DependencyAnnotation { @CONST() @@ -16,7 +17,8 @@ export class Parent extends DependencyAnnotation { /** * The directive can only be injected from the current element * or from its ancestor. - * @publicModule angular2/angular2 + * + * @publicModule angular2/annotations */ export class Ancestor extends DependencyAnnotation { @CONST()