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":[
"!**/*.d.ts",
"!**/*.js",
"!app/*.[1,2,3].*"
"!app/*.[0,1,2,3].*"
],
"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 */
// #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';

View File

@ -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
}

View File

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

View File

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

View File

@ -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 <code>attribute-directives</code>.
Create the directive class file in a terminal window with this CLI command.
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 `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`.
The CLI creates `src/app/highlight.directive.ts`, a corresponding test file (`.../spec.ts`, and _declares_ the directive class in the root `AppModule`.
<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,
a best practice is to prefix selector names to ensure
</div >
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.
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.
</div>
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.
called `HighlightDirective`, which contains the (currently empty) logic for the directive.
Exporting `HighlightDirective` makes the directive accessible.
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
Now edit the generated `src/app/highlight.directive.ts` to look as follows:
<code-example path="attribute-directives/src/app/highlight.directive.1.ts" title="src/app/highlight.directive.ts"></code-example>
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.
This first implementation sets the background color of the host element to yellow.
{@a apply-directive}
## Apply the attribute directive
To use the new `HighlightDirective`, create a template that
applies the directive as an attribute to a paragraph (`<p>`) element.
In Angular terms, the `<p>` element is the attribute **host**.
To use the new `HighlightDirective`, add a paragraph (`<p>`) element to the template of the root `AppComponent` and apply the directive as an attribute.
Put the template in its own <code>app.component.html</code>
file that looks like this:
<code-example path="attribute-directives/src/app/app.component.1.html" title="src/app/app.component.html" region="applied"></code-example>
<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>
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 language="sh" class="code-shell">
ng serve
</code-example>
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.
</div>
To summarize, Angular found the `myHighlight` attribute on the `<p>` element.
To summarize, Angular found the `appHighlight` attribute on the **host** `<p>` element.
It created an instance of the `HighlightDirective` class and
injected a reference to the `<p>` element into the directive's constructor
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
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.
<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,
each adorned by the `HostListener` decorator.
@ -180,8 +157,10 @@ There are at least three problems with _that_ approach:
</div>
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`.
<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.
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>
@ -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>
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.
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.
<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}
@ -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>
_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:
<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.
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:
<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>
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.

View File

@ -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.