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