diff --git a/public/docs/_examples/attribute-directives/ts/app/app.component.1.html b/public/docs/_examples/attribute-directives/ts/app/app.component.1.html index e5ee1c6463..a345271921 100644 --- a/public/docs/_examples/attribute-directives/ts/app/app.component.1.html +++ b/public/docs/_examples/attribute-directives/ts/app/app.component.1.html @@ -2,6 +2,17 @@
Highlight me!
+ + +Highlighted in yellow
+Highlighted in orange
+ + + + +Highlighted with parent component's color
+ +I am green with envy!
diff --git a/public/docs/_examples/attribute-directives/ts/app/app.component.1.ts b/public/docs/_examples/attribute-directives/ts/app/app.component.1.ts new file mode 100644 index 0000000000..13bb5ef1f4 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/app/app.component.1.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: 'app.component.1.html' +}) +// #docregion class +export class AppComponent { + color = 'yellow'; +} +// #enddocregion class diff --git a/public/docs/_examples/attribute-directives/ts/app/app.component.html b/public/docs/_examples/attribute-directives/ts/app/app.component.html index e4e445b1a8..0ef41b925d 100644 --- a/public/docs/_examples/attribute-directives/ts/app/app.component.html +++ b/public/docs/_examples/attribute-directives/ts/app/app.component.html @@ -1,20 +1,27 @@Highlight me!
- + -+
Highlight me too!
+ +Mouse over the following lines to see fixed highlights
+ +Highlighted in yellow
+Highlighted in orange
diff --git a/public/docs/_examples/attribute-directives/ts/app/app.component.ts b/public/docs/_examples/attribute-directives/ts/app/app.component.ts index cc57c9e6b1..800a5864d6 100644 --- a/public/docs/_examples/attribute-directives/ts/app/app.component.ts +++ b/public/docs/_examples/attribute-directives/ts/app/app.component.ts @@ -6,6 +6,9 @@ import { Component } from '@angular/core'; selector: 'my-app', templateUrl: 'app.component.html' }) - -export class AppComponent { } +// #docregion class +export class AppComponent { + color: string; +} +// #enddocregion class // #enddocregion diff --git a/public/docs/_examples/attribute-directives/ts/app/dummy.module.1.ts b/public/docs/_examples/attribute-directives/ts/app/dummy.module.1.ts new file mode 100644 index 0000000000..7ba41d53bb --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/app/dummy.module.1.ts @@ -0,0 +1,17 @@ +// Not used. Keep away from plunker +// Keeps ATLS from complaining about undeclared directives. +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component.1'; +import { HighlightDirective as HLD1 } from './highlight.directive.1'; +import { HighlightDirective as HLD2 } from './highlight.directive.2'; +import { HighlightDirective as HLD3 } from './highlight.directive.3'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ + AppComponent, HLD1, HLD2, HLD3 + ] +}) +export class DummyModule { } diff --git a/public/docs/_examples/attribute-directives/ts/app/highlight.directive.2.ts b/public/docs/_examples/attribute-directives/ts/app/highlight.directive.2.ts index 6b2101b543..156fabaaa8 100644 --- a/public/docs/_examples/attribute-directives/ts/app/highlight.directive.2.ts +++ b/public/docs/_examples/attribute-directives/ts/app/highlight.directive.2.ts @@ -1,15 +1,26 @@ -/* tslint:disable:no-unused-variable */ +/* tslint:disable:no-unused-variable member-ordering */ +// #docplaster // #docregion import { Directive, ElementRef, HostListener, Input } from '@angular/core'; @Directive({ selector: '[myHighlight]' }) - export class HighlightDirective { // #docregion ctor constructor(private el: ElementRef) { } // #enddocregion ctor + // #enddocregion + + // #docregion color + @Input() highlightColor: string; + // #enddocregion color + + // #docregion color-2 + @Input() myHighlight: string; + // #enddocregion color-2 + + // #docregion // #docregion mouse-methods, host @HostListener('mouseenter') onMouseEnter() { @@ -26,7 +37,7 @@ export class HighlightDirective { // #enddocregion host private highlight(color: string) { - this.el.nativeElement.style.backgroundColor = 'yellow'; + this.el.nativeElement.style.backgroundColor = color; } // #enddocregion mouse-methods diff --git a/public/docs/_examples/attribute-directives/ts/app/highlight.directive.3.ts b/public/docs/_examples/attribute-directives/ts/app/highlight.directive.3.ts new file mode 100644 index 0000000000..bf76769c93 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/app/highlight.directive.3.ts @@ -0,0 +1,27 @@ +/* tslint:disable:member-ordering */ +// #docregion +import { Directive, ElementRef, HostListener, Input } from '@angular/core'; + +@Directive({ + selector: '[myHighlight]' +}) +export class HighlightDirective { + + constructor(private el: ElementRef) { } + + @Input('myHighlight') highlightColor: string; + + // #docregion mouse-enter + @HostListener('mouseenter') onMouseEnter() { + this.highlight(this.highlightColor || 'red'); + } + // #enddocregion mouse-enter + + @HostListener('mouseleave') onMouseLeave() { + this.highlight(null); + } + + private highlight(color: string) { + this.el.nativeElement.style.backgroundColor = color; + } +} diff --git a/public/docs/_examples/attribute-directives/ts/app/highlight.directive.ts b/public/docs/_examples/attribute-directives/ts/app/highlight.directive.ts index 5f5c33b0db..68c9f0cc73 100644 --- a/public/docs/_examples/attribute-directives/ts/app/highlight.directive.ts +++ b/public/docs/_examples/attribute-directives/ts/app/highlight.directive.ts @@ -1,24 +1,20 @@ /* tslint:disable:member-ordering */ // #docplaster -// #docregion full +// #docregion +// #docregion imports import { Directive, ElementRef, HostListener, Input } from '@angular/core'; +// #enddocregion imports @Directive({ selector: '[myHighlight]' }) -// #docregion class export class HighlightDirective { - private _defaultColor = 'red'; constructor(private el: ElementRef) { } - // #enddocregion class // #docregion defaultColor - @Input() set defaultColor(colorName: string){ - this._defaultColor = colorName || this._defaultColor; - } + @Input() defaultColor: string; // #enddocregion defaultColor - // #docregion class // #docregion color @Input('myHighlight') highlightColor: string; @@ -26,9 +22,10 @@ export class HighlightDirective { // #docregion mouse-enter @HostListener('mouseenter') onMouseEnter() { - this.highlight(this.highlightColor || this._defaultColor); + this.highlight(this.highlightColor || this.defaultColor || 'red'); } // #enddocregion mouse-enter + @HostListener('mouseleave') onMouseLeave() { this.highlight(null); } @@ -37,10 +34,3 @@ export class HighlightDirective { this.el.nativeElement.style.backgroundColor = color; } } -// #enddocregion class -// #enddocregion full -/* -// #docregion highlight -@Input() myHighlight: string; -// #enddocregion highlight -*/ diff --git a/public/docs/_examples/attribute-directives/ts/plnkr.json b/public/docs/_examples/attribute-directives/ts/plnkr.json index 7124dcb6a7..a64cbd2e95 100644 --- a/public/docs/_examples/attribute-directives/ts/plnkr.json +++ b/public/docs/_examples/attribute-directives/ts/plnkr.json @@ -3,7 +3,7 @@ "files":[ "!**/*.d.ts", "!**/*.js", - "!app/*.[1,2].*" + "!app/*.[1,2,3].*" ], "tags": ["attribute", "directive"] -} \ No newline at end of file +} diff --git a/public/docs/ts/latest/guide/attribute-directives.jade b/public/docs/ts/latest/guide/attribute-directives.jade index 911e815b02..b3d9efc990 100644 --- a/public/docs/ts/latest/guide/attribute-directives.jade +++ b/public/docs/ts/latest/guide/attribute-directives.jade @@ -13,10 +13,10 @@ block includes * [Build a simple attribute directive](#write-directive) * [Apply the attribute directive to an element in a template](#apply-directive) * [Respond to user-initiated events](#respond-to-user) - * [Pass values into the directive using data binding](#bindings) + * [Pass values into the directive with an _@Input_ data binding](#bindings) * [Bind to a second property](#second-property) - Try the`) element. In Angular terms, the `
` element will be the attribute **host**. p | Put the template in its own @@ -141,23 +141,22 @@ figure.image-display ### Your directive isn't working? Did you remember to add the directive to the the `declarations` attribute of `@NgModule`? It is easy to forget! - Open the console in the browser tools and look for an error like this: code-example(format="nocode"). EXCEPTION: Template parse errors: Can't bind to 'myHighlight' since it isn't a known property of 'p'. :marked - Angular detects that you're trying to bind to *something* but it doesn't know what, - so it looks to the `declarations` metadata array. By specifying `HighlightDirective` - in the array, Angular knows to check the import statements and from there, - to go to `highlight.directive.ts` to find out what `myHighlight` does. + Angular detects that you're trying to bind to *something* but it can't find this directive + in the module's `declarations` array. + After specifying `HighlightDirective` in the `declarations` array, + Angular knows it can apply the directive to components declared in this module. :marked - To summarize, Angular found the `myHighlight` attribute on the `
` element. It created - an instance of the `HighlightDirective` class, - injecting a reference to the element into the constructor - where the `
` element's background style is set to yellow. + To summarize, Angular found the `myHighlight` attribute on the `
` element. + It created an instance of the `HighlightDirective` class and + injected a reference to the `
` element into the directive's constructor + which sets the `
` element's background style to yellow. .l-main-section a#respond-to-user @@ -165,37 +164,37 @@ a#respond-to-user ## Respond to user-initiated events Currently, `myHighlight` simply sets an element color. - The directive should set the color when the user hovers over an element. + The directive could be more dynamic. + It could detect when the user mouses into or out of the element + and respond by setting or clearing the highlight color. - This requires two things: - 1. detecting when the user hovers into and out of the element. - 2. responding to those actions by setting and clearing the highlight color. + Begin by adding `HostListener` to the list of imported symbols; + add the `Import` symbol as well because you'll need it soon. ++makeExample('attribute-directives/ts/app/highlight.directive.ts','imports')(format=".") - To do this, you can apply the `@HostListener` !{_decorator} to methods which are called when an event is raised. +:marked + Then add two eventhandlers that respond when the mouse enters or leaves, each adorned by the `HostListener` !{_decorator}. ++makeExample('attribute-directives/ts/app/highlight.directive.2.ts','mouse-methods')(format=".") -+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','host')(format=".") +:marked + The `@HostListener` !{_decorator} lets you subscribe to events of the DOM element that hosts an attribute directive, the `
` in this case. .l-sub-section :marked - The `@HostListener` !{_decorator} refers to the DOM element that hosts an attribute directive, the `
` in this case. - - It is possible to attach event listeners by manipulating the host DOM element directly, but - there are at least three problems with such an approach: + Of course you could reach into the DOM with standard JavaScript and and attach event listeners manually. + There are at least three problems with _that_ approach: 1. You have to write the listeners correctly. 1. The code must *detach* the listener when the directive is destroyed to avoid memory leaks. 1. Talking to DOM API directly isn't a best practice. :marked - Now implement the two mouse event handlers: -+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','mouse-methods')(format=".") -:marked - Notice that they delegate to a helper method that sets the color via a private local variable, `#{_priv}el`. - Next, revise the constructor to capture the `ElementRef.nativeElement` in this variable. + The handlers delegate to a helper method that sets the color on the DOM element, `#{_priv}el`, + which you declare and initialize in the constructor. +makeExample('attribute-directives/ts/app/highlight.directive.2.ts','ctor')(format=".") :marked - Here's the updated directive: + Here's the updated directive in full: +makeExample('app/highlight.directive.2.ts') :marked Run the app and confirm that the background color appears when the mouse hovers over the `p` and @@ -205,70 +204,90 @@ figure.image-display .l-main-section a#bindings :marked - ## Pass values into the directive using data binding + ## Pass values into the directive with an _@Input_ data binding - Currently the highlight color is hard-coded within the directive. That's inflexible. - A better practice is to set the color externally with a binding as follows: -+makeExample('attribute-directives/ts/app/app.component.html','pHost') -:marked - You can extend the directive class with a bindable **input** `highlightColor` property and use it to highlight text. + Currently the highlight color is hard-coded _within_ the directive. That's inflexible. + Let the user of the directive set the color in the template with a binding. + + Start by adding a `highlightColor` property to the directive class like this: ++makeExample('attribute-directives/ts/app/highlight.directive.2.ts','color', 'app/highlight.directive.ts') - Here is the final version of the class: -+makeExcerpt('app/highlight.directive.ts', 'class') a#input :marked - The new `highlightColor` property is called an *input* property because data flows from the binding expression into the directive. - Notice the `@Input()` #{_decorator} applied to the property. -+makeExcerpt('app/highlight.directive.ts', 'color') -:marked - `@Input` adds metadata to the class that makes the `highlightColor` property available for - property binding under the `myHighlight` alias. - Without this input metadata Angular rejects the binding. - See the [appendix](#why-input) below for more information. -.l-sub-section - :marked - ### @Input(_alias_) - Currently, the code **aliases** the `highlightColor` property with the attribute name by - passing `myHighlight` into the `@Input` #{_decorator}: - +makeExcerpt('app/highlight.directive.ts', 'color', '') - :marked - The code binds to the attribute name, `myHighlight`, but the - the directive property name is `highlightColor`. That's a disconnect. + ### Binding to an _@Input_ property - You can resolve the discrepancy by renaming the property to `myHighlight` and define it as follows: + Notice the `@Input` !{_decorator}. It adds metadata to the class that makes the directive's `highlightColor` property available for binding. + + It's called an *input* property because data flows from the binding expression _into_ the directive. + Without that input metadata, Angular rejects the binding; see [below](#why-input "Why add @Input?") for more about that. - +makeExcerpt('app/highlight.directive.ts', 'highlight', '') + Try it by adding the following directive binding variations to the `AppComponent` template: ++makeExample('attribute-directives/ts/app/app.component.1.html','color-1', 'app/app.component.html')(format='.') :marked - Now that you're getting the highlight color as an input, modify the `onMouseEnter()` method to use - it instead of the hard-coded color name and define red as the default color. - -+makeExcerpt('attribute-directives/ts/app/highlight.directive.ts', 'mouse-enter', '') + Add a `color` property to the `AppComponent`. ++makeExample('attribute-directives/ts/app/app.component.1.ts','class', 'app/app.component.ts (class)')(format='.') :marked - To let users pick the highlight color and bind their choice to the directive, - update `app.component.html` as follows: + Let it control the highlight color with a property binding. ++makeExample('attribute-directives/ts/app/app.component.1.html','color-2', 'app/app.component.html') + +:marked + That's good, but it would be nice to _simultaneously_ apply the directive and set the color _in the same attribute_ like this. ++makeExample('attribute-directives/ts/app/app.component.html','color') + +:marked + The `[myHighlight]` attribute binding both applies the highlighting directive to the `
` element
+ and sets the directive's highlight color with a property binding.
+ You're re-using the directive's attribute selector (`[myHighlight]`) to do both jobs.
+ That's a crisp, compact syntax.
+
+ You'll have to rename the directive's `highlightColor` property to `myHighlight` because that's now the color property binding name.
++makeExample('attribute-directives/ts/app/highlight.directive.2.ts','color-2', 'app/highlight.directive.ts (renamed to match directive selector)')
+:marked
+ This is disagreeable. The word, `myHighlight`, is a terrible property name and it doesn't convey the property's intent.
+
+a#input-alias
+:marked
+ ### Bind to an _@Input_ alias
+
+ Fortunately you can name the directive property whatever you want _and_ **_alias it_** for binding purposes.
+
+ Restore the original property name and specify the selector as the alias in the argument to `@Input`.
+
++makeExcerpt('app/highlight.directive.ts', 'color', 'app/highlight.directive.ts (color property with alias')
+:marked
+ _Inside_ the directive the property is known as `highlightColor`.
+ _Outside_ the directive, where you bind to it, it's known as `myHighlight`.
+
+ You get the best of both worlds: the property name you want and the binding syntax you want:
++makeExample('attribute-directives/ts/app/app.component.html','color')
+
+:marked
+ Now that you're binding to `highlightColor`, modify the `onMouseEnter()` method to use it.
+ If someone neglects to bind to `highlightColor`, highlight in "red" by default.
+
++makeExample('attribute-directives/ts/app/highlight.directive.3.ts', 'mouse-enter', 'app/highlight.directive.ts (mouse enter)')(format='.')
+:marked
+ Here's the latest version of the directive class.
++makeExcerpt('app/highlight.directive.3.ts')
+
+:marked
+ ## Write a harness to try it
+:marked
+ It may be difficult to imagine how this directive actually works.
+ In this section, you'll turn `AppComponent` into a harness that
+ lets you pick the highlight color with a radio button and bind your color choice to the directive.
+
+ Update `app.component.html` as follows:
+makeExcerpt('attribute-directives/ts/app/app.component.html', 'v2', '')
-.l-sub-section
- :marked
- ### Where is the templated *color* property?
-
- You may notice that the radio button click handlers in the template set a `color` property
- and the code is binding that `color` to the directive.
- However, you never defined a color property for the host `AppComponent`.
- 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.
- For clarity, consider adding the `color` property to the `AppComponent`.
-
-
+:marked
+ Revise the `AppComponent.color` so that it has no initial value.
++makeExcerpt('attribute-directives/ts/app/app.component.ts', 'class', '')
:marked
- Here is the second version of the directive in action.
+ Here is the harness and directive in action.
figure.image-display
img(src="/resources/images/devguide/attribute-directives/highlight-directive-v2-anim.gif" alt="Highlight v.2")
@@ -276,31 +295,29 @@ figure.image-display
a#second-property
:marked
## Bind to a second property
- This example directive only has a single customizable property. A real app often needs more.
+ This highlight directive has a single customizable property. In a real app, it may need more.
- Let's allow the template developer to set the default color—the color that prevails until the user picks a highlight color.
- To do this, first add a second **input** property to `HighlightDirective` called `defaultColor`:
-+makeExample('attribute-directives/ts/app/highlight.directive.ts', 'defaultColor')(format=".")
+ At the moment, the default color — the color that prevails until the user picks a highlight color —
+ is hard-coded as "red". Let the template developer set the default color.
+
+ Add a second **input** property to `HighlightDirective` called `defaultColor`:
++makeExcerpt('attribute-directives/ts/app/highlight.directive.ts', 'defaultColor','app/highlight.directive.ts (defaultColor)')
:marked
- The `defaultColor` property has a setter that overrides the hard-coded default color, "red".
- You don't need a getter.
-
- How do you bind to it? The app is already using `myHighlight` attribute name as a binding target.
-
- Remember that a *component is a directive, too*.
- You can add as many component property bindings as you need by stringing them along in the template
- as in this example that sets the `a`, `b`, `c` properties to the string literals 'a', 'b', and 'c'.
-code-example(format="." ).
- <my-component [a]="'a'" [b]="'b'" [c]="'c'"><my-component>
+ Revise the directive's `onMouseEnter` so that it first tries to highlight with the `highlightColor`,
+ then with the `defaultColor`, and falls back to "red" if both properties are undefined.
++makeExample('attribute-directives/ts/app/highlight.directive.ts', 'mouse-enter')(format=".")
:marked
- The same holds true for an attribute directive.
+ How do you bind to a second property when you're already binding to the `myHighlight` attribute name?
+
+ As with components, you can add as many directive property bindings as you need by stringing them along in the template.
+ The developer should be able to write the following template HTML to both bind to the `AppComponent.color`
+ and fall back to "violet" as the default color.
+makeExample('attribute-directives/ts/app/app.component.html', 'defaultColor')(format=".")
:marked
- Here the code is binding the user's color choice to the `myHighlight` attribute as before.
- It is *also* binding the literal string, 'violet', to the `defaultColor`.
+ Angular knows that the `defaultColor` binding belongs to the `HighlightDirective`
+ because you made it _public_ with the `@Input` !{_decorator}.
-
- Here is the final version of the directive in action.
+ Here's how the harness should work when you're done coding.
figure.image-display
img(src="/resources/images/devguide/attribute-directives/highlight-directive-final-anim.gif" alt="Final Highlight")
@@ -308,12 +325,12 @@ figure.image-display
:marked
## Summary
This page covered how to:
- - [Build a simple **attribute directive** to attach behavior to an HTML element](#write-directive).
- - [Use that directive in a template](#apply-directive).
- - [Respond to **events** to change behavior based on an event](#respond-to-user).
- - [Use **binding** to pass values to the attribute directive](#bindings).
+ - [Build an **attribute directive**](#write-directive) that modifies the behavior of an element.
+ - [Apply the directive](#apply-directive) to an element in a template.
+ - [Respond to **events**](#respond-to-user) that change the directive's behavior.
+ - [**Bind** values to the directive](#bindings).
- The final source:
+ The final source code follows:
+makeTabs(
`attribute-directives/ts/app/app.component.ts,
@@ -323,7 +340,7 @@ figure.image-display
attribute-directives/ts/app/main.ts,
attribute-directives/ts/index.html
`,
- ',,full',
+ '',
`app.component.ts,
app.component.html,
highlight.directive.ts,
@@ -332,43 +349,57 @@ figure.image-display
index.html
`)
+:marked
+ You can also experience and download the