Attribute directives look like normal HTML attributes (possibly with data binding or event binding). they affect/change the element they added on, means they change the properties of the element. for example, changing the background color.
Let’s create a simple directive that will change the background of an element.
We create a custom directive by adding a directive decorator to the class and we use a selector just like when we create a component, but since we want the directive to be an attribute and not an element, we wrap the value for the selector with square brackets – [].
Now let’s say we want the directive to change the background color of the element where we attach this directive. for this we need to get access to the element the directive sits on and we do it by injecting the element. we do inject by adding a constructor and passing an argument which is a reference to the element the directive sits on.
from here we can write the logic we need, in this case we will change the background color for the element in the OnInit lifecycle hook.
keep in mind that just like with components, we have to inform Angular about this directive in the declaration on the module
import { Directive, ElementRef, OnInit } from "@angular/core";
@Directive({
selector: "[appBasicBg]"
})
export class BasicBg implements OnInit {
constructor(private elementRef: ElementRef) {}
ngOnInit() {
this.elementRef.nativeElement.style.backgroundColor = "blue";
}
}
<p appBasicBg>Basic directive example</p>
The example above however is not the best way of changing that style, because accessing elements directly like this is not good practice (we should not change the element by accessing the nativeElement) because Angular is also able to render the templates without a DOM and these properties might not be available.
instead of changing the element directly like we did in the example above, we can inject an helper that called renderer and use it to change the properties of the element, in our case the background
The renderer give us some helper methods to work with the DOM, in our case we will use the setStyle method and pass it the following parameters: the element we want to change, the property we want to change and the value for the property.
import { Directive, Renderer2, OnInit, ElementRef } from "@angular/core";
@Directive({
selector: "[appImprovedBg]"
})
export class ImprovedBgDirective implements OnInit {
constructor(private elRef: ElementRef, private renderer: Renderer2) {}
ngOnInit() {
this.renderer.setStyle(
this.elRef.nativeElement,
"background-color",
"yellow"
);
}
}
<p appImprovedBg>
Improved directive example
</p>
our current directive is not interactive, the directive create a static background that doesn’t change. let’s make it interactive so only when we hover the element the new background will appear.
We need to react to some events occurring on the element the directive sits on and for that we will add a decorator named @HostListener
import { Directive, Renderer2, ElementRef, HostListener } from "@angular/core";
@Directive({
selector: "[appInteractiveBg]"
})
export class InteractiveBgDirective {
constructor(private elRef: ElementRef, private renderer: Renderer2) {}
@HostListener("mouseenter") mouseover(eventData: Event) {
this.renderer.setStyle(
this.elRef.nativeElement,
"background-color",
"orange"
);
}
@HostListener("mouseleave") mouseleave(eventData: Event) {
this.renderer.setStyle(
this.elRef.nativeElement,
"background-color",
"transparent"
);
}
}
<p appInteractiveBg>interactive directive example</p>
We can use another decorator named @HostBinding instead of using the renderer.
export class ImprovedBgDirective {
@HostBinding("style.backgroundColor") backgroundColor: string = 'tansparent';
constructor(private elRef: ElementRef, private renderer: Renderer2) {}
@HostListener("mouseenter") mouseover(eventData: Event) {
this.backgroundColor = 'green';
}
@HostListener("mouseleave") mouseleave(eventData: Event) {
this.backgroundColor = 'red';
}
}
Now, before we finish with this directive, lets add another feature to it. lets make this directive more dynamic by giving the option to set the colos from outside of the directive. we will give 2 options, one for default background color and another for background color when we hover.
To achieve that all we have to do is use property binding. we will add 2 variables with the @Input Decorator , one for the default and another for the hover effect.
import {
Directive,
Renderer2,
OnInit,
ElementRef,
HostListener,
HostBinding,
Input
} from "@angular/core";
@Directive({
selector: "[appAdvancedBg]"
})
export class AdvancedBgDirective implements OnInit {
@Input() defaultColor: string = "transparent";
@Input("appAdvancedBg") highlightColor: string = "red";
@HostBinding("style.backgroundColor") backgroundColor: string;
constructor(private elRef: ElementRef, private renderer: Renderer2) {}
ngOnInit() {
this.backgroundColor = this.defaultColor;
}
@HostListener("mouseenter") mouseover(eventData: Event) {
this.backgroundColor = this.highlightColor;
}
@HostListener("mouseleave") mouseleave(eventData: Event) {
this.backgroundColor = this.defaultColor;
}
}
<p [appAdvancedBg]="'purple'" defaultColor="orange">
Improved directive example
</p>