AngularJS directives
Quoting from the AngularJS documentation.
"At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS's HTML compiler ($compile) to attach a specified behavior to that DOM element or even transform the DOM element and its children."
This is a very useful feature when you want to abstract out the common functionality on your web page. This is similar to what AngularJS has done with its directives, such as the following ones:
ng-app
: This initializes a new default AngularJS module when no value is passed to it; otherwise, it initializes the named moduleng-model
: This maps the input element's value to the current scopeng-show
: This shows the DOM element when the expression passed tong-show
is trueng-hide
: This hides the DOM element when the expression passed tong-hide
is trueng-repeat
: This iterates the current tag and all its children based on the expression passed tong-repeat
Referring to the Search App, which we built earlier, imagine that there are multiple pages in your web application that need this search form. The expected end result is pretty much the same across all pages.
So, instead of replicating the controller and the HTML template where needed, we would abstract this functionality into a custom directive.
You can initialize a new directive on a DOM element by referring to it using an attribute notation, such as <div my-search></div>
, or you can create your own tag/element, such as <my-search></my-search>
.
This would enable us to write the search functionality only once but use it many times. AngularJS takes care of initializing the directive when it comes into view and destroying the directive when it goes away from the view. Pretty nifty, right?
We will update our Search App by creating a new custom directive called my-search
. The sole functionality of this directive would be to render a textbox and a button. When the user clicks on the Search button, we will fetch the results and display them below the search form.
So, let's get started.
As with any AngularJS component, the directives are also bound to a module. In our case, we already have a searchApp
module. We will bind a new directive to this module:
searchApp.directive('mySearch', [function () { return { template : 'This is Search template', restrict: 'E', link: function (scope, iElement, iAttrs) { } }; }]);
The directive is named mySearch
in camel case. AngularJS will take care of matching this directive with my-search
when used in HTML. We will set a sample text to the template
property. We will restrict the directive to be used as an element (E
).
Note
Other values that you can restrict in an AngularJS directive are A
(attribute), C
(class), and M
(comment). You can also allow the directive to use all four (ACEM) formats.
We have created a link method. This method is invoked whenever the directive comes into view. This method has three arguments injected to it, which are as follows:
scope
: This refers to the scope in which this tag prevails in the DOM. For example, it could be insideAppCtrl
or even directly insiderootScope
(ng-app
).iElement
: This is the DOM node object of the element on which the directive is present.iAttrs
: These are the attributes present on the current element.
In our directive, we would not be using iAttrs
, as we do not have any attributes on our my-search
tag.
In complex directives, it is a best practice to abstract your directive template to another file and then refer it in the directive using the templateUrl
property. We will do the same in our directive too.
You can create a new file named directive.html
in the same folder as index.html
and add the following content:
<form> <label>Search : </label> <input type="text" name="query" ng-model="query" required> <input type="button" ng-disabled="!query" value="Search" ng-click="search()"> </form> <div ng-repeat="res in results"> <h2>{{res.heading}}</h2> <span>{{res.summary}}</span> <a ng-href="{{res.link}}">{{res.linkText}}</a> </div>
In simple terms, we have removed all the markup in the index.html
related to the search and placed it here.
Now, we will register a listener for the click
event on the button inside the directive. The updated directive will look like this:
searchApp.directive('mySearch', [function() { return { templateUrl: './directive.html', restrict: 'E', link: function postLink(scope, iElement, iAttrs) { scope.search = function() { var q = { query : scope.query }; // Interact with the factory (next step) } } }; }])
As you can see from the preceding lines of code, the scope.search
method is executed when the click
event on the button is fired and scope.query
returns the value of the textbox. This is quite similar to what we did in the controller.
Now, when a user clicks on the Search button after entering some text, we will call getResults
method from ResultsFactory
. Then, once the results are back, we will bind them to the results
property on scope.
The completed directive will look like this:
searchApp.directive('mySearch', ['ResultsFactory', function(ResultsFactory) { return { templateUrl: './directive.html', restrict: 'E', link: function postLink(scope, iElement, iAttrs) { scope.search = function() { var q = { query : scope.query }; ResultsFactory.getResults(q).then(function(response){ scope.results = response.data.results; }); } } }; }])
With this, we can update our index.html
to this:
<html ng-app="searchApp"> <head> <script src="angular.min.js" type="text/JavaScript"></script> <script src="app.js" type="text/JavaScript"></script> </head> <body> <my-search></my-search> </body> </html>
We can update our app.js
to this:
var searchApp = angular.module('searchApp', []); searchApp.factory('ResultsFactory', ['$http', function($http){ return { getResults : function(query){ return $http.post('/getResults', query); } }; }]); searchApp.directive('mySearch', ['ResultsFactory', function(ResultsFactory) { return { templateUrl: './directive.html', restrict: 'E', link: function postLink(scope, iElement, iAttrs) { scope.search = function() { var q = { query : scope.query }; ResultsFactory.getResults(q).then(function(response){ scope.results = response.data.results; }); } } }; }]);
Quite simple, yet powerful!
Now, you can start sprinkling the <my-search></my-search>
tag wherever you need a search bar.
You can take this directive to another level, where you can pass in an attribute named results-target
to it. This would essentially be an ID of an element on the page. So, instead of showing the results below the search bar always, you can show the results inside the target provided.
Note
AngularJS comes with a lightweight version of jQuery named jqLite. jqLite does not support selector lookup. You need to add jQuery before AngularJS for AngularJS to use jQuery instead of jqLite. You can read more about jqLite at https://docs.angularJS.org/api/ng/function/angular.element.
This very feature makes AngularJS directives a perfect solution for reusable components when dealing with DOM.
So, if you want to add a new navigation bar to your Ionic app, all you need to do is throw in an ion-nav-bar
tag, such as the following one, one your page:
<ion-nav-bar class="bar-positive"> <ion-nav-back-button> </ion-nav-back-button> </ion-nav-bar>
Then, things will fall in place.
We went through the pain of understanding a custom directive so that you could easily relate to Ionic components that are built using AngularJS directives.