Custom Attribute Directive Example in Angular
Objective
To understand the basics of Custom Attribute Directives in Angular with the help of a small example and to do some experiments in it.
Pre-requisites
- An initial angular project structure on StackBlitz
- Basics of Parent-Child Component Interaction in Angular
Pertaining Repository
The code, with all the commits, is available in this repository.
While the commits are arranged according to the section in master branch, you will see the commits pertaining to experiments in other branches.
You can also view the project in this Stackblitz Project.
Final Outcome
The final outcome of this experiment looks like this.
Just take the mouse pointer over the div.
Let’s Start …
Section #1
What is a Directive in Angular?
In basic terms, a directives are classes that add additional behaviour to elements in the Angular Application.
There are 3 types of Directives in Angular :
- Component Directives
- Attribute Directives
- Structural Directives
** Definition reference taken from Angular Docs
Step #1.1
In the initial Angular project structure on StackBlitz, go to app.component.html
and remove the content of the file to make the sandbox blank.
Now, we want to add a div which spans across the full width and height of the sandbox display. Creating a div in app.component.html
and giving style to that div as width: 100%; height: 100%
won’t do the trick, as height will still be 0.
We can make this use-case work with the help of an attribute directive. If you have created the project structure in your local machine using Angular CLI, you can use the following command to make Angular CLI generate the file automatically for you:
ng generate directive directives/full-screen-span/full-screen-span
This will create two files as follows :
src/app/directives/full-screen-span/full-screen-span.directive.ts
src/app/directives/full-screen-span/full-screen-span.directive.spec.ts
But in the StackBlitz, there is no provision to execute the Angular CLI command, so you will have to create those files at that location by yourself (essentially only full-screen-span.directive.ts
is needed). Also, import that directive in app.module.ts
.
These two files look like this :
Step #1.2
Now, let’s add some code. The code in the following two files corresponds to how a div will span across the width and height of available screen.
Let’s understand what this piece of code is doing. In full-screen-span.directive.ts
, ElementRef
variable in the constructor gives the reference of the host element on which this directive is gonna be applied, and in the ngOnInit()
method, we say that this should be the width and height of the host element when it is rendered initially.
But after it is rendered, the user may change the size of the screen intentionally, and in that case, we would like the div to scale its size accordingly. For that we are using @HostListener
decorator to decorate the event handler, to make the attribute directive, and eventually the host element, respond to window-resize event. It works in the same way as addEventListener()
in vanilla JS.
So what is happening is that whenever the window-resize event is fired, onWindowResize()
event handler is executed and it sets the height of the host element accordingly, not before it goes through the debouncing process.
After this, apply this directive onto the div in app.component.html
as shown in the code above.
After all this process what we’ll get is a div having grey background spanning across the entire width and height of the screen.
Step #1.3
Now, its time to add a div at the middle of the whole-screen-spanning div and do some cool stuff with it.
But first create an empty directive, as we did earlier, at the following location :
src/app/directives/div-transform/div-transform.directive.ts
And import this directive in app.module.ts
.
Now, lets look at the code which serves our requirement :
The code pattern in div-transform.directive.ts
is similar as in full-screen-span.directive.ts
, in that we are assigning some values to the ElementRef
native property on start up, and adding some event handlers and giving those handlers its corresponding functionality.
How the new div is being placed right at the middle of whole-screen-spanning div? It is all due to Flex Display in CSS. It is what we have used in app.component.css
in .outerDiv
class. It is very cool thing to keep in your pocket to make your life easy. You can know all about it at this detailed blog.
Now, the feature that is allowing us to do the animation part is called CSS Keyframes. They are declared in app.component.css
with @keyframes
. You can know about the CSS keyframes in this detailed blog. Notice that we are using that keyframe identifier in onMouseOver()
event listener in div-transform.directive.ts
.
The output will come out to be this. Just hover over the mouse cursor over the div and animation will start and hovering out will end it.
So, this is how we can utilise Angular’s Attribute Directive in our projects.
In the next section, we will see the various ways with which we can pass values to the attribute directives.
Section #2
Let’s look at various ways with which we can pass value to the attribute directive from the host element.
What we will do is pass the CSS Keyframe Identifier and animation’s duration from the host element.
Method #2.1 : One input variable same as directive selector
Observe the following code :
Look at line number 3, 6 and 17 of div-transform.directive.ts
. We have introduced two input variables with their respective aliases and used those variables to set the animation property in line #17.
Note that the alias of animationType
input variable is same as the selector of the directive.
Notice how we have used these input variables in app.component.html
at line #2. The syntax is same as how parent-child interaction happens in Angular Components. But the difference is that we are not mentioning the directive explicitly in the template as what we saw in Step #1.3, instead we are just using the input variables, with one input having same alias as directive selector, and the angular determines behind the scene, which directive we are talking about.
There can be one variation under this category while using the input variables in app.component.html
. Look at line #4. We can omit the square brackets around the duration input variable, with some changes in the right hand side, and it will still work.
Method #2.2 : No input variable same as directive selector
This case is very simple, like the Parent-Component Interaction in Angular.
Observe the changes in the file app.component.html
and div-transform.directive.ts
.
We have added the alias to animationType
property in div-transform.directive.ts
, and used that input variable in app.component.html
. Observe, there is no square bracket around directive’s selector in app.component.html
.
Section #3
In this section, we will conduct some experiments upon the work which we have done so far and note down the behaviour of Angular
Experiment #3.1 : What if there is a third input variable in Method #2.1
We’ll try this experiment with four cases in the app.component.html
.
a) [Second I.V.] && [Third I.V] // With [] on both Input Variables
b) [Second I.V.] && Third I.V. // With [] on one Input Variable
c) Second I.V. && [Third I.V.] // With [] on one Input Variable
d) Second I.V. && Third I.V. // With no [] on any Variable
Just added a new Input Variable named as animationIterationCount
and used that in onMouseOver()
handler in div-transform.directive.ts
. In app.component.html, we are utilising this new variable in four different ways.
Observation: All cases works in the expected way. There is no difference in behaviour of the directive. This implies that we can use our Input Variables, other than the one having alias same as directive selector, with or without the square brackets while using the Method #2.1.
Experiment #3.2 : What if two directives have same input variable in Method #2.2
For this case, let’s create a new attribute directive, in the same way, and at its corresponding location, as we created earlier and import that in app.module.ts
.
Now observe the following code :
In div-border.directive.ts
, we have made an event listener which sets the border of the host element for a particular time and after completion of that time, it will be removed.
In app.component.html
, place the directive at its appropriate position. Note that input variable duration
is there both the directives applied to the div.
Observation: Both directive’s functionalities is working as expected. duration
input variable value is transmitted to both the directives.
Section #4
In this section, we will see how @HostBinding works.
@HostBinding is the decorator using which you can directly set the host element properties, without having to explicitly get the properties reference using native element.
Lets see an example :
Pay attention to line no. 14 and17.
We have bound the variable animation
and animationDirection
with the style.animation
and style.animationDirection
property of host element respectively, and have used those variables in onMouseOver()
and onMouseOut()
event handlers rather than taking their reference through nativeElement
property of ElementRef
.
Conclusion
In this article, we understood the very basics of Angular directives and performed some experiments and noted the observation.