diff --git a/public/docs/_examples/attribute-directives/e2e-spec.ts b/public/docs/_examples/attribute-directives/e2e-spec.ts index 25a0cf258e..79425d7923 100644 --- a/public/docs/_examples/attribute-directives/e2e-spec.ts +++ b/public/docs/_examples/attribute-directives/e2e-spec.ts @@ -15,7 +15,7 @@ describe('Attribute directives', function () { }); it('should be able to select green highlight', function () { - let highlightedEle = element(by.cssContainingText('p', 'Highlight me')); + let highlightedEle = element(by.cssContainingText('p', 'Highlight me!')); let lightGreen = 'rgba(144, 238, 144, 1)'; expect(highlightedEle.getCssValue('background-color')).not.toEqual(lightGreen); 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 index 9f8c13f077..9505bc9dff 100644 --- 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 @@ -2,19 +2,13 @@

My First Attribute Directive

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/src/app/app.component.1.ts b/public/docs/_examples/attribute-directives/ts/src/app/app.component.1.ts index d65cb57850..ded6e8c3c5 100644 --- a/public/docs/_examples/attribute-directives/ts/src/app/app.component.1.ts +++ b/public/docs/_examples/attribute-directives/ts/src/app/app.component.1.ts @@ -9,4 +9,3 @@ import { Component } from '@angular/core'; export class AppComponent { color = 'yellow'; } -// #enddocregion class 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 index 0ef41b925d..3adb52bc1e 100644 --- a/public/docs/_examples/attribute-directives/ts/src/app/app.component.html +++ b/public/docs/_examples/attribute-directives/ts/src/app/app.component.html @@ -1,5 +1,4 @@ - - +

My First Attribute Directive

Pick a highlight color

@@ -10,15 +9,13 @@

Highlight me!

- - +

Highlight me too!

- - +

Mouse over the following lines to see fixed highlights

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 index a88e8e49a3..962f88fcb9 100644 --- a/public/docs/_examples/attribute-directives/ts/src/app/app.component.ts +++ b/public/docs/_examples/attribute-directives/ts/src/app/app.component.ts @@ -10,5 +10,3 @@ import { Component } from '@angular/core'; export class AppComponent { color: string; } -// #enddocregion class -// #enddocregion 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 index 156fabaaa8..4696132f64 100644 --- 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 @@ -10,17 +10,6 @@ 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() { @@ -39,7 +28,14 @@ export class HighlightDirective { private highlight(color: string) { this.el.nativeElement.style.backgroundColor = color; } - // #enddocregion mouse-methods + // #enddocregion mouse-methods, + // #docregion color + @Input() highlightColor: string; + // #enddocregion color + + // #docregion color-2 + @Input() myHighlight: string; + // #enddocregion color-2 } -// #enddocregion + 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 index 68c9f0cc73..97b1497013 100644 --- a/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.ts +++ b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.ts @@ -1,7 +1,5 @@ /* tslint:disable:member-ordering */ -// #docplaster -// #docregion -// #docregion imports +// #docregion imports, import { Directive, ElementRef, HostListener, Input } from '@angular/core'; // #enddocregion imports diff --git a/public/docs/ts/latest/guide/attribute-directives.jade b/public/docs/ts/latest/guide/attribute-directives.jade index 469b2eb7f7..dee09df5af 100644 --- a/public/docs/ts/latest/guide/attribute-directives.jade +++ b/public/docs/ts/latest/guide/attribute-directives.jade @@ -4,11 +4,8 @@ block includes :marked An **Attribute** directive changes the appearance or behavior of a DOM element. - -:marked # Contents - * [Directives overview](#directive-overview) * [Build a simple attribute directive](#write-directive) * [Apply the attribute directive to an element in a template](#apply-directive) @@ -18,13 +15,12 @@ block includes Try the . -.l-main-section -a#directive-overview +.l-main-section#directive-overview :marked ## Directives overview There are three kinds of directives in Angular: - + 1. Components—directives with a template. 1. Structural directives—change the DOM layout by adding and removing DOM elements. 1. Attribute directives—change the appearance or behavior of an element, component, or another directive. @@ -37,13 +33,14 @@ a#directive-overview Learn about them in the [Structural Directives](structural-directives.html) guide. *Attribute directives* are used as attributes of elements. - The built-in [NgStyle](template-syntax.html#ngStyle) directive in the [Template Syntax](template-syntax.html) guide, for example, + The built-in [NgStyle](template-syntax.html#ngStyle) directive in the + [Template Syntax](template-syntax.html) guide, for example, can change several element styles at the same time. -.l-main-section -a#write-directive +.l-main-section#write-directive :marked ## Build a simple attribute directive + An attribute directive minimally requires building a controller class annotated with `@Directive`, which specifies the selector that identifies the attribute. @@ -53,27 +50,28 @@ a#write-directive directive to set an element's background color when the user hovers over that element. You can apply it like this: -+makeExample('attribute-directives/ts/src/app/app.component.1.html','applied') ++makeExcerpt('src/app/app.component.1.html', 'applied', '') :marked ### Write the directive code + Follow the [setup](setup.html) instructions for creating a new local project named attribute-directives. -:marked - Create the following source file in `src/app` with the following code: + Create the following source file in the indicated folder: + +makeExample('src/app/highlight.directive.1.ts') -block highlight-directive-1 - :marked - The `import` statement specifies symbols from the Angular `core`: - 1. `Directive` provides the functionality of the `@Directive` decorator. - 1. `ElementRef` [injects](dependency-injection.html) into the directive's constructor - so the code can access the DOM element. - 1. `Input` allows data to flow from the binding expression into the directive. +:marked + The `import` statement specifies symbols from the Angular `core`: - Next, the `@Directive` decorator function contains the directive metadata in a configuration object - as an argument. + 1. `Directive` provides the functionality of the `@Directive` decorator. + 1. `ElementRef` [injects](dependency-injection.html) into the directive's constructor + so the code can access the DOM element. + 1. `Input` allows data to flow from the binding expression into the directive. + + Next, the `@Directive` decorator function contains the directive metadata in a configuration object + as an argument. :marked `@Directive` requires a CSS selector to identify @@ -82,59 +80,70 @@ block highlight-directive-1 is the attribute name in square brackets. Here, the directive's selector is `[myHighlight]`. Angular locates all elements in the template that have an attribute named `myHighlight`. + .l-sub-section :marked ### Why not call it "highlight"? + Though *highlight* is a more concise name than *myHighlight* and would work, a best practice is to prefix selector names to ensure - they don't conflict with standard HTML attributes. + they don't conflict with standard HTML attributes. This also reduces the risk of colliding with third-party directive names. Make sure you do **not** prefix the `highlight` directive name with **`ng`** because - that prefix is reserved for Angular and using it could cause bugs that are difficult to diagnose. For a simple demo, the short prefix, `my`, helps distinguish your custom directive. + that prefix is reserved for Angular and using it could cause bugs that are difficult to diagnose. + For a simple demo, the short prefix, `my`, helps distinguish your custom directive. -p - | After the #[code @Directive] metadata comes the directive's controller class, called #[code HighlightDirective], which contains the logic for the directive. - +ifDocsFor('ts') - | Exporting #[code HighlightDirective] makes it accessible to other components. :marked + After the `@Directive` metadata comes the directive's controller class, + called `HighlightDirective`, which contains the logic for the directive. + Exporting `HighlightDirective` makes it accessible to other components. + Angular creates a new instance of the directive's controller class for each matching element, injecting an Angular `ElementRef` into the constructor. `ElementRef` is a service that grants direct access to the DOM element through its `nativeElement` property. -.l-main-section -a#apply-directive +.l-main-section#apply-directive :marked ## Apply the attribute directive + To use the new `HighlightDirective`, create a template that applies the directive as an attribute to a paragraph (`

`) element. In Angular terms, the `

` element is the attribute **host**. -p - | Put the template in its own - code #[+adjExPath('app.component.html')] - | file that looks like this: -+makeExample('attribute-directives/ts/src/app/app.component.1.html',null,'src/app/app.component.html')(format=".") + + Put the template in its own app.component.html + file that looks like this: + ++makeExample('src/app/app.component.1.html') + :marked Now reference this template in the `AppComponent`: -+makeExample('attribute-directives/ts/src/app/app.component.ts',null,'src/app/app.component.ts') + ++makeExample('src/app/app.component.ts') + :marked Next, add an `import` statement to fetch the `Highlight` directive and add that class to the `declarations` NgModule metadata. This way Angular recognizes the directive when it encounters `myHighlight` in the template. -+makeExample('attribute-directives/ts/src/app/app.module.ts',null,'src/app/app.module.ts') + ++makeExample('src/app/app.module.ts') + :marked Now when the app runs, the `myHighlight` directive highlights the paragraph text. figure.image-display img(src="/resources/images/devguide/attribute-directives/first-highlight.png" alt="First Highlight") + .l-sub-section :marked ### Your directive isn't working? - Did you remember to add the directive to the `declarations` attribute of `@NgModule`? It is easy to forget! + Did you remember to add the directive to 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'. @@ -151,8 +160,7 @@ figure.image-display 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 +.l-main-section#respond-to-user :marked ## Respond to user-initiated events @@ -163,14 +171,18 @@ a#respond-to-user Begin by adding `HostListener` to the list of imported symbols; add the `Input` symbol as well because you'll need it soon. -+makeExample('attribute-directives/ts/src/app/highlight.directive.ts','imports')(format=".") + ++makeExcerpt('src/app/highlight.directive.ts', 'imports', '') :marked - Then add two eventhandlers that respond when the mouse enters or leaves, each adorned by the `HostListener` !{_decorator}. -+makeExample('attribute-directives/ts/src/app/highlight.directive.2.ts','mouse-methods')(format=".") + Then add two eventhandlers that respond when the mouse enters or leaves, + each adorned by the `HostListener` !{_decorator}. + ++makeExcerpt('src/app/highlight.directive.2.ts','mouse-methods', '') :marked - The `@HostListener` !{_decorator} lets you subscribe to events of the DOM element that hosts an attribute directive, the `

` in this case. + 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 @@ -185,17 +197,21 @@ a#respond-to-user 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/src/app/highlight.directive.2.ts','ctor')(format=".") ++makeExcerpt('src/app/highlight.directive.2.ts (constructor)', 'ctor') + :marked Here's the updated directive in full: + +makeExample('src/app/highlight.directive.2.ts') + :marked - Run the app and confirm that the background color appears when the mouse hovers over the `p` and - disappears as it moves out. + Run the app and confirm that the background color appears when + the mouse hovers over the `p` and disappears as it moves out. + figure.image-display img(src="/resources/images/devguide/attribute-directives/highlight-directive-anim.gif" alt="Second Highlight") -.l-main-section -a#bindings + +.l-main-section#bindings :marked ## Pass values into the directive with an _@Input_ data binding @@ -203,7 +219,8 @@ a#bindings In this section, you give the developer the power to set the highlight color while applying the directive. Start by adding a `highlightColor` property to the directive class like this: -+makeExample('attribute-directives/ts/src/app/highlight.directive.2.ts','color', 'src/app/highlight.directive.ts') + ++makeExcerpt('src/app/highlight.directive.2.ts (highlightColor)','color') a#input :marked @@ -215,18 +232,23 @@ a#input Without that input metadata, Angular rejects the binding; see [below](#why-input "Why add @Input?") for more about that. Try it by adding the following directive binding variations to the `AppComponent` template: -+makeExample('attribute-directives/ts/src/app/app.component.1.html','color-1', 'src/app/app.component.html')(format='.') + ++makeExcerpt('src/app/app.component.1.html (excerpt)', 'color-1') :marked Add a `color` property to the `AppComponent`. -+makeExample('attribute-directives/ts/src/app/app.component.1.ts','class', 'src/app/app.component.ts (class)')(format='.') + ++makeExcerpt('src/app/app.component.1.ts (class)') + :marked Let it control the highlight color with a property binding. -+makeExample('attribute-directives/ts/src/app/app.component.1.html','color-2', 'src/app/app.component.html') + ++makeExcerpt('src/app/app.component.1.html (excerpt)', 'color-2') :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/src/app/app.component.html','color') + ++makeExcerpt('src/app/app.component.html', 'color', '') :marked The `[myHighlight]` attribute binding both applies the highlighting directive to the `

` element @@ -235,7 +257,9 @@ a#input 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/src/app/highlight.directive.2.ts','color-2', 'src/app/highlight.directive.ts (renamed to match directive selector)') + ++makeExcerpt('src/app/highlight.directive.2.ts (renamed to match directive selector)', 'color-2') + :marked This is disagreeable. The word, `myHighlight`, is a terrible property name and it doesn't convey the property's intent. @@ -247,78 +271,93 @@ a#input-alias Restore the original property name and specify the selector as the alias in the argument to `@Input`. -+makeExcerpt('src/app/highlight.directive.ts', 'color', 'src/app/highlight.directive.ts (color property with alias') ++makeExcerpt('src/app/highlight.directive.ts (color property with alias)', 'color') + :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/src/app/app.component.html','color') + ++makeExcerpt('src/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. + If someone neglects to bind to `highlightColor`, highlight in red: + ++makeExcerpt('src/app/highlight.directive.3.ts (mouse enter)', 'mouse-enter') -+makeExample('attribute-directives/ts/src/app/highlight.directive.3.ts', 'mouse-enter', 'src/app/highlight.directive.ts (mouse enter)')(format='.') :marked Here's the latest version of the directive class. + +makeExcerpt('src/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: + Update app.component.html as follows: -+makeExcerpt('attribute-directives/ts/src/app/app.component.html', 'v2', '') ++makeExcerpt('src/app/app.component.html', 'v2', '') :marked Revise the `AppComponent.color` so that it has no initial value. -+makeExcerpt('attribute-directives/ts/src/app/app.component.ts', 'class', '') + ++makeExcerpt('src/app/app.component.ts', 'class', '') :marked - Here is the harness and directive in action. + Here are 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") -.l-main-section -a#second-property +.l-main-section#second-property :marked ## Bind to a second property + This highlight directive has a single customizable property. In a real app, it may need more. - At the moment, the default color—the color that prevails until - the user picks a highlight color—is hard-coded as "red". + 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/src/app/highlight.directive.ts', 'defaultColor','src/app/highlight.directive.ts (defaultColor)') + ++makeExcerpt('src/app/highlight.directive.ts (defaultColor)') + :marked 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/src/app/highlight.directive.ts', 'mouse-enter')(format=".") + ++makeExcerpt('src/app/highlight.directive.ts', 'mouse-enter', '') + :marked 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/src/app/app.component.html', 'defaultColor')(format=".") + ++makeExcerpt('src/app/app.component.html', 'defaultColor', '') + :marked Angular knows that the `defaultColor` binding belongs to the `HighlightDirective` because you made it _public_ with the `@Input` !{_decorator}. 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") .l-main-section :marked ## Summary + This page covered how to: + - [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. @@ -346,17 +385,19 @@ figure.image-display :marked You can also experience and download the . -a#why-input -.l-main-section +.l-main-section#why-input :marked ### Appendix: Why add _@Input_? In this demo, the `hightlightColor` property is an ***input*** property of the `HighlightDirective`. You've seen it applied without an alias: -+makeExample('attribute-directives/ts/src/app/highlight.directive.2.ts','color') + ++makeExcerpt('src/app/highlight.directive.2.ts', 'color', '') + :marked You've seen it with an alias: -+makeExample('attribute-directives/ts/src/app/highlight.directive.ts','color') + ++makeExcerpt('src/app/highlight.directive.ts', 'color', '') :marked Either way, the `@Input` !{_decorator} tells Angular that this property is @@ -381,19 +422,21 @@ a#why-input You can tell if `@Input` is needed by the position of the property name in a binding. * When it appears in the template expression to the ***right*** of the equals (=), - it belongs to the template's component and does not require the `@Input` !{_decorator}. + it belongs to the template's component and does not require the `@Input` !{_decorator}. * When it appears in **square brackets** ([ ]) to the **left** of the equals (=), - the property belongs to some _other_ component or directive; - that property must be adorned with the `@Input` !{_decorator}. + the property belongs to some _other_ component or directive; + that property must be adorned with the `@Input` !{_decorator}. Now apply that reasoning to the following example: -+makeExample('attribute-directives/ts/src/app/app.component.html','color')(format=".") + ++makeExcerpt('src/app/app.component.html', 'color', '') + :marked * The `color` property in the expression on the right belongs to the template's component. - The template and its component trust each other. - The `color` property doesn't require the `@Input` !{_decorator}. + The template and its component trust each other. + The `color` property doesn't require the `@Input` !{_decorator}. - * The `myHighlight` property on the left refers to an _aliased_ property of the `MyHighlightDirective`, - not a property of the template's component. There are trust issues. - Therefore, the directive property must carry the `@Input` !{_decorator}. + * The `myHighlight` property on the left refers to an _aliased_ property of the `HighlightDirective`, + not a property of the template's component. There are trust issues. + Therefore, the directive property must carry the `@Input` !{_decorator}.