{
    "componentChunkName": "component---src-templates-post-js",
    "path": "/blog/2014/02/11/lets-make-full-ass-angularjs-directives",
    "result": {"data":{"site":{"siteMetadata":{"title":"your friend Joel's digital garden","description":"Articles and notes from a collaborator at egghead.io. Musings on software, business, and life from a skilled virtual assistant.","author":{"name":"Joel Hooks"},"keywords":["Video Blogger"]}},"mdx":{"excerpt":"With best intentions we set forth to create the mighty directive. The steepest slope of the dreaded AngularJS learning curve. The \"place where the jQuery goes.\" So what is a directive? We've  talked about this before , and decided that they…","fields":{"github":"https://github.com/joelhooks/joelhooks-com/tree/master/content/legacy_blog/2014-02-11-lets-make-full-ass-angularjs-directives.markdown"},"body":"var _excluded = [\"components\"];\n\nfunction _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"layout\": \"post\",\n  \"title\": \"Let's Make Full-Ass AngularJS Directives\",\n  \"date\": \"2014-02-11T00:00:00.000Z\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, _excluded);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, mdx(\"em\", {\n    parentName: \"p\"\n  }, \"With best intentions we set forth to create the mighty directive. The steepest slope of the dreaded AngularJS learning curve.\")), mdx(\"h3\", null, \"The \\\"place where the jQuery goes.\\\"\"), mdx(\"p\", null, \"So what is a directive? We've \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://www.joelhooks.com/blog/2013/07/27/using-angularjs-stop-using-jquery-as-a-crutch/\"\n  }, \"talked about this before\"), \", and decided that they are \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"not\"), \" where the jQuery goes... usually... if you \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"are\"), \" going to use jQuery, directives are \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"definitely\"), \" where it should go.\"), mdx(\"p\", null, \"It's easy to say \\\"OMG, NO JQUERY\\\" - but what does that actually mean? What does a \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"really kickass directive\"), \" look like?\"), mdx(\"h3\", null, \"The Anatomy of a Good Directive.\"), mdx(\"p\", null, \"The \", mdx(\"a\", {\n    parentName: \"p\",\n    \"href\": \"https://github.com/angular-ui/bootstrap\"\n  }, \"ui-bootstrap\"), \" library is the best singular resource on what a good directive should look like. They are solving multiple common problems in varied ways, with generally solid patterns and practices. It's a robust open-source project, with a constant flurry of activity.\"), mdx(\"p\", null, \"When you start to dig through the library's src, you can see how many different styles and solutions have solved the various problem. Some are extremely complex, while some are relatively simple.\"), mdx(\"p\", null, \"While the solutions \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"are\"), \" varied, there are also some common traits the ui-boostrap directives share across the library.\"), mdx(\"h4\", null, \"Minimal use of the link function\"), mdx(\"p\", null, \"How many of your project's link functions contain \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"all the things\"), \"?\"), mdx(\"p\", null, mdx(\"em\", {\n    parentName: \"p\"\n  }, \"me: raises guilt hand sheepishly\")), mdx(\"p\", null, \"Down and dirty, just toss it in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"link\"), \" function.\"), mdx(\"p\", null, \"When you start to browse the ui-bootstrap code, pay attention to the link functions on the directives themselves.\"), mdx(\"h5\", null, \"dropdown\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-javascript\"\n  }, \".directive('dropdown', function() {\\n  return {\\n    restrict: 'CA',\\n    controller: 'DropdownController',\\n    scope: {\\n      isOpen: '=?',\\n      onToggle: '&'\\n    },\\n    link: function(scope, element, attrs, dropdownCtrl) {\\n      dropdownCtrl.init( element );\\n    }\\n  };\\n})\\n\")), mdx(\"p\", null, \"The \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Dropdown\"), \" directive has a link function, but its sole purpose in life is to associate the directive with a controller.\"), mdx(\"p\", null, mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"This is a good thing\")), mdx(\"p\", null, \"Why? Well, to be honest, directives can be a real pain in the ass to unit test. How do you make a directive easy to unit test? Don't give it any functionality.\"), mdx(\"p\", null, \"Controllers, on the other hand, are easy to unit test, so we can avoid the headache of even thinking about unit testing a directive by offloading the logical bits to a controller.\"), mdx(\"h4\", null, \"The directive's controller\"), mdx(\"p\", null, \"What does it look like? Basically, it looks like a normal controller.\"), mdx(\"h5\", null, \"DropdownController\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-javascript\"\n  }, \".controller('DropdownController', function($scope, $attrs, dropdownConfig, dropdownService, $animate) {\\n  var self = this, openClass = dropdownConfig.openClass;\\n\\n  this.init = function( element ) {\\n    self.$element = element;\\n    $scope.isOpen = angular.isDefined($attrs.isOpen) ? $scope.$parent.$eval($attrs.isOpen) : false;\\n  };\\n\\n  this.toggle = function( open ) {\\n    return $scope.isOpen = arguments.length ? !!open : !$scope.isOpen;\\n  };\\n\\n  // Allow other directives to watch status\\n  this.isOpen = function() {\\n    return $scope.isOpen;\\n  };\\n\\n  $scope.$watch('isOpen', function( value ) {\\n    $animate[value ? 'addClass' : 'removeClass'](self.$element, openClass);\\n\\n    if ( value ) {\\n      dropdownService.open( $scope );\\n    } else {\\n      dropdownService.close( $scope );\\n    }\\n\\n    $scope.onToggle({ open: !!value });\\n  });\\n\\n  $scope.$on('$locationChangeSuccess', function() {\\n    $scope.isOpen = false;\\n  });\\n})\\n\")), mdx(\"p\", null, \"It's obviously \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"much\"), \" bulkier than the actual directive, but it's also crystal clear what the controller is doing. Normal controller stuff!\"), mdx(\"p\", null, \"One particular item of note is the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"init\"), \" function. In the directive above, you probably noticed that the link function did exactly one thing:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-javascript\"\n  }, \"dropdownCtrl.init(element);\\n\")), mdx(\"p\", null, \"Since you can't get at the element in the controller, this allows us to still have access to the element, but in a clean, testable, injected way.\"), mdx(\"p\", null, \"If we were going to critique the controller, it might be about its access to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"$element\"), \" at all. Is the controller the right place to be doing \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"any\"), \" DOM manipulation, even if it is ever so slight? Where else would we do that if we aren't going to do it in the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"link\"), \" function or the controller?\"), mdx(\"p\", null, \"It needs to go someplace, and pedantic nitpicking is just a hobby.\"), mdx(\"h4\", null, \"Out at the boundaries\"), mdx(\"p\", null, \"This facet of this clean gem of a directive sparkles bright:\"), mdx(\"h5\", null, \"dropdownService\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-javascript\"\n  }, \".service('dropdownService', function($document) {\\n  var self = this, openScope = null;\\n\\n  this.open = function( dropdownScope ) {\\n    if ( !openScope ) {\\n      $document.bind('click', closeDropdown);\\n      $document.bind('keydown', escapeKeyBind);\\n    }\\n\\n    if ( openScope && openScope !== dropdownScope ) {\\n        openScope.isOpen = false;\\n    }\\n\\n    openScope = dropdownScope;\\n  };\\n\\n  this.close = function( dropdownScope ) {\\n    if ( openScope === dropdownScope ) {\\n      openScope = null;\\n      $document.unbind('click', closeDropdown);\\n      $document.unbind('keydown', escapeKeyBind);\\n    }\\n  };\\n\\n  var closeDropdown = function() {\\n    openScope.$apply(function() {\\n      openScope.isOpen = false;\\n    });\\n  };\\n\\n  var escapeKeyBind = function( evt ) {\\n    if ( evt.which === 27 ) {\\n      closeDropdown();\\n    }\\n  };\\n})\\n\")), mdx(\"p\", null, \"The AngularJS 'service' is where the boundaries of our applications live.\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"connections to the outside world\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"domain/data models\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"core logic\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"...\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"the DOM?\")), mdx(\"p\", null, \"This actor is wonderful. It connects \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"all the dropdowns\"), \" and manages their shared state. In this case, we can only have one open drop down on the page. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"dropdownService\"), \" keeps track of who that is, and if another drop down is opened, it snaps the current one shut before allowing the next to open.\"), mdx(\"p\", null, \"Additionally, the service listens for events on the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"document\"), \" to close the open drop down if the user clicks the page or hits the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"esc\"), \" key.\"), mdx(\"p\", null, \"One central place to encapsulate what would otherwise be confusing spaghetti logic on line 342 of a typical directive's \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"link\"), \" function.\"), mdx(\"h3\", null, \"Pause and study\"), mdx(\"p\", null, \"It really pays to take pause and review a little code when you sit down to write a complex directive. For one, ui-bootstrap covers a lot of ground. The entire point of Bootstrap (proper) is to provide a robust set of \", mdx(\"em\", {\n    parentName: \"p\"\n  }, \"typical\"), \" components. Meaning, odds are the component you are building (at least the soul of it) is likely covered by the Boostrap component set.\"), mdx(\"p\", null, \"ui-bootstrap conveniently provides this world class reference implementation of the most common web application components... the Angular Way\\u2122.\"));\n}\n;\nMDXContent.isMDXComponent = true;","frontmatter":{"title":"Let's Make Full-Ass AngularJS Directives","date":"February 11, 2014","banner":null,"slug":null,"keywords":null}}},"pageContext":{"id":"3d119165-4c79-55dd-8cc5-85429ef2bd4c","prev":{"id":"9a697976-9642-5e56-a7a6-59fd756ecc73","parent":{"name":"2014-12-24-bootstrapping-egghead-dot-io-to-feed-my-family-2014-in-review","sourceInstanceName":"legacy"},"excerpt":"2014 Year in Review It's that time of the year. People dusting off the blog and endeavoring to capture the past 12 months of their life in a wordy nutshell. It's something that I've never participated in, but I thought I'd give it a try. I quit my…","fields":{"title":"Bootstrapping egghead.io to feed my family: 2014 in Review","slug":"blog/2014/12/24/bootstrapping-egghead-dot-io-to-feed-my-family-2014-in-review","date":"2014-12-24T00:00:00.000Z"}},"next":{"id":"3ddd2e99-9978-5cc3-ac49-048d61a25b81","parent":{"name":"2014-02-06-stop-writing-for-loops-start-using-underscorejs","sourceInstanceName":"legacy"},"excerpt":"How many for loops did you write today? This week? Sure. That's harmless enough. Ugly and weird, but not something to really complain about. But this is all too common: Which on the scale of bad code, isn't even  that  bad, but you start throwing…","fields":{"title":"Stop writing For loops. Start using underscore.","slug":"blog/2014/02/06/stop-writing-for-loops-start-using-underscorejs","date":"2014-02-06T00:00:00.000Z"}}}},
    "staticQueryHashes": ["1045846374"]}