diff --git a/public/docs/_examples/attribute-directives/ts/.gitignore b/public/docs/_examples/attribute-directives/ts/.gitignore new file mode 100644 index 0000000000..6724ce3596 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/.gitignore @@ -0,0 +1 @@ +src/**/*.js \ No newline at end of file diff --git a/public/docs/_examples/attribute-directives/ts/package.json b/public/docs/_examples/attribute-directives/ts/package.json new file mode 100644 index 0000000000..fc50f6ea1c --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/package.json @@ -0,0 +1,21 @@ +{ + "name": "angular2-attribute-directive", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "tsc": "tsc -p src -w", + "start": "live-server --open=src" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "angular2": "2.0.0-alpha.47", + "systemjs": "0.19.6" + }, + "devDependencies": { + "live-server": "^0.8.2", + "typescript": "^1.6.2" + } +} diff --git a/public/docs/_examples/attribute-directives/ts/src/app/app.component.1.html b/public/docs/_examples/attribute-directives/ts/src/app/app.component.1.html new file mode 100644 index 0000000000..a07e40597b --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/app.component.1.html @@ -0,0 +1,4 @@ + +

My First Attribute Directive

+Highlight me! + \ No newline at end of file diff --git a/public/docs/_examples/attribute-directives/ts/src/app/app.component.html b/public/docs/_examples/attribute-directives/ts/src/app/app.component.html new file mode 100644 index 0000000000..5efbbc2d6f --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/app.component.html @@ -0,0 +1,12 @@ + +

My First Attribute Directive

+

Pick a highlight color

+
+ Green + Yellow + Cyan +
+ +

Highlight me!

+ + \ No newline at end of file diff --git a/public/docs/_examples/attribute-directives/ts/src/app/app.component.ts b/public/docs/_examples/attribute-directives/ts/src/app/app.component.ts new file mode 100644 index 0000000000..ea434251f8 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/app.component.ts @@ -0,0 +1,13 @@ +// #docregion +import {Component} from 'angular2/angular2'; +import {Highlight} from './highlight.directive' + +@Component({ + selector: 'my-app', + templateUrl: 'app/app.component.html', + directives: [Highlight] +}) + +export class AppComponent { } + +// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/attribute-directives/ts/src/app/boot.ts b/public/docs/_examples/attribute-directives/ts/src/app/boot.ts new file mode 100644 index 0000000000..dd6bfc08e1 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/boot.ts @@ -0,0 +1,5 @@ +// #docregion +import {bootstrap} from 'angular2/angular2'; +import {AppComponent} from './app.component'; + +bootstrap(AppComponent); diff --git a/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.1.ts b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.1.ts new file mode 100644 index 0000000000..10b98f7b00 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.1.ts @@ -0,0 +1,14 @@ +// #docregion +import {Directive, ElementRef, Renderer, Input} from 'angular2/angular2'; + +@Directive({ + selector: '[my-highlight]' +}) + +export class Highlight { + constructor(el: ElementRef, renderer: Renderer) { + //el.nativeElement.style.backgroundColor = 'yellow'; + renderer.setElementStyle(el, 'background-color', 'yellow'); + } +} +// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.2.ts b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.2.ts new file mode 100644 index 0000000000..3b8a5831b7 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.2.ts @@ -0,0 +1,30 @@ +// #docregion +import {Directive, ElementRef, Renderer, Input} from 'angular2/angular2'; + +@Directive({ + selector: '[my-highlight]', + // #docregion host + host: { + '(mouseenter)': 'onMouseEnter()', + '(mouseleave)': 'onMouseLeave()' + } + // #enddocregion host +}) + +export class Highlight { + // #docregion ctor + constructor(private el: ElementRef, private renderer: Renderer) { + } + // #enddocregion ctor + + // #docregion mouse-methods + onMouseEnter() { this._highlight("yellow"); } + onMouseLeave() { this._highlight(null); } + + private _highlight(color: string) { + this.renderer.setElementStyle(this.el, 'background-color', color); + } + // #enddocregion mouse-methods + +} +// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.ts b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.ts new file mode 100644 index 0000000000..8caca5de27 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.ts @@ -0,0 +1,43 @@ +// #docplaster +// #docregion full +import {Directive, ElementRef, Renderer, Input} from 'angular2/angular2'; + +@Directive({ + selector: '[my-highlight]', + host: { + '(mouseenter)': 'onMouseEnter()', + '(mouseleave)': 'onMouseLeave()' + } +}) + +// #docregion class +export class Highlight { +// #enddocregion class +// #enddocregion full + /* +// #docregion highlight + @Input() myHighlight: string; +// #enddocregion highlight + */ +// #docregion full +// #docregion class +// #docregion color + @Input('my-highlight') highlightColor: string; +// #enddocregion color + + private _defaultColor = 'red'; + + constructor(private el: ElementRef, private renderer: Renderer) { } + +// #docregion mouse-enter + onMouseEnter() { this._highlight(this.highlightColor || this._defaultColor); } +// #enddocregion mouse-enter + onMouseLeave() { this._highlight(null); } + + private _highlight(color:string) { + this.renderer.setElementStyle(this.el, 'background-color', color); + } + +} +// #enddocregion class +// #enddocregion full \ No newline at end of file diff --git a/public/docs/_examples/attribute-directives/ts/src/index.html b/public/docs/_examples/attribute-directives/ts/src/index.html new file mode 100644 index 0000000000..c5a241a6e2 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/index.html @@ -0,0 +1,21 @@ + + + + + + Attribute Directives + + + + + + + loading... + + + \ No newline at end of file diff --git a/public/docs/_examples/attribute-directives/ts/src/plnkr.json b/public/docs/_examples/attribute-directives/ts/src/plnkr.json new file mode 100644 index 0000000000..7124dcb6a7 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Attribute Directive", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!app/*.[1,2].*" + ], + "tags": ["attribute", "directive"] +} \ No newline at end of file diff --git a/public/docs/_examples/attribute-directives/ts/src/tsconfig.json b/public/docs/_examples/attribute-directives/ts/src/tsconfig.json new file mode 100644 index 0000000000..48fc224e99 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES5", + "module": "commonjs", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "removeComments": false, + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true + } +} \ No newline at end of file diff --git a/public/docs/ts/latest/guide/_data.json b/public/docs/ts/latest/guide/_data.json index 13857d8fe6..264694d716 100644 --- a/public/docs/ts/latest/guide/_data.json +++ b/public/docs/ts/latest/guide/_data.json @@ -23,26 +23,32 @@ "title": "Forms", "intro": "A form creates a cohesive, effective, and compelling data entry experience. An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors." }, - - "pipes": { - "title": "Pipes", - "intro": "Pipes transform displayed values within a template" + + "dependency-injection": { + "title": "Dependency Injection", + "intro": "Angular's dependency injection system creates and delivers dependent services \"just-in-time\"." }, "template-syntax": { "title": "Template Syntax", "intro": "How to write templates that display data and consume user events with the help of data binding." }, - - "dependency-injection": { - "title": "Dependency Injection", - "intro": "Angular's dependency injection system creates and delivers dependent services \"just-in-time\"." + + "pipes": { + "title": "Pipes", + "intro": "Pipes transform displayed values within a template" + }, + + "attribute-directives": { + "title": "Attribute Directives", + "intro": "Attribute directives attach behavior to elements." }, "hierarchical-dependency-injection": { "title": "Hierarchical Injectors", "intro": "Angular's hierarchical dependency injection system supports nested injectors in parallel with the component tree." }, + "glossary": { "title": "Glossary", "intro": "Brief definitions of the most important words in the Angular 2 vocabulary" diff --git a/public/docs/ts/latest/guide/attribute-directives.jade b/public/docs/ts/latest/guide/attribute-directives.jade new file mode 100644 index 0000000000..2b86bb96f9 --- /dev/null +++ b/public/docs/ts/latest/guide/attribute-directives.jade @@ -0,0 +1,314 @@ +include ../../../../_includes/_util-fns + +:marked + An **Attribute** directive changes the appearance or behavior of a DOM element. + +:marked + In this chapter we will + * write an attribute directive to change the background color + * apply the attribute directive to an element in a template + * respond to user-initiated events + * pass a value into the directive using data binding + + [Live Example](/resources/live-examples/attribute-directives/ts/src/plnkr.html) + + ## Directives overview + + There are three kinds of directives in Angular: + 1. Components + 1. Structural directives + 1. Attribute directives + + The *Component* is really a directive with a template. + It's the most common of the three directives and we write lots of them as we build our application. + + The *Structural* directive changes the DOM layout by adding and removing DOM elements. + [NgFor](template-syntax.html#ng-for) and [NgIf](template-syntax.html#ng-if) are two familiar examples. + + The *Attribute* directive changes the appearance or behavior of an element. + The built-in [NgStyle](template-syntax.html#ng-style) directive, for example, + can change several element styles at the same time. + + We are going to write our own attribute directive to set an element's background color + when the user hovers over that element. +.l-sub-section + :marked + We don't need *any* directive to simply set the background color. + We can set it with the special [Style Binding](template-syntax.html#style-binding) like this: + code-example. + <p [style.background]="'lime'">I am green with envy!</p> +
+ :marked + That wouldn't be nearly as much fun as creating our own directive. + + Besides, we're not just *setting* the color; we'll be *changing* the color + in response to a user action, a mouse hover. + +.l-main-section +:marked + ## Build a simple attribute directive + An attribute directive minimally requires building a controller class annotated with a + `Directive` decorator. The `Directive` decorator specifies the selector identifying + the attribute associated with the directive. + The controller class implements the desired directive behavior. + + Let's build a small illustrative example together. + + ### Setup + Create a new project folder (`attribute-directives`) and follow the steps in the [QuickStart](../quickstart.html). + + As in the [tutorial](/docs/ts/latest/tutorial/), we'll rename `app.ts` to `app.component.ts` + and relocate the call to `bootstrap` to a separate `boot.ts` file. ++makeExample('attribute-directives/ts/src/app/boot.ts', null, 'app/boot.ts') +:marked + A clean `app.component.ts` without bootstrapping is much easer to test. + + Finally, we remember to update `index.html` to load `boot.ts` + +code-example. + System.import('app/boot'); +:marked + ### Write the directive + Add a new file to the `app` folder called `highlight.directive.ts` and add the following code: ++makeExample('attribute-directives/ts/src/app/highlight.directive.1.ts', null, 'app/highlight.directive.ts') + +:marked + We begin by importing some symbols from the Angular library. + We need the `Directive` symbol for the `@Directive` decorator. + We need symbols for the *Element Reference* and the *Renderer* service that + we will [inject](dependency-injection.html) into the directive's constructor. + We don't need `Input` now but we will need it later in the chapter. + + Then we define the directive metadata in a configuration object passed + as an argument to the `@Directive` decorator function. + A `@Directive` decorator for an attribute directive requires a css selector to identify + the HTML in the template that is associated with our directive. + The [css selector for an attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors) + is the attribute name in square brackets. + + Our directive's selector is `[my-highlight]`. + Angular will locate all elements in the template that have an attribute named `my-highlight`. +.l-sub-section + :marked + ### Why not call it "highlight"? + *highlight* is a nicer name than *my-highlight* and, technically, it would work if we called it that. + However, the good folks at Angular strongly prefer hyphenated directive selector names. + The HTML standards body will never name one of its attributes with a hyphen and there is + less risk of colliding with a third-party directive name when we give ours a prefix. + The `ng-` prefix belongs to Angular. + We need a prefix of our own, preferably short, and `my-` will do for now. +:marked + After the `@Component` metadata comes the directive's controller class which we are exporting + to make it accessible to other components. + The directive's controller class contains the logic for our directive. + + Angular creates a new instance of the directive's controller class for + each matching element, injecting an *Element Reference* and + the *Renderer* service as arguments to our constructor. + We'll need them to set the element's background color. + + Our code shows two ways to do that. + + We could access the `nativeElement` property of the element reference + and set the element's background using the browser DOM API. We don't need + the `Renderer` for this technique. But we commented it out. + + We chose the second way, the preferred way, that relies on the `Renderer` service + to set the element properties. + +.l-sub-section + :marked + ### Why prefer the Renderer? + Manipulating the DOM directly is a practice we should *avoid* because it chains us + to the browser DOM API. + + The `Renderer` insulates our code from the browser's API. + That gives us options. + The rendering phase could be offloaded to a Web Worker for faster performance. + Our directive might work when we ran the application outside the browser, + perhaps on the server in a pre-render phase. + Server-side rendering can make our application load faster and + is often friendlier to Search Engine Optimizations (SEO). + +:marked +.l-main-section +:marked + ## Apply the attribute directive + The `AppComponent` will be the test harness for our `highlight` directive. + Let's give it a new template that + applies the directive as an attribute to a `span` element. + In Angular terms, the `` element will be the attribute **host**. + + We'll put the template in its own `app.component.html` file that looks like this: ++makeExample('attribute-directives/ts/src/app/app.component.1.html',null,'app/app.component.html') +:marked + A separate template file is clearly overkill for a 2-line template. + Hang in there; we're going to expand it later. + Meanwhile, we'll revise the `AppComponent` to reference this template. ++makeExample('attribute-directives/ts/src/app/app.component.ts',null,'app/app.component.ts') +:marked + We've added an `import` statement to fetch the 'Highlight' directive and + added that class to a `directives` array in the component metadata so that Angular + will recognize our directive when it encounters `my-highlight` in the template. + Angular would simply ignore the `my-highlight` attribute without it. + + We run the app and see that our directive highlights the span text. + +figure.image-display + img(src="/resources/images/devguide/attribute-directives/first-highlight.png" alt="First Highlight") +:marked + Let's recap what happened. + + Angular found the `my-highlight` attribute on the `` element. It created + an instance of the `Highlight` directive class, + injecting both a reference to the element and the `Renderer` service into the constructor. + The constructor told the `Renderer` to set the `` element's background style to yellow. + +.l-main-section +:marked + ## Respond to user action + + We are not satisfied to simply set an element color. + Our directive should set the color in response to a user action. + Specifically, we want to set the color when the user mouses over the element. + + We'll need to + 1. detect when the user mouses into and out of the element + 1. respond to those actions by setting and clearing the highlight color. + + Start with event detection. + We add a `host` property to the directive metadata and give it a configuration object + that specifies two mouse events and the directive methods to call when they are raised. ++makeExample('attribute-directives/ts/src/app/highlight.directive.2.ts','host') +:marked +.l-sub-section + :marked + The `host` property refers to the DOM element that hosts our attribute directive, the `` in our case. + + We could have attached an event listener to the native element (`el.nativeElement`) with + plain old JavaScript. + There are at least three problems with that approach: + + 1. We have to write the listeners correctly. + 1. We must *detach* our listener when the directive is destroyed to avoid memory leaks. + 1. We'd be talking to DOM API directly which, we learned, is something to avoid. + + Let's roll with the `host` property. +:marked + Now we implement those two mouse event handlers: ++makeExample('attribute-directives/ts/src/app/highlight.directive.2.ts','mouse-methods') +:marked + Notice that they delegate to a helper method that calls the `Renderer` service + as we used to do in the constructor. + + We no longer need the constructor body but + we still want the injected `ElementRef` and `Renderer` service. + We revise the constructor signature to capture the injectables in private variables + and clear the body. ++makeExample('attribute-directives/ts/src/app/highlight.directive.2.ts','ctor') +:marked + Here's the updated directive: ++makeExample('attribute-directives/ts/src/app/highlight.directive.2.ts',null, 'app/highlight.directive.ts') +:marked + We run the app and confirm that the background color appears as we move the mouse over the `span` and + disappears as we move out. +figure.image-display + img(src="/resources/images/devguide/attribute-directives/highlight-directive-anim.gif" alt="Second Highlight") +:marked +.l-main-section +:marked + ## Configure the directive with binding + + Currently the highlight color is hard-coded within the directive. That's inflexible. + We should set the highlight color externally with a binding like this: ++makeExample('attribute-directives/ts/src/app/app.component.html','span') +:marked + We'll extend our directive class with a bindable **input** `highlightColor` property and use it when we highlight text. + + Here is the final version of the class: + ++makeExample('attribute-directives/ts/src/app/highlight.directive.ts', 'class', 'app/highlight.directive.ts (class only)') +:marked + The new `highlightColor` property is called an "input" property because data flows from the binding expression into our directive. + Notice that we call the `@Input()` decorator function while defining the property. ++makeExample('attribute-directives/ts/src/app/highlight.directive.ts', 'color') +:marked + This `@Input` decorator adds metadata to the class that makes the `highlightColor` property available for property binding + under the `my-highlight` alias. + We must add this input metadata. Angular will give us an error if we try to bind + to a property without declaring it as an input. +.l-sub-section + :marked + The developer who uses our directive expects to bind to the attribute name, `my-highlight`. + The directive property name is `highlightColor`. That's a disconnect. + + We can resolve the discrepancy by renaming the property to `myHighlight` and define it as follows: + + +makeExample('attribute-directives/ts/src/app/highlight.directive.ts', 'highlight') +
+ :marked + We don't like that property name. + We prefer, in this case, to **alias** the `highlightColor` property with the attribute name by + passing `my-highlight` into the `@Input` decorator: + +makeExample('attribute-directives/ts/src/app/highlight.directive.ts', 'color') +:marked + Now that we're getting the highlight color as an input, we modify the `onMouseEnter()` method to use + it instead of the hard-coded color name. + We also define a red default color as a fallback in case + the user neglects to bind with a color. ++makeExample('attribute-directives/ts/src/app/highlight.directive.ts', 'mouse-enter') +:marked + Now we'll update our `AppComponent` template to let + users pick the highlight color and bind their choice to our directive. + + Here is the updated template: + ++makeExample('attribute-directives/ts/src/app/app.component.html') + +.l-sub-section + :marked + ### Where is the templated *color* property? + + The eagle-eyed may notice that the radio button click handlers in the template set a `color` property + and we are binding that `color` to the directive. + We should expect to find a `color` on the host `AppComponent`. + + **We never defined a color property for the host *AppComponent***! + And yet this code works. Where is the template `color` value going? + + Browser debugging reveals that Angular dynamically added a `color` property + to the runtime instance of the `AppComponent`. + + This is *convenient* behavior but it is also *implicit* behavior that could be confusing. + While it's cool that this technique works, we recommend adding the `color` property to the `AppComponent`. + +:marked + Here is our final app in action. +figure.image-display + img(src="/resources/images/devguide/attribute-directives/highlight-directive-final-anim.gif" alt="Final Highlight") +:marked +.l-main-section +:marked + ## Summary + Now we know how to + - build a simple **attribute directive** to attach behavior to an HTML element, + - use that directive in a template, + - respond to **events** to change behavior based on an event, + - and use **binding** to pass a value to the attribute directive. + + The final source: + ++makeTabs( + `attribute-directives/ts/src/app/app.component.ts, + attribute-directives/ts/src/app/app.component.html, + attribute-directives/ts/src/app/highlight.directive.ts, + attribute-directives/ts/src/app/boot.ts, + attribute-directives/ts/src/index.html + `, + ',,full', + `app.component.ts, + app.component.html, + highlight.directive.ts, + boot.ts, + index.html + `) diff --git a/public/docs/ts/latest/guide/dependency-injection.jade b/public/docs/ts/latest/guide/dependency-injection.jade index 056d79ebe6..dccd138bdd 100644 --- a/public/docs/ts/latest/guide/dependency-injection.jade +++ b/public/docs/ts/latest/guide/dependency-injection.jade @@ -559,8 +559,8 @@ include ../../../../_includes/_util-fns The Angular Dependency Injection is more capable than we've described. We can learn more about its advanced features, beginning with its support for - a hierarchy of nested injectors in the next - [Dependency Injection chapter](./hierarchical-dependency-injection.html) + nested injectors, in the + [Hierarchical Dependency Injection](./hierarchical-dependency-injection.html) chapter. .l-main-section diff --git a/public/resources/images/devguide/attribute-directives/first-highlight.png b/public/resources/images/devguide/attribute-directives/first-highlight.png new file mode 100644 index 0000000000..8788a75134 Binary files /dev/null and b/public/resources/images/devguide/attribute-directives/first-highlight.png differ diff --git a/public/resources/images/devguide/attribute-directives/highlight-directive-anim.gif b/public/resources/images/devguide/attribute-directives/highlight-directive-anim.gif new file mode 100644 index 0000000000..16e2a3befd Binary files /dev/null and b/public/resources/images/devguide/attribute-directives/highlight-directive-anim.gif differ diff --git a/public/resources/images/devguide/attribute-directives/highlight-directive-final-anim.gif b/public/resources/images/devguide/attribute-directives/highlight-directive-final-anim.gif new file mode 100644 index 0000000000..cd3204587f Binary files /dev/null and b/public/resources/images/devguide/attribute-directives/highlight-directive-final-anim.gif differ