Attribute Directives
Attribute directives change appearance or behavior of a host DOM element. Usually, they look like regular HTML element attributes.
Let's create a simple clickable directive and call it Clickme
.
ng g directive directives/clickme
Angular CLI generates directive with unit test and updates main application module:
installing directive
create src/app/directives/clickme.directive.spec.ts
create src/app/directives/clickme.directive.ts
update src/app/app.module.ts
Initial implementation should look like the following one:
// src/app/directives/clickme.directive.ts
import { Directive } from '@angular/core';
@Directive({
selector: '[appClickme]'
})
export class ClickmeDirective {
constructor() { }
}
Directives can interact with the host by listening to events and setting properties of the target element.
Handling host events
Your newly created directive can listen to events with the help of the HostListener
decorator.
Angular invokes decorated method when the host element emits a corresponding event.
The format of the HostListener
decorator and metadata is as following:
interface HostListener {
eventName : string
args : string[]
}
You can use it to decorate class methods like shown below:
// src/app/directives/clickme.directive.ts
@HostListener('domEvent', ['$event'])
onEvent(event) {
// ...
}
Angular should be listening for the 'domEvent' event (if supported by host element)
and invoke onEvent
method of the directive controller.
The event handler is also going to get a reference to the original DOM event,
as we instruct Angular to map specific $event
argument to the event
parameter.
That means directive can inspect properties of the original event
and even prevent default behavior or event bubbling if necessary.
The easiest way to demonstrate HostListener
in action is by wiring a standard click
event.
// src/app/directives/clickme.directive.ts
import { /*...,*/ HostListener } from '@angular/core';
...
export class ClickmeDirective {
// ...
@HostListener('click', ['$event'])
onClicked(e: Event) {
console.log(e);
alert('Clicked');
}
}
Next put a div
element to the app.component.html
and decorate it with your appHighlight
directive:
<!-- src/app/app.component.html -->
<h1>
{{title}}
</h1>
<div class="click-area" appClickme>
Click me
</div>
You also need to have some space to click on; this is what we are going to use highlight-area
CSS class for:
/* src/app/app.component.css */
.click-area {
width: 100px;
height: 100px;
background-color: beige;
}
Below is how the main page looks like after your changes:
You get the default 'app works!' label generated by the Angular CLI, and coloured div
element.
Now if you click anywhere on the beige area the browser should display an alert:
So as you can see in the example above, with the help of HostListener
, you can listen to any event emitted by host element.
We have added appClickme
directive as an attribute of the div
,
and every click on the div
automatically triggers onClicked
method in the ClickmeDirective
.
You can use any target for the directive, for example:
<!-- src/app/app.component.html -->
<button appClickme>Click me</button>
<span appClickme>Click me</span>
<input type="text" appClickme>
Host members binding
Angular also provides a way for a directive to change host properties by using a HostBinding
decorator.
During change detection cycle Angular checks all property bindings and updates host element of the directive if bound value changes.
The format of the HostBinding decorator and metadata is as following:
interface HostBinding {
hostPropertyName : string
}
You can use this decorator to change:
-
attributes (applies to all elements)
@HostBinding('attr.text)
-
properties (corresponding properties must exist)
@HostBinding('title')
-
style values (applies to all elements)
@HostBinding('style.background-color')
-
class names (applies to all elements)
@HostBinding('class.some-class-name')
Binding element attributes
If you want your directive to change element attributes, you can use this decorator with class methods like below:
// src/app/directives/clickme.directive.ts
export class ClickmeDirective {
@HostBinding('attr.propertyName')
myProperty: string = 'hello world';
}
For example, if you apply directive to a div
element, the property binding should cause the following attributes rendering at run time:
<!-- src/app/app.component.html -->
<div appclickme="" propertyName="hello world"></div>
Please note that if host property name parameter is not defined, then a class property name should be taken as a fallback value.
@HostBinding()
title: string = 'element title';
This time, if you apply the directive to the input
element, for instance, you should see the title
property name as an attribute of the host:
<input appclickme="" type="text" title="element title">
Binding element properties
Keep in mind that in this case, the property should exist for a given element. Angular should throw an error if you try to bind a missing property.
Let's try to bind a value
property to demonstrate this behavior.
// src/app/directives/clickme.directive.ts
// ...
export class ClickmeDirective {
@HostBinding()
value: string = 'input value';
// ...
}
You may still have a click area example on the main page, or you can do it once again:
<div class="click-area" appClickme>
Angular should produce an error when page compiles and reloads:
Error: Uncaught (in promise): Error: Template parse errors:
Can't bind to 'value' since it isn't a known property of 'div'.
[ERROR ->]<div class="click-area" appClickme>
Click me
</div>
However, if you replace div
with an input
element that natively supports value
property, you should get it rendered properly:
You can, however, fix the issue and provide compatibility with all HTML elements by utilizing attr.value
instead of value
for the property binding:
// src/app/directives/clickme.directive.ts
// ...
export class ClickmeDirective {
@HostBinding('attr.value')
value: string = 'input value';
// ...
}
In this case you are going to get the following HTML when both <input>
and <div>
are present on the page:
<!-- src/app/app.component.html -->
<div appclickme="" class="click-area" value="input value">
Click me
</div>
<input appclickme="" type="text" value="input value">
Your main application component page should now render without errors.
Binding style attributes
You bind single style attribute values using @HostBinding('style.<attribute>)
format,
where <attribute>
is a valid name of the CSS style attribute.
// src/app/directives/clickme.directive.ts
// ...
export class ClickmeDirective {
// ...
@HostBinding('style.background-color')
background: string = 'lightblue';
}
Now the directive is painting its host element's background into light-blue.
This is how the rendered HTML looks like:
<!-- src/app/app.component.html -->
<div appclickme="" class="click-area" value="input value"
style="background-color: rgb(173, 216, 230);">
Click me
</div>
Binding Class Names
Instead of binding single style attributes, you may want to operate CSS class names,
to be able providing external themes, or separating presentation layer from directive implementation.
It can be achieved by utilizing @HostBinding('class.<class-name>')
where <class-name>
is the name of the CSS class.
Note that having corresponding CSS class implementation is optional if you directive is not enforcing styles directly. Developers can choose whether to implement or override the class, or leave defaults.
You can bind class names to the boolean
values or expressions.
Angular appends provided CSS class name to the host element if the resulting value is true
, and automatically removes it if value changes back to false
.
// src/app/directives/clickme.directive.ts
// ...
export class ClickmeDirective {
// ...
@HostBinding('class.is-selected')
isSelected: boolean = true;
}
This is the initial template we have been using:
<!-- src/app/app.component.html -->
<div class="click-area" appClickme>
Click me
</div>
So this is how Angular renders component at run time.
Note the class
value now has both click-area
we defined manually, and is-selected
class provided by the directive controller.
<!-- src/app/app.component.html -->
<div appclickme="" class="click-area is-selected" value="input value"
style="background-color: rgb(173, 216, 230);">
Click me
</div>
Typically you are going to apply or change CSS classes of the host element as a response to the host events wired by HostBinding
decorators.
For example, the directive can listen to mouse events and toggle hovered
styles:
// src/app/directives/clickme.directive.ts
// ...
export class ClickmeDirective {
// ...
@HostBinding('class.is-hovered')
isHovered: boolean = false;
@HostListener('mouseenter')
onMouseEnter() {
this.isHovered = true;
// other code if needed
}
@HostListener('mouseleave')
onMouseLeave() {
this.isHovered = false;
// other code if needed
}
}
Our directive toggles the isHovered
property value upon mouse enter and leave,
but it does not directly change the way its host element looks.
Instead, you or developers that use your directive can optionally add a custom is-hovered
CSS class
to alter how the element looks and behaves on mouse interaction.
The example below adds a thin dashed border to the element when a user hovers it:
/* src/app/app.component.css */
.is-hovered {
border: 1px dashed darkblue;
}
You can now run the application and move the mouse cursor in and out of the click area.
Of course, you can control hover styles in pure CSS.
The code above is more a simple demonstration of capabilities to give you more ideas
on what is possible with HostListener
and HostBinding
combined.
Source Code
You can find the source code in the angular/directives/directive-example folder.
Built-in Attribute Directives
Angular comes with the following ready-to-use attribute directives:
- NgStyle, updates an HTML element styles
- NgClass, adds and removes CSS classes on an HTML element
- NgModel, provides two-way binding to Form elements, see Forms chapter for more details
- NgNonBindable, prevents content from being evaluated and compiled in templates
NgStyle
The NgStyle
directive is used to modify CSS style attributes of the host element or component.
<element [ngStyle]="OPTIONS">
...
</element>
Where OPTIONS
is an object literal NgStyle
that binds and maps properties to style attributes.
Object keys should represent style names with an optional .<unit>
suffix, for example, width.px
, font-style.em
.
<button [ngStyle]="{ 'border-width': '1px' }">button 1</button>
<button [ngStyle]="{ 'border-width.px': '1' }">button 2</button>
<button [ngStyle]="{
'background-color': 'white',
'border': '1px blue solid'
}">button 3</button>
You should see three buttons with custom styles once you run the application:
It is also possible to bind ngStyle
to the component property.
<input type="text" value="hello world" [ngStyle]="inputStyle">
In this case, you declare object literal within the component class implementation, for example:
// src/app/app.component.ts
// ...
export class AppComponent {
// ...
inputStyle = {
'border': '1px green solid',
'background-color': 'white',
'color': 'blue'
};
}
That allows you to compose styles based on some other conditions dynamically.
Source Code
You can find the source code in the angular/directives/attribute-directives folder.
NgClass
The NgClass
directive allows binding CSS class names on an HTML element.
<element [ngClass]="OPTIONS">
<!-- ... -->
</element>
Where the OPTIONS
value can take one of the following formats:
- string expression (single or
space
delimited) - object literal
- array
Binding to String Expression (single)
With this format you specify a string expression that corresponds to a CSS class name:
<element [ngClass]="'class1'">
<!-- ... -->
</element>
Binding to String Expression (space delimited)
You can also provide multiple class names per single space
delimited string expression:
<element [ngClass]="'class1 class2 class3'">
<!-- ... -->
</element>
Binding to Object Literal
This format is very similar to that of NgStyle
one.
All object keys are CSS class names and get added to host element only if value evaluates to a truthy value.
In a case of a non-truthy value, Angular removes class names from the host.
<element [ngClass]="{
'class1': true,
'class2': false,
'class3': true }">
<!-- ... -->
</element>
Binding to Array
Finally you can bind the NgClass
directive to an array of class names:
<element [ngClass]="['class1', 'class2', 'class3']">
<!-- ... -->
</element>
In all the cases described above you can also bind directive options to underlying controller properties or methods:
<!-- src/app/app.component.html -->
<element [ngClass]="currentClass"><!-- ... --></element>
<element [ngClass]="getClassObj()"><!-- ... --></element>
<element [ngClass]="getClassArr()"><!-- ... --></element>
// src/app/app.component.ts
export class MyComponent {
currentClass: string = 'class1';
getClassObj(): any {
return {
'class2': true,
'class3': true
};
}
getClassArr(): string[] {
return [
'class4',
'class5'
];
}
}
Source Code
You can find the source code in the angular/directives/attribute-directives folder.
NgNonBindable
You use NgNonBindable
directive to switch off Angular evaluating code or binding values for a particular element and its content.
For example if you want displaying source code:
<h2>NgNonBindable</h2>
<pre>
You can bind <strong>title</strong> property like following:
<code ngNonBindable>
<div>{{title}}</div>
</code>
</pre>
Which renders to the following if you run the application:
Please note that the NgNonBindable
is a very specific directive, and typically you are not going to use it often, if at all.
Source Code
You can find the source code in the "angular/directives/attribute-directives" folder.