docs: AttributeDirectives guide for CLI (#19771)

PR Close #19771
This commit is contained in:
Ward Bell 2017-10-17 16:42:05 -07:00 committed by Jason Aden
parent 24cf8b3269
commit 75d474e1d3
8 changed files with 93 additions and 98 deletions

View File

@ -4,7 +4,7 @@
"files":[ "files":[
"!**/*.d.ts", "!**/*.d.ts",
"!**/*.js", "!**/*.js",
"!app/*.[1,2,3].*" "!app/*.[0,1,2,3].*"
], ],
"tags": ["attribute", "directive"] "tags": ["attribute", "directive"]
} }

View File

@ -0,0 +1,9 @@
// #docregion
import { Directive } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor() { }
}

View File

@ -1,8 +1,10 @@
/* tslint:disable:no-unused-variable */ /* tslint:disable:no-unused-variable */
// #docregion // #docregion
import { Directive, ElementRef, Input } from '@angular/core'; import { Directive, ElementRef } from '@angular/core';
@Directive({ selector: '[appHighlight]' }) @Directive({
selector: '[appHighlight]'
})
export class HighlightDirective { export class HighlightDirective {
constructor(el: ElementRef) { constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow'; el.nativeElement.style.backgroundColor = 'yellow';

View File

@ -1,7 +1,10 @@
/* tslint:disable:no-unused-variable member-ordering */ /* tslint:disable:no-unused-variable member-ordering */
// #docplaster // #docplaster
// #docregion imports,
import { Directive, ElementRef, HostListener } from '@angular/core';
// #enddocregion imports,
import { Input } from '@angular/core';
// #docregion // #docregion
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({ @Directive({
selector: '[appHighlight]' selector: '[appHighlight]'
@ -35,7 +38,7 @@ export class HighlightDirective {
// #enddocregion color // #enddocregion color
// #docregion color-2 // #docregion color-2
@Input() myHighlight: string; @Input() appHighlight: string;
// #enddocregion color-2 // #enddocregion color-2
} }

View File

@ -1,6 +1,7 @@
/* tslint:disable:member-ordering */ /* tslint:disable:member-ordering */
// #docregion // #docregion, imports
import { Directive, ElementRef, HostListener, Input } from '@angular/core'; import { Directive, ElementRef, HostListener, Input } from '@angular/core';
// #enddocregion imports
@Directive({ @Directive({
selector: '[appHighlight]' selector: '[appHighlight]'

View File

@ -1,7 +1,5 @@
/* tslint:disable:member-ordering */ /* tslint:disable:member-ordering */
// #docregion imports,
import { Directive, ElementRef, HostListener, Input } from '@angular/core'; import { Directive, ElementRef, HostListener, Input } from '@angular/core';
// #enddocregion imports
@Directive({ @Directive({
selector: '[appHighlight]' selector: '[appHighlight]'

View File

@ -33,7 +33,7 @@ An attribute directive minimally requires building a controller class annotated
the attribute. the attribute.
The controller class implements the desired directive behavior. 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 directive to set an element's background color
when the user hovers over that element. You can apply it like this: 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 ### Write the directive code
Follow the [setup](guide/setup) instructions for creating a new local project Create the directive class file in a terminal window with this CLI command.
named <code>attribute-directives</code>.
Create the following source file in the indicated folder: <code-example language="sh" class="code-shell">
ng generate directive highlight
</code-example>
<code-example path="attribute-directives/src/app/highlight.directive.1.ts" title="src/app/highlight.directive.ts"></code-example> The CLI creates `src/app/highlight.directive.ts`, a corresponding test file (`.../spec.ts`, and _declares_ the directive class in the root `AppModule`.
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.
Next, the `@Directive` decorator function contains the directive metadata in a configuration object
as an argument.
`@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`.
<div class="l-sub-section"> <div class="l-sub-section">
### Why not call it "highlight"? _Directives_ must be declared in [Angular Modules](guide/ngmodule) in the same manner as _components_.
Though *highlight* is a more concise name than *myHighlight* and would work, </div >
a best practice is to prefix selector names to ensure
The generated `src/app/highlight.directive.ts` is as follows:
<code-example path="attribute-directives/src/app/highlight.directive.0.ts" title="src/app/highlight.directive.ts"></code-example>
The imported `Directive` symbol provides the Angular the `@Directive` decorator.
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]`.
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.
<div class="l-sub-section">
#### Why not "highlight"?
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. they don't conflict with standard HTML attributes.
This also reduces the risk of colliding with third-party directive names. 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 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. 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.
</div> </div>
After the `@Directive` metadata comes the directive's controller class, After the `@Directive` metadata comes the directive's controller class,
called `HighlightDirective`, which contains the logic for the directive. called `HighlightDirective`, which contains the (currently empty) logic for the directive.
Exporting `HighlightDirective` makes it accessible to other components. Exporting `HighlightDirective` makes the directive accessible.
Angular creates a new instance of the directive's controller class for Now edit the generated `src/app/highlight.directive.ts` to look as follows:
each matching element, injecting an Angular `ElementRef`
into the constructor. <code-example path="attribute-directives/src/app/highlight.directive.1.ts" title="src/app/highlight.directive.ts"></code-example>
`ElementRef` is a service that grants direct access to the DOM element
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`.
`ElementRef` grants direct access to the host DOM element
through its `nativeElement` property. through its `nativeElement` property.
This first implementation sets the background color of the host element to yellow.
{@a apply-directive} {@a apply-directive}
## Apply the attribute directive ## Apply the attribute directive
To use the new `HighlightDirective`, create a template that To use the new `HighlightDirective`, add a paragraph (`<p>`) element to the template of the root `AppComponent` and apply the directive as an attribute.
applies the directive as an attribute to a paragraph (`<p>`) element.
In Angular terms, the `<p>` element is the attribute **host**.
Put the template in its own <code>app.component.html</code> <code-example path="attribute-directives/src/app/app.component.1.html" title="src/app/app.component.html" region="applied"></code-example>
file that looks like this:
<code-example path="attribute-directives/src/app/app.component.1.html" title="src/app/app.component.html"></code-example> Now run the application to see the `HighlightDirective` in action.
Now reference this template in the `AppComponent`:
<code-example path="attribute-directives/src/app/app.component.ts" title="src/app/app.component.ts"></code-example> <code-example language="sh" class="code-shell">
ng serve
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.
<code-example path="attribute-directives/src/app/app.module.ts" title="src/app/app.module.ts"></code-example>
Now when the app runs, the `myHighlight` directive highlights the paragraph text.
<figure>
<img src="generated/images/guide/attribute-directives/first-highlight.png" alt="First Highlight">
</figure>
<div class="l-sub-section">
<h3 class="no-toc">Your directive isn't working?</h3>
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'.
</code-example> </code-example>
Angular detects that you're trying to bind to *something* but it can't find this directive To summarize, Angular found the `appHighlight` attribute on the **host** `<p>` element.
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.
</div>
To summarize, Angular found the `myHighlight` attribute on the `<p>` element.
It created an instance of the `HighlightDirective` class and It created an instance of the `HighlightDirective` class and
injected a reference to the `<p>` element into the directive's constructor injected a reference to the `<p>` element into the directive's constructor
which sets the `<p>` element's background style to yellow. which sets the `<p>` element's background style to yellow.
@ -151,15 +129,14 @@ which sets the `<p>` element's background style to yellow.
## Respond to user-initiated events ## 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. The directive could be more dynamic.
It could detect when the user mouses into or out of the element It could detect when the user mouses into or out of the element
and respond by setting or clearing the highlight color. and respond by setting or clearing the highlight color.
Begin by adding `HostListener` to the list of imported symbols; Begin by adding `HostListener` to the list of imported symbols.
add the `Input` symbol as well because you'll need it soon.
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (imports)" region="imports"></code-example> <code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (imports)" region="imports"></code-example>
Then add two eventhandlers that respond when the mouse enters or leaves, Then add two eventhandlers that respond when the mouse enters or leaves,
each adorned by the `HostListener` decorator. each adorned by the `HostListener` decorator.
@ -180,8 +157,10 @@ There are at least three problems with _that_ approach:
</div> </div>
The handlers delegate to a helper method that sets the color on the DOM element, `el`, The handlers delegate to a helper method that sets the color on the host DOM element, `el`.
which you declare and initialize in the constructor.
The helper method, `highlight`, was extracted from the constructor.
The revised constructor simply declares the injected `el: ElementRef`.
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (constructor)" region="ctor"></code-example> <code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (constructor)" region="ctor"></code-example>
@ -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. 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. 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`.
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" title="src/app/highlight.directive.ts (imports)" region="imports"></code-example>
Add a `highlightColor` property to the directive class like this:
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (highlightColor)" region="color"></code-example> <code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (highlightColor)" region="color"></code-example>
@ -232,16 +214,16 @@ That's good, but it would be nice to _simultaneously_ apply the directive and se
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color"></code-example> <code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color"></code-example>
The `[myHighlight]` attribute binding both applies the highlighting directive to the `<p>` element The `[appHighlight]` attribute binding both applies the highlighting directive to the `<p>` element
and sets the directive's highlight color with a property binding. 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. 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.
<code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (renamed to match directive selector)" region="color-2"></code-example> <code-example path="attribute-directives/src/app/highlight.directive.2.ts" linenums="false" title="src/app/highlight.directive.ts (renamed to match directive selector)" region="color-2"></code-example>
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} {@a input-alias}
@ -254,14 +236,14 @@ Restore the original property name and specify the selector as the alias in the
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (color property with alias)" region="color"></code-example> <code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (color property with alias)" region="color"></code-example>
_Inside_ the directive the property is known as `highlightColor`. _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: You get the best of both worlds: the property name you want and the binding syntax you want:
<code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color"></code-example> <code-example path="attribute-directives/src/app/app.component.html" linenums="false" title="src/app/app.component.html (color)" region="color"></code-example>
Now that you're binding to `highlightColor`, modify the `onMouseEnter()` method to use it. 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 `highlightColor`, highlight in red: If someone neglects to bind to `appHighlightColor`, highlight the host element in red:
<code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" title="src/app/highlight.directive.ts (mouse enter)" region="mouse-enter"></code-example> <code-example path="attribute-directives/src/app/highlight.directive.3.ts" linenums="false" title="src/app/highlight.directive.ts (mouse enter)" region="mouse-enter"></code-example>
@ -308,7 +290,7 @@ then with the `defaultColor`, and falls back to "red" if both properties are und
<code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (mouse-enter)" region="mouse-enter"></code-example> <code-example path="attribute-directives/src/app/highlight.directive.ts" linenums="false" title="src/app/highlight.directive.ts (mouse-enter)" region="mouse-enter"></code-example>
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. 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` 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 template and its component trust each other.
The `color` property doesn't require the `@Input` decorator. 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. not a property of the template's component. There are trust issues.
Therefore, the directive property must carry the `@Input` decorator. Therefore, the directive property must carry the `@Input` decorator.

View File

@ -25,7 +25,7 @@ They shape or reshape the DOM's _structure_, typically by adding, removing, or m
elements. elements.
As with other directives, you apply a structural directive to a _host element_. 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. Structural directives are easy to recognize.
An asterisk (*) precedes the directive attribute name as in this example. An asterisk (*) precedes the directive attribute name as in this example.