Angular Directive Guidelines

May 24 2015

The Angular documentation for
directives
can be a bit
intimidating so here are a few simple guidelines to creating a reusable
widget in Angular. A widget is a visual element implemented via an
Angular directive and tested mostly manually in a browser. If the widget
performs some calculations such as date calculations for instance that
do require tests, this is best done in an injected lib that is tested
separately. Examples of widgets are two-way sliders, multi-select
drop-downs, graphs etc., these are visual elements that have the
following interface to the outside world:

  • They have an isolate scope which contains their data model that they draw and mutate
  • They draw their visuals under the directives element according to their isolate scopes values
  • They may call callback functions that are bound in their isolate scope for instance on click events
  • They may mutate their internal isolate scope upon various UI events
  • They may register on global events such as window resize

It is important to note the following about widgets:

  • They do not rely on their parent scopes in general and on the rootScope in particular
  • They always unbind from global events upon scope destroy

A widget may be implemented in its entirety using the
javascript postLink(scope, elem, attr)
function. There is no need to use a controller function in a
directive and this practice is actually discouraged unless there's a
specific need to share code between directives. Certain performance
optimizations may be achieved leveraging also the
javascript compile(elem, attr)
function and of course the template or templateUrl options help in
organizing the code although they are not strictly needed in many cases.

The general code layout of a widget is therefore as follows:

function myWidget(someInjectedLib) {
  return {
    restrict: 'E',

    scope: {
      // Isolate scope parameter list
      // May contain callbacks with ‘&’ binding
      // May contain values that we wish to reflect via the ‘=’ binding
      // May contain values that we wish to use like the ‘@’ binding
    },
    templateUrl: 'myWidgetName.html',
    link: function postLink(scope, elem, attr) {
      // Init

      // Here we do things like register on global events
      // and init local variables

      // UI -> Model

      // Here we write handlers for the local widget’s UI events
      // and mutate the scope accordingly and/or call callbacks
      // on the scope to let the component know that something happened

      // Model -> UI

      // Here we write watches on the isolate scope and change the
      // drawing of the widget according to changes in the model

      // Cleanup

      // Here we unregister from global events on scope destroy
    },

  };

}
Gil M.
Software Developer
Back to Blog