Skip to content
  • Services
  • About
  • Partners
  • Work
  • Insights
  • Careers
  • Contact
  • Services
  • About
  • Partners
  • Work
  • Insights
  • Careers
  • Contact
September 16, 2014

A Direct Dive Into Angular Directives

Angular

Angular is awesome. Directives, on the other hand, can be a pain in the ass. They seem complex – their place in the application sometimes seems unclear, and using them properly is practically an art form. Fortunately anyone can learn why directives are the beating heart of the Angular framework, and with some instruction and practice begin writing their own. Today!

So let’s get started.

How Directives Wake Up

Angular applications have two distinct phases: Compilation and Runtime. Compilation is a multistep process that begins when the browser fires the DOMContentLoaded event. When this event fires – and assuming the ng-app directive was attached to an element – Angular will automatically start loading. If the ng-app directive is missing Angular will require manual loading. In either case, when Angular starts it immediately creates the rootScope, which it attaches to the element with the ng-app attribute, and then starts the compile service. The compile service treats that same element as the document root, and begins its work from that point.

The compile service is a two step process. More importantly, the compile service is also the point at which directives are initialized. As elaborate as it seems, a directive is just a function executed when the compiler service reaches it in the DOM. The first step the compile service takes is – appropriately – to compile each directive it finds. Specifically the compile service runs the directive’s own compile function. If the compile service finds an element with more than one directive, then the directives are sorted by priority and each compile function is executed in order.

Parsing the entire DOM for directives and executing them in turn is not terribly performant. The responsiveness of an application with thousands of directives would be impacted by this type of recursive process. Therefore, Angular separates the effort of directive compiling with the effort of linking the compiled directive to a scope and attaching the necessary listeners / watchers. This second effort is step two in the two step compile service process. Essentially the link step produces a live binding between the DOM and the scope. Remember also, the scope in this instance is the scope as defined by the directive – not necessarily the scope of the immediate parent controller. More on that below.

How Directives Work

Angular provides a special augmentation to the browser’s event queue called the digest loop. In the normal event loop, events occur and are placed into the queue for execution. If function handlers are configured to execute when an event fires they are called with the event as a parameter. Thanks to the digest loop, directives themselves can register event listeners So when a given event fires, the directive runs – within the context of the digest loop.

So what does this mean from a practical standpoint? When a browser event triggers, if the directive is listening it fires – in the digest loop – and the digest loop will keep spinning until all possible changes spawned by the directive are resolved. In fact, once the final change is made the loop will run one last time, just to be sure. If you have ever seen duplicated console.log() messages, this is the reason.

What makes Angular so powerful is that the digest process occurs between every browser event. This means directives registered as event listeners react and behave just like built in functions, and can be as performant as other, non-Angular techniques such as those favored by jQuery.

The DOM and the Directive

Unlike other MVC frameworks, Views in Angular are constructed with plain old HTML syntax. Unlike jQuery, Angular does not explicitly manipulate the markup within a View to provide user interface behavior. Views in Angular are the official record, the final statement clearly documenting what happens upon rendering. As a result, Angular rarely requires element IDs, and classes are used almost exclusively for styling.

Standard jQuery programs take an imperative approach to DOM manipulation. Elements intended as part of some user interface behavior are commonly configured with IDs, and the developer crafting the interaction must be well aware of the entire DOM. In contrast, Angular’s declarative approach requires the developer to handle only the data pushed into the View by the Model. The plumbing is handled by the framework, behind the scenes.
Directives are an important part of Angular’s declarative approach. A directive is literally an improvement to the DOM – an extension of what ordinary markup is capable of, while obeying HTML syntactical rules. A well crafted directive can be shared between projects – consider the large number of directives provided by Angular. For this reason, best practice – aka “The Angular Way” – is that developers stuff all DOM related functionality into a directive or set of directives, and to keep this functionality as declarative as possible.

A Peek Inside The Machine

A directive contains two basic pieces: the return object and all of the code outside of the return. The return object itself possesses many different properties and methods. However a directive requires surprisingly few properties to operate correctly. For example:

angular.module('app.directives')
    .directive('spinner', function(){
        return {
            restrict: "E",
            template: "<div id='circularG'> … </div>"
        };
    });

Note how the restrict and template properties require strings. Some properties can accept multiple data types. The scope property, for example, accepts true/false. It can also accept an object as in this example:

angular.module(app.directives')
    .directive('activateAndToggle', function() {
        var YES_CLASS = "upsellBtnYes";
        var NO_CLASS = "upsellBtnNo";
        return {
            restrict: 'A',
            scope: {
                answerButton: '@',
                toggleClick: '&'
            },
            link: function(scope, element, attrs, ctrl) {
                element.bind('click', function(evt) { … });
            }
        }
    });

Also, notice the variables declared prior to the return. These variables are useful later within the link’s click listener – and declaring them outside the return improves overall code reusability.

Here are a few of the more common properties and methods available to the return object:

Restrict A property that defines how a directive is allowed to appear within the DOM. The restrict property will accept one or more items from this list:

E – Element ( <my-directive></my-directive> )
A – Attribute ( <div my-directive></div> )
C – Class ( <div class=”my-directive”></div> )
M – Comment ( <!– directive: my-directive –> )

Scope Property that determines the relationship between the directive’s scope and the immediate parent’s scope. The options are:

False – directive scope is an extension of the parent’s scope
True – directive prototypically inherits from the parent’s scope

The final choice is to pass the scope property an object, which creates an isolate scope cut off completely from the parent scope.

TemplateUrl (or) Markup template required by a directive can be stored in a separate file and loaded into the directive via this property. This template is cached by Angular’s $templateCache service the first time it is loaded, and is accessible through multiple means. Templates cached by $templateCache load very quickly.
Template Markup can also be fed to the directive via this property. The markup is part of the overall code structure in this situation, and the markup must be encapsulated by a container (sibling elements without an immediate parent are not allowed). Makes the source code messier, but this property is sometimes easier to use.
Controller The controller method accepts parameters via traditional dependency injection. However any code located in the controller is executed BEFORE the directive compiles. Generally speaking it is a better idea to place code within the link function.
Link Any behavior interacts with the DOM goes into this method. Link is executed AFTER the directive compiles. Parameters passed to the link are not dependency injections (as with the controller), but they must be passed in a specific order. The parameter definitions are:

scope – directive’s scope object
element – the element to which the directive is attached
attrs – hash table of all of the element’s attributes

Building Community Best Practices

Thanks to Angular’s accelerating popularity, community best practices are forming and solidifying. Adhering to community best practices improves the community and helps you and your organization by ensuring your project stays maintainable and extensible. A couple of our favorite best practices include:

Invoke directives sensibly. If you are building a clock, stock ticker or animated logo widget, use an element. On the other hand, if you are developing specific programmatic functionality use an attribute. The majority of Angular’s bundled directives are invoked as attributes. As for classes and comments? Forget about them. Using elements and attributes make it easier to determine what directives match a given element.

Prefer the templateUrl property over template. HTML template files injected via templateUrl can be cached – or even precached – within Angular’s $templateCache, which eliminates additional XHR transactions thereby improving performance. Additionally, keeping directives free from random string concatenated HTML will improve everybody’s day.

Final Thoughts

Directives are not as bizarre as they may first appear. If you have trouble getting your head around them, try writing your own. Create an event listener on a button using Angular’s declarative approach and prove it to yourself – and then build on that success. Practice by writing your own directives whenever possible. Eventually, these concepts will click – good luck!

Posted in Angular
Share this

Jay Garcia

Jay Garcia is co-founder and managing director at Modus Create. He is a U.S. Air Force veteran with 20 plus years of technology and consulting experience in leading RIA development for companies around the world. He is co-organizer of the NoVa.JS and NYC.JS meetups, and is actively involved in the software communities that provide business frameworks and technologies, which enable rich mobile and desktop web experiences.
Follow

Related Posts

  • AngularJS-Tricks-with-angular-extend
    AngularJS: Tricks with angular.extend()

    AngularJS has this built-in method for doing object to object copies called angular.extend(). It is…

  • Angular + React Become ReAngular
    Angular + React Become ReAngular

    Angular and React developers have revealed this morning that they are planning to merge to…

Subscribe to the Modus Newsletter

Receive the latest blog articles and insights every month from the Modus team.

Let's Chat

If forms aren’t your thing, you can always call us (+1-855-721-7223).

Modus-Logo-Primary-White.svg
  • Services
  • About
    • Newsroom
  • Partners
  • Work
  • Insights
    • Blog
    • Modus Labs
  • Careers
Virginia (US)

12100 Sunset Hills Road
Suite 150
Reston, Virginia, 20190
Tel: +1-855-721-7223

California (US)
12130 Millennium Dr

Los Angeles, CA 90094

Missouri (US)
609 E High St

Jefferson City, MO 65101

Romania

Str. Mihai Veliciu, no. 17
Cluj-Napoca, Romania
Tel: +40-0786-887-444

Costa Rica

2nd Floor, Plaza Koros, Av 3
San José, Santa Ana, Costa Rica

© 2021 Modus. All Rights Reserved.

Privacy Policy | Accessibility Statement | Sitemap

This website uses cookies.
These cookies are used to collect information about how you interact with our website and allow us to remember you. We use this information in order to improve and customize your browsing experience, and for analytics and metrics about our visitors both on this website and other media. To find out more about the cookies we use, see our Privacy Policy.

Accept
Privacy & Cookies Policy

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary
Always Enabled

Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.

Non-necessary

Any cookies that may not be particularly necessary for the website to function and is used specifically to collect user personal data via analytics, ads, other embedded contents are termed as non-necessary cookies. It is mandatory to procure user consent prior to running these cookies on your website.

Scroll To Top