Angular Icon Get 73% off the Angular Master bundle

See the bundle then add to cart and your discount is applied.

0 days
00 hours
00 mins
00 secs

Write AngularJS like a pro. Angularjs Icon

Follow the ultimate AngularJS roadmap.

Creating an AngularJS Directive from one of your existing plugins scripts

Writing scripts for your websites or web apps is often a simple process, you write your script, concatenate the file into your main scripts file and it gets pushed into the DOM. Not much to it, but when it comes to AngularJS, they believe in something slightly different…

No DOM manipulation should be carried out inside a Controller, the Controller is where most of your magic happens, a communications channel between your Model data and the browser. It can be tempting to simply whack in an existing script in there (as it’ll work just fine), but this goes against Angular’s principles.

So here’s how to migrate one of your existing scripts or plugins across into a tightly coded AngularJS directive, this also makes code readability and reuse ultra-efficient, as Directives take the strain of repetitive code out the window.

Directives are Angular’s answer to Web Components ‘Shadow DOM’ but are compatible in all browsers (not just cutting edge HTML5 supporting ones) - bringing you the power of the future technology, today. Shadow DOM injects new content based on the element, has its own CSS and JavaScript scope and introduces some incredible behaviour mechanisms, and this is what Directives mimic to bring you this technology today.

Defining a Directive:

Directives are really easy to use once you’ve set them up. For purposes of this demonstration I’m going to migrate FluidVids across into an AngularJS Directive.

Existing code

Here’s the existing code for the script, annotated below to show what each part does.

window.fluidvids = (function (window, document, undefined) {

  'use strict';

  /*
   * Constructor function
   */
  var Fluidvids = function (elem) {
    this.elem = elem;
  };

  /*
   * Prototypal setup
   */
  Fluidvids.prototype = {

    init : function () {

      var videoRatio = (this.elem.height / this.elem.width) * 100;
      this.elem.style.position = 'absolute';
      this.elem.style.top = '0';
      this.elem.style.left = '0';
      this.elem.width = '100%';
      this.elem.height = '100%';

      var wrap = document.createElement('div');
      wrap.className = 'fluidvids';
      wrap.style.width = '100%';
      wrap.style.position = 'relative';
      wrap.style.paddingTop = videoRatio + '%';

      var thisParent = this.elem.parentNode;
      thisParent.insertBefore(wrap, this.elem);
      wrap.appendChild(this.elem);

    }

  };

  /*
   * Initiate the plugin
   */
  var iframes = document.getElementsByTagName( 'iframe' );

  for (var i = 0; i  0) {
      new Fluidvids(iframes[i]).init();
    }
  }

})(window, document);
Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

Directive code

First you need to know how to restructure your code, instead of using a small API to apply your script to each of the elements in your selector/plugin. Remember to apply your changes to the single scoped element only, as Directives are for reused components and therefore can be repeated a lot, so they refer to themselves instead of as a NodeList of elements to loop through.

// Module
var myApp = angular.module('myApp', []);

// FluidVids Directive
myApp.directive('fluidvids', function () {

  return {
    restrict: 'EA',
    replace: true,
    scope: {
      video: '@'
    },
    template: '<div class="fluidvids">' +
                '<iframe ng-src="{{ video }}"></iframe>' +
              '</div>',
    link: function (scope, element, attrs) {
      var ratio = (attrs.height / attrs.width) * 100;
      element[0].style.paddingTop = ratio + '%';
    }
  };

});

I’ll talk through the above for those interested in what the workings are. A Directive returns an Object, which all of the configuration sit inside for that specific Directive. I’ve used restrict with the value of ‘EA’, this means either an Element or Attribute. I then use replace to replace the markup in the DOM so that it renders nice and valid. I’m using a scope here as the third property which you can grab as your element name. Using ‘@’ means I’m just using this as a string, which you’ll see I’ve used inside my small template which gets injected in the DOM with the custom video src. I’m also using ng-src here which Angular recommend for better browser consistency when dynamically creating src attributes (mainly for legacy browsers, ofc). I then create a small link function which defines any DOM manipulation past the template that needs doing. You can also bind click events any anything else here too.

Moving JavaScript styles to CSS

Using an Angular Directive allowed me to use less individual DOM manipulation as I split out the JavaScript style objects into using CSS instead:

.fluidvids {
    width: 100%;
    position: relative;
}
.fluidvids iframe {
    border: 0;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

The video ratio isn’t here as this is calculated by the JavaScript and appended to each individual element, that ensures each video gets a custom ratio as intended by any initial iframe embeds.

Release Candidate errors with cross-domain media

Whilst porting FluidVids over to AngularJS, it was really easy testing until I hit the Release Candidate (version 1.2.0-rc.2). They’ve actually done this to help you out, but you actually need to whitelist external domains that you’ll be retrieving media from. I got this error whilst changing from version 1.0.8 to 1.2.0-rc.2:

Error: [$interpolate:interr] https://errors.angularjs.org/undefined/$interpolate/interr?p0=%7B%7B%20src%2…%24sce%2Finsecurl%3Fp0%3D%252F%252Fplayer.vimeo.com%252Fvideo%252F23919731

Turns out, after a quick Google search I needed to whitelist the domains I use inside AngularJS, a somewhat protectively smart move (even though slightly irritating error) by the team. After some searching I found somebody had commented the following which whitelists all domains for quick and ease use:

myApp.config(function ($sceDelegateProvider) {
  $sceDelegateProvider.resourceUrlWhitelist(['.*']);
});

Custom Elements or Attributes (E or A, or both!)

If you’re advocating HTML5 Web Components and developing for HTML5 browsers only, then you might as well start creating your own elements inline with the HTML5 Web Components spec.

Custom element:

<fluidvids video="//player.vimeo.com/video/23919731" height="281" width="500"></fluidvids>

As an attribute:

<div fluidvids video="//player.vimeo.com/video/23919731" height="281" width="500"></div>

Depending on your team and setups, it might be easier using one method or the other. They’re not too indifferent, but I feel Web Components offer oddly better semantics - despite their loose markup that let the developer decide.

Web Components introduces custom attributes too, use - but don’t abuse :)

Learn JavaScript the right way.

The most complete guide to learning JavaScript ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover