|
|
|
@ -10,7 +10,7 @@ include ../../../../_includes/_util-fns
|
|
|
|
|
* respond to user-initiated events
|
|
|
|
|
* pass values into the directive using data binding
|
|
|
|
|
|
|
|
|
|
[Live Example](/resources/live-examples/attribute-directives/ts/src/plnkr.html)
|
|
|
|
|
[Live Example](/resources/live-examples/attribute-directives/ts/plnkr.html)
|
|
|
|
|
|
|
|
|
|
## Directives overview
|
|
|
|
|
|
|
|
|
@ -59,7 +59,7 @@ include ../../../../_includes/_util-fns
|
|
|
|
|
|
|
|
|
|
As in the [tutorial](/docs/ts/latest/tutorial/), we'll rename `app.ts` to `app.component.ts`
|
|
|
|
|
and relocate the call to `bootstrap` to a separate `boot.ts` file.
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/boot.ts', null, 'app/boot.ts')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/boot.ts', null, 'app/boot.ts')
|
|
|
|
|
:marked
|
|
|
|
|
A clean `app.component.ts` without bootstrapping is much easer to test.
|
|
|
|
|
|
|
|
|
@ -70,7 +70,7 @@ code-example.
|
|
|
|
|
:marked
|
|
|
|
|
### Write the directive
|
|
|
|
|
Add a new file to the `app` folder called `highlight.directive.ts` and add the following code:
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/highlight.directive.1.ts', null, 'app/highlight.directive.ts')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/highlight.directive.1.ts', null, 'app/highlight.directive.ts')
|
|
|
|
|
|
|
|
|
|
:marked
|
|
|
|
|
We begin by importing some symbols from the Angular library.
|
|
|
|
@ -86,17 +86,17 @@ code-example.
|
|
|
|
|
The [css selector for an attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors)
|
|
|
|
|
is the attribute name in square brackets.
|
|
|
|
|
|
|
|
|
|
Our directive's selector is `[my-highlight]`.
|
|
|
|
|
Angular will locate all elements in the template that have an attribute named `my-highlight`.
|
|
|
|
|
Our directive's selector is `[myHighlight]`.
|
|
|
|
|
Angular will locate all elements in the template that have an attribute named `myHighlight`.
|
|
|
|
|
.l-sub-section
|
|
|
|
|
:marked
|
|
|
|
|
### Why not call it "highlight"?
|
|
|
|
|
*highlight* is a nicer name than *my-highlight* and, technically, it would work if we called it that.
|
|
|
|
|
*highlight* is a nicer name than *myHighlight* and, technically, it would work if we called it that.
|
|
|
|
|
However, the good folks at Angular strongly prefer hyphenated directive selector names.
|
|
|
|
|
The HTML standards body will never name one of its attributes with a hyphen and there is
|
|
|
|
|
less risk of colliding with a third-party directive name when we give ours a prefix.
|
|
|
|
|
The `ng-` prefix belongs to Angular.
|
|
|
|
|
We need a prefix of our own, preferably short, and `my-` will do for now.
|
|
|
|
|
The `ng` prefix belongs to Angular.
|
|
|
|
|
We need a prefix of our own, preferably short, and `my` will do for now.
|
|
|
|
|
:marked
|
|
|
|
|
After the `@Component` metadata comes the directive's controller class which we are exporting
|
|
|
|
|
to make it accessible to other components.
|
|
|
|
@ -140,17 +140,17 @@ code-example.
|
|
|
|
|
In Angular terms, the `<span>` element will be the attribute **host**.
|
|
|
|
|
|
|
|
|
|
We'll put the template in its own `app.component.html` file that looks like this:
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/app.component.1.html',null,'app/app.component.html')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/app.component.1.html',null,'app/app.component.html')
|
|
|
|
|
:marked
|
|
|
|
|
A separate template file is clearly overkill for a 2-line template.
|
|
|
|
|
Hang in there; we're going to expand it later.
|
|
|
|
|
Meanwhile, we'll revise the `AppComponent` to reference this template.
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/app.component.ts',null,'app/app.component.ts')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/app.component.ts',null,'app/app.component.ts')
|
|
|
|
|
:marked
|
|
|
|
|
We've added an `import` statement to fetch the 'Highlight' directive and
|
|
|
|
|
added that class to a `directives` array in the component metadata so that Angular
|
|
|
|
|
will recognize our directive when it encounters `my-highlight` in the template.
|
|
|
|
|
Angular would simply ignore the `my-highlight` attribute without it.
|
|
|
|
|
will recognize our directive when it encounters `myHighlight` in the template.
|
|
|
|
|
Angular would simply ignore the `myHighlight` attribute without it.
|
|
|
|
|
|
|
|
|
|
We run the app and see that our directive highlights the span text.
|
|
|
|
|
|
|
|
|
@ -159,7 +159,7 @@ figure.image-display
|
|
|
|
|
:marked
|
|
|
|
|
Let's recap what happened.
|
|
|
|
|
|
|
|
|
|
Angular found the `my-highlight` attribute on the `<span>` element. It created
|
|
|
|
|
Angular found the `myHighlight` attribute on the `<span>` element. It created
|
|
|
|
|
an instance of the `Highlight` directive class,
|
|
|
|
|
injecting both a reference to the element and the `Renderer` service into the constructor.
|
|
|
|
|
The constructor told the `Renderer` to set the `<span>` element's background style to yellow.
|
|
|
|
@ -179,7 +179,7 @@ figure.image-display
|
|
|
|
|
Start with event detection.
|
|
|
|
|
We add a `host` property to the directive metadata and give it a configuration object
|
|
|
|
|
that specifies two mouse events and the directive methods to call when they are raised.
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/highlight.directive.2.ts','host')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','host')
|
|
|
|
|
:marked
|
|
|
|
|
.l-sub-section
|
|
|
|
|
:marked
|
|
|
|
@ -196,7 +196,7 @@ figure.image-display
|
|
|
|
|
Let's roll with the `host` property.
|
|
|
|
|
:marked
|
|
|
|
|
Now we implement those two mouse event handlers:
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/highlight.directive.2.ts','mouse-methods')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','mouse-methods')
|
|
|
|
|
:marked
|
|
|
|
|
Notice that they delegate to a helper method that calls the `Renderer` service
|
|
|
|
|
as we used to do in the constructor.
|
|
|
|
@ -205,10 +205,10 @@ figure.image-display
|
|
|
|
|
we still want the injected `ElementRef` and `Renderer` service.
|
|
|
|
|
We revise the constructor signature to capture the injectables in private variables
|
|
|
|
|
and clear the body.
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/highlight.directive.2.ts','ctor')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts','ctor')
|
|
|
|
|
:marked
|
|
|
|
|
Here's the updated directive:
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/highlight.directive.2.ts',null, 'app/highlight.directive.ts')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/highlight.directive.2.ts',null, 'app/highlight.directive.ts')
|
|
|
|
|
:marked
|
|
|
|
|
We run the app and confirm that the background color appears as we move the mouse over the `span` and
|
|
|
|
|
disappears as we move out.
|
|
|
|
@ -221,49 +221,50 @@ figure.image-display
|
|
|
|
|
|
|
|
|
|
Currently the highlight color is hard-coded within the directive. That's inflexible.
|
|
|
|
|
We should set the highlight color externally with a binding like this:
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/app.component.html','span')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/app.component.html','span')
|
|
|
|
|
:marked
|
|
|
|
|
We'll extend our directive class with a bindable **input** `highlightColor` property and use it when we highlight text.
|
|
|
|
|
|
|
|
|
|
Here is the final version of the class:
|
|
|
|
|
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/highlight.directive.ts', 'class-1', 'app/highlight.directive.ts (class only)')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/highlight.directive.ts', 'class-1', 'app/highlight.directive.ts (class only)')
|
|
|
|
|
:marked
|
|
|
|
|
The new `highlightColor` property is called an "input" property because data flows from the binding expression into our directive.
|
|
|
|
|
Notice that we call the `@Input()` decorator function while defining the property.
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/highlight.directive.ts', 'color')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/highlight.directive.ts', 'color')
|
|
|
|
|
:marked
|
|
|
|
|
This `@Input` decorator adds metadata to the class that makes the `highlightColor` property available for property binding
|
|
|
|
|
under the `my-highlight` alias.
|
|
|
|
|
under the `myHighlight` alias.
|
|
|
|
|
We must add this input metadata. Angular will give us an error if we try to bind
|
|
|
|
|
to a property without declaring it as an input.
|
|
|
|
|
.l-sub-section
|
|
|
|
|
:marked
|
|
|
|
|
The developer who uses our directive expects to bind to the attribute name, `my-highlight`.
|
|
|
|
|
The developer who uses our directive expects to bind to the attribute name, `myHighlight`.
|
|
|
|
|
The directive property name is `highlightColor`. That's a disconnect.
|
|
|
|
|
|
|
|
|
|
We can resolve the discrepancy by renaming the property to `myHighlight` and define it as follows:
|
|
|
|
|
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/highlight.directive.ts', 'highlight')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/highlight.directive.ts', 'highlight')
|
|
|
|
|
<br>
|
|
|
|
|
:marked
|
|
|
|
|
We don't like that property name.
|
|
|
|
|
We prefer, in this case, to **alias** the `highlightColor` property with the attribute name by
|
|
|
|
|
passing `my-highlight` into the `@Input` decorator:
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/highlight.directive.ts', 'color')
|
|
|
|
|
Maybe we don't want that property name inside the directive perhaps because it
|
|
|
|
|
doesn't express our intention well.
|
|
|
|
|
We can **alias** the `highlightColor` property with the attribute name by
|
|
|
|
|
passing `myHighlight` into the `@Input` decorator:
|
|
|
|
|
+makeExample('attribute-directives/ts/app/highlight.directive.ts', 'color')
|
|
|
|
|
:marked
|
|
|
|
|
Now that we're getting the highlight color as an input, we modify the `onMouseEnter()` method to use
|
|
|
|
|
it instead of the hard-coded color name.
|
|
|
|
|
We also define a red default color as a fallback in case
|
|
|
|
|
the user neglects to bind with a color.
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/highlight.directive.ts', 'mouse-enter')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/highlight.directive.ts', 'mouse-enter')
|
|
|
|
|
:marked
|
|
|
|
|
Now we'll update our `AppComponent` template to let
|
|
|
|
|
users pick the highlight color and bind their choice to our directive.
|
|
|
|
|
|
|
|
|
|
Here is the updated template:
|
|
|
|
|
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/app.component.html', 'v2')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/app.component.html', 'v2')
|
|
|
|
|
|
|
|
|
|
.l-sub-section
|
|
|
|
|
:marked
|
|
|
|
@ -294,12 +295,12 @@ figure.image-display
|
|
|
|
|
|
|
|
|
|
Let's let the template developer set the default color, the color that prevails until the user picks a highlight color.
|
|
|
|
|
We'll add a second **input** property to `HighlightDirective` called `defaultColor`:
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/highlight.directive.ts', 'defaultColor')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/highlight.directive.ts', 'defaultColor')
|
|
|
|
|
:marked
|
|
|
|
|
The `defaultColor` property has a setter that overrides the hard-coded default color, "red".
|
|
|
|
|
We don't need a getter.
|
|
|
|
|
|
|
|
|
|
How do we bind to it? We already "burned" the `my-highlight` attribute name as a binding target.
|
|
|
|
|
How do we bind to it? We already "burned" the `myHighlight` attribute name as a binding target.
|
|
|
|
|
|
|
|
|
|
Remember that a *component is a directive too*.
|
|
|
|
|
We can add as many component property bindings as we need by stringing them along in the template
|
|
|
|
@ -308,11 +309,10 @@ figure.image-display
|
|
|
|
|
<my-component [a]="'a'" [b]="'b'" [c]="'c'"><my-component>
|
|
|
|
|
```
|
|
|
|
|
We do the same thing with an attribute directive.
|
|
|
|
|
+makeExample('attribute-directives/ts/src/app/app.component.html', 'defaultColor')
|
|
|
|
|
+makeExample('attribute-directives/ts/app/app.component.html', 'defaultColor')
|
|
|
|
|
:marked
|
|
|
|
|
Here we're binding the user's color choice to the `my-highlight` attribute as we did before.
|
|
|
|
|
We're *also* binding the literal string, 'violet', to the `defaultColor`
|
|
|
|
|
(remembering to spell that property in "kebab-case" as *default-color*).
|
|
|
|
|
Here we're binding the user's color choice to the `myHighlight` attribute as we did before.
|
|
|
|
|
We're *also* binding the literal string, 'violet', to the `defaultColor`.
|
|
|
|
|
|
|
|
|
|
Here is the final version of the directive in action.
|
|
|
|
|
figure.image-display
|
|
|
|
@ -330,11 +330,11 @@ figure.image-display
|
|
|
|
|
The final source:
|
|
|
|
|
|
|
|
|
|
+makeTabs(
|
|
|
|
|
`attribute-directives/ts/src/app/app.component.ts,
|
|
|
|
|
attribute-directives/ts/src/app/app.component.html,
|
|
|
|
|
attribute-directives/ts/src/app/highlight.directive.ts,
|
|
|
|
|
attribute-directives/ts/src/app/boot.ts,
|
|
|
|
|
attribute-directives/ts/src/index.html
|
|
|
|
|
`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/boot.ts,
|
|
|
|
|
attribute-directives/ts/index.html
|
|
|
|
|
`,
|
|
|
|
|
',,full',
|
|
|
|
|
`app.component.ts,
|
|
|
|
|