block includes
include ../_util-fns
: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)
* [Respond to user-initiated events](#respond-to-user)
* [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 code #[+adjExPath('app.component.html')] | file that looks like this: +makeExample('attribute-directives/ts/app/app.component.1.html',null,'app/app.component.html')(format=".") :marked Now reference this template in the `AppComponent`: +makeExample('attribute-directives/ts/app/app.component.ts',null,'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/app/app.module.ts',null,'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! 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 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 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 :marked ## Respond to user-initiated events Currently, `myHighlight` 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 `Import` symbol as well because you'll need it soon. +makeExample('attribute-directives/ts/app/highlight.directive.ts','imports')(format=".") :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=".") :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 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 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 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 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 :marked ## Pass values into the directive with an _@Input_ data binding 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') a#input :marked ### Binding to an _@Input_ property 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. 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 Add a `color` property to the `AppComponent`. +makeExample('attribute-directives/ts/app/app.component.1.ts','class', 'app/app.component.ts (class)')(format='.') :marked 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', '')
: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 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
: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". 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
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
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
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.
- [**Bind** values to the directive](#bindings).
The final source code follows:
+makeTabs(
`attribute-directives/ts/app/app.component.ts,
attribute-directives/ts/app/app.component.html,
attribute-directives/ts/app/highlight.directive.ts,
attribute-directives/ts/app/app.module.ts,
attribute-directives/ts/app/main.ts,
attribute-directives/ts/index.html
`,
'',
`app.component.ts,
app.component.html,
highlight.directive.ts,
app.module.ts,
main.ts,
index.html
`)
:marked
You can also experience and download the