From a29fca1c99af11fe6240c3af98a5d4594f64c501 Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Tue, 17 Oct 2017 16:42:05 -0700 Subject: [PATCH] docs: AttributeDirectives guide for CLI --- .../examples/attribute-directives/plnkr.json | 2 +- .../src/app/highlight.directive.0.ts | 9 ++ .../src/app/highlight.directive.1.ts | 6 +- .../src/app/highlight.directive.2.ts | 7 +- .../src/app/highlight.directive.3.ts | 3 +- .../src/app/highlight.directive.ts | 2 - aio/content/guide/attribute-directives.md | 148 ++++++++---------- aio/content/guide/structural-directives.md | 2 +- 8 files changed, 87 insertions(+), 92 deletions(-) create mode 100644 aio/content/examples/attribute-directives/src/app/highlight.directive.0.ts diff --git a/aio/content/examples/attribute-directives/plnkr.json b/aio/content/examples/attribute-directives/plnkr.json index 112e1de6f47f0..b13ed925a4da0 100644 --- a/aio/content/examples/attribute-directives/plnkr.json +++ b/aio/content/examples/attribute-directives/plnkr.json @@ -4,7 +4,7 @@ "files":[ "!**/*.d.ts", "!**/*.js", - "!app/*.[1,2,3].*" + "!app/*.[0,1,2,3].*" ], "tags": ["attribute", "directive"] } diff --git a/aio/content/examples/attribute-directives/src/app/highlight.directive.0.ts b/aio/content/examples/attribute-directives/src/app/highlight.directive.0.ts new file mode 100644 index 0000000000000..a15f757cf5a6f --- /dev/null +++ b/aio/content/examples/attribute-directives/src/app/highlight.directive.0.ts @@ -0,0 +1,9 @@ +// #docregion +import { Directive } from '@angular/core'; + +@Directive({ + selector: '[appHighlight]' +}) +export class HighlightDirective { + constructor() { } +} diff --git a/aio/content/examples/attribute-directives/src/app/highlight.directive.1.ts b/aio/content/examples/attribute-directives/src/app/highlight.directive.1.ts index bc9d1ac6e1f61..1ce9949ec0d61 100644 --- a/aio/content/examples/attribute-directives/src/app/highlight.directive.1.ts +++ b/aio/content/examples/attribute-directives/src/app/highlight.directive.1.ts @@ -1,8 +1,10 @@ /* tslint:disable:no-unused-variable */ // #docregion -import { Directive, ElementRef, Input } from '@angular/core'; +import { Directive, ElementRef } from '@angular/core'; -@Directive({ selector: '[appHighlight]' }) +@Directive({ + selector: '[appHighlight]' +}) export class HighlightDirective { constructor(el: ElementRef) { el.nativeElement.style.backgroundColor = 'yellow'; diff --git a/aio/content/examples/attribute-directives/src/app/highlight.directive.2.ts b/aio/content/examples/attribute-directives/src/app/highlight.directive.2.ts index 15765ca481f82..fdf8ba0c85c1d 100644 --- a/aio/content/examples/attribute-directives/src/app/highlight.directive.2.ts +++ b/aio/content/examples/attribute-directives/src/app/highlight.directive.2.ts @@ -1,7 +1,10 @@ /* tslint:disable:no-unused-variable member-ordering */ // #docplaster +// #docregion imports, +import { Directive, ElementRef, HostListener } from '@angular/core'; +// #enddocregion imports, +import { Input } from '@angular/core'; // #docregion -import { Directive, ElementRef, HostListener, Input } from '@angular/core'; @Directive({ selector: '[appHighlight]' @@ -35,7 +38,7 @@ export class HighlightDirective { // #enddocregion color // #docregion color-2 - @Input() myHighlight: string; + @Input() appHighlight: string; // #enddocregion color-2 } diff --git a/aio/content/examples/attribute-directives/src/app/highlight.directive.3.ts b/aio/content/examples/attribute-directives/src/app/highlight.directive.3.ts index c0892fa3812fa..01c7cb679c2e9 100644 --- a/aio/content/examples/attribute-directives/src/app/highlight.directive.3.ts +++ b/aio/content/examples/attribute-directives/src/app/highlight.directive.3.ts @@ -1,6 +1,7 @@ /* tslint:disable:member-ordering */ -// #docregion +// #docregion, imports import { Directive, ElementRef, HostListener, Input } from '@angular/core'; +// #enddocregion imports @Directive({ selector: '[appHighlight]' diff --git a/aio/content/examples/attribute-directives/src/app/highlight.directive.ts b/aio/content/examples/attribute-directives/src/app/highlight.directive.ts index a5a1eb7d898b1..dd138ae09deef 100644 --- a/aio/content/examples/attribute-directives/src/app/highlight.directive.ts +++ b/aio/content/examples/attribute-directives/src/app/highlight.directive.ts @@ -1,7 +1,5 @@ /* tslint:disable:member-ordering */ -// #docregion imports, import { Directive, ElementRef, HostListener, Input } from '@angular/core'; -// #enddocregion imports @Directive({ selector: '[appHighlight]' diff --git a/aio/content/guide/attribute-directives.md b/aio/content/guide/attribute-directives.md index 9c0daebdc632b..05d07bf4000bc 100644 --- a/aio/content/guide/attribute-directives.md +++ b/aio/content/guide/attribute-directives.md @@ -33,7 +33,7 @@ An attribute directive minimally requires building a controller class annotated the attribute. The controller class implements the desired directive behavior. -This page demonstrates building a simple _myHighlight_ attribute +This page demonstrates building a simple _appHighlight_ attribute directive to set an element's background color when the user hovers over that element. You can apply it like this: @@ -43,106 +43,84 @@ when the user hovers over that element. You can apply it like this: ### Write the directive code -Follow the [setup](guide/setup) instructions for creating a new local project -named attribute-directives. +Create the directive class file in a terminal window with this CLI command. -Create the following source file in the indicated folder: + +ng generate directive highlight + - +The CLI creates `src/app/highlight.directive.ts`, a corresponding test file (`.../spec.ts`, and _declares_ the directive class in the root `AppModule`. + +
+ +_Directives_ must be declared in [Angular Modules](guide/ngmodule) in the same manner as _components_. + +
+ +The generated `src/app/highlight.directive.ts` is as follows: -The `import` statement specifies symbols from the Angular `core`: + -1. `Directive` provides the functionality of the `@Directive` decorator. -1. `ElementRef` [injects](guide/dependency-injection) 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. +The imported `Directive` symbol provides the Angular the `@Directive` decorator. -Next, the `@Directive` decorator function contains the directive metadata in a configuration object -as an argument. +The `@Directive` decorator's lone configuration property specifies the directive's +[CSS attribute selector](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors), `[appHighlight]`. -`@Directive` requires a CSS selector to identify -the HTML in the template that is associated with the 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. -Here, the directive's selector is `[myHighlight]`. -Angular locates all elements in the template that have an attribute named `myHighlight`. +It's the brackets (`[]`) that make it an attribute selector. +Angular locates each element in the template that has an attribute named `appHighlight` and applies the logic of this directive to that element. + +The _attribute selector_ pattern explains the name of this kind of directive.
-### Why not call it "highlight"? +#### Why not "highlight"? -Though *highlight* is a more concise name than *myHighlight* and would work, -a best practice is to prefix selector names to ensure +Though *highlight* would be a more concise selector than *appHighlight* and it would work, +the best practice is to prefix selector names to ensure they don't conflict with standard HTML attributes. This also reduces the risk of colliding with third-party directive names. +The CLI added the `app` prefix for you. 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.
- 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. +called `HighlightDirective`, which contains the (currently empty) logic for the directive. +Exporting `HighlightDirective` makes the directive accessible. -{@a apply-directive} - -## Apply the attribute directive +Now edit the generated `src/app/highlight.directive.ts` to look as follows: -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**. + -Put the template in its own app.component.html -file that looks like this: +The `import` statement specifies an additional `ElementRef` symbol from the Angular `core` library: - +You use the `ElementRef`in the directive's constructor +to [inject](guide/dependency-injection) a reference to the host DOM element, +the element to which you applied `appHighlight`. -Now reference this template in the `AppComponent`: +`ElementRef` grants direct access to the host DOM element +through its `nativeElement` property. - +This first implementation sets the background color of the host element to yellow. -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. +{@a apply-directive} - +## Apply the attribute directive -Now when the app runs, the `myHighlight` directive highlights the paragraph text. +To use the new `HighlightDirective`, add a paragraph (`

`) element to the template of the root `AppComponent` and apply the directive as an attribute. -

- First Highlight -
+ -
+Now run the application to see the `HighlightDirective` in action. -

Your directive isn't working?

-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: - - - EXCEPTION: Template parse errors: - Can't bind to 'myHighlight' since it isn't a known property of 'p'. + +ng serve -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. - -
- -To summarize, Angular found the `myHighlight` attribute on the `

` element. +To summarize, Angular found the `appHighlight` attribute on the **host** `

` 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. @@ -151,15 +129,14 @@ which sets the `

` element's background style to yellow. ## Respond to user-initiated events -Currently, `myHighlight` simply sets an element color. +Currently, `appHighlight` simply sets an element color. 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. -Begin by adding `HostListener` to the list of imported symbols; -add the `Input` symbol as well because you'll need it soon. +Begin by adding `HostListener` to the list of imported symbols. - + Then add two eventhandlers that respond when the mouse enters or leaves, each adorned by the `HostListener` decorator. @@ -180,8 +157,10 @@ There are at least three problems with _that_ approach: -The handlers delegate to a helper method that sets the color on the DOM element, `el`, -which you declare and initialize in the constructor. +The handlers delegate to a helper method that sets the color on the host DOM element, `el`. + +The helper method, `highlight`, was extracted from the constructor. +The revised constructor simply declares the injected `el: ElementRef`. @@ -203,7 +182,10 @@ the mouse hovers over the `p` and disappears as it moves out. Currently the highlight color is hard-coded _within_ the directive. That's inflexible. 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: +Begin by adding `Input` to the list of symbols imported from `@angular/core`. + + +Add a `highlightColor` property to the directive class like this: @@ -232,16 +214,16 @@ That's good, but it would be nice to _simultaneously_ apply the directive and se -The `[myHighlight]` attribute binding both applies the highlighting directive to the `

` element +The `[appHighlight]` 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. +You're re-using the directive's attribute selector (`[appHighlight]`) 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. +You'll have to rename the directive's `highlightColor` property to `appHighlight` because that's now the color property binding name. -This is disagreeable. The word, `myHighlight`, is a terrible property name and it doesn't convey the property's intent. +This is disagreeable. The word, `appHighlight`, is a terrible property name and it doesn't convey the property's intent. {@a input-alias} @@ -254,14 +236,14 @@ Restore the original property name and specify the selector as the alias in the _Inside_ the directive the property is known as `highlightColor`. -_Outside_ the directive, where you bind to it, it's known as `myHighlight`. +_Outside_ the directive, where you bind to it, it's known as `appHighlight`. You get the best of both worlds: the property name you want and the binding syntax you want: -Now that you're binding to `highlightColor`, modify the `onMouseEnter()` method to use it. -If someone neglects to bind to `highlightColor`, highlight in red: +Now that you're binding via the alias to the `highlightColor`, modify the `onMouseEnter()` method to use that property. +If someone neglects to bind to `appHighlightColor`, highlight the host element in red: @@ -308,7 +290,7 @@ then with the `defaultColor`, and falls back to "red" if both properties are und -How do you bind to a second property when you're already binding to the `myHighlight` attribute name? +How do you bind to a second property when you're already binding to the `appHighlight` 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` @@ -398,6 +380,6 @@ Now apply that reasoning to the following example: 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 `HighlightDirective`, +* The `appHighlight` 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. diff --git a/aio/content/guide/structural-directives.md b/aio/content/guide/structural-directives.md index e8892bb282cc7..45831ca3f832c 100644 --- a/aio/content/guide/structural-directives.md +++ b/aio/content/guide/structural-directives.md @@ -25,7 +25,7 @@ They shape or reshape the DOM's _structure_, typically by adding, removing, or m elements. As with other directives, you apply a structural directive to a _host element_. -The directive then does whatever it's supposed to do with that host element and its descendents. +The directive then does whatever it's supposed to do with that host element and its descendants. Structural directives are easy to recognize. An asterisk (*) precedes the directive attribute name as in this example.