Web Components and AngularJS

An introductory guide to creating reusable directives


Web Components and AngularJS

AngularJS, the so called Superheroic Javascript Framework, offers an intuitive and sophisticated way to encapsulate behavior and data in what we can now call a Web Component.

This is an advanced topic, please refer to the original AngularJS documentation to erase all doubt.

A brief introduction to the Web Components concept:
Web Components are a collection of standards that will eventually allow Web developers to create isolated pieces of code that may include HTML templates, isolated CSS, isolated javascript and a nice API to interact with them in an elegant and resource-saver manner.
Raw visualization of a Web Component.

But what do Web Components have to do with AngularJS? A lot more than you may expect. Right now, the closest we can get to Web Components from an AngularJS perspective is to a Angular Directive with isolated scope.

AngularJS Directives and the isolated scope:

Directives offer a great way to accomplish web-component-like result, they can inject HTML and add isolated Javascript behavior unique to the component itself. Directives can also interact with the external world by emitting and broadcasting events (an advanced topic that is out of the scope of this article.)

Our button component

The component we will be developing will be a simple button. For the sake of simplicity, we will use Bootstrap framework for styling. Let’s get started.

Things to keep in mind while developing custom components:

  1. Each component is a world — most of the times we would not want to mess with the external world data, therefore it is convenient to keep all of our components isolated with their own scope, this is achieved by setting scope: {…} in the directive definition object.
  2. Each component in its own module modules in Angular are a great way to create a clear separation between each part of your application that can work without depending on others.
  3. Each component with its own namespace — namespaces are a great way to avoid collisions between two projects that can use similar or equal name for their components, — i.e. I can create a component named menu, but there can be thousands of other developers that can name their component menu as well, if for any reason I decide to use your components in my project, both directives will collide by name and Angular will throw an error. — To avoid this collision I can use a namespace for my directive, say foo-menu, that way changes to get a collision with other component drop considerably.

Directive Definition Object (DDO)

The directive definition object has multiple properties that can be of use depending on your necessities, but first of all and adhering to our best practices from above, our button needs its own module (we will call it MyButtonModule) and its own namespace (this will be ‘my’):

angular.module(‘MyButtonModule’, [])

Once we have our module defined, we can continue defining our directive:

angular.module(‘MyButtonModule’, [])
.directive(‘myButton’, function (){});

Next, we’ll cover the DDO properties for the directive configuration, for the sake of simplicity, the button will receive a text property, this text will be added to the template to create the button it self. I will explain the code afterwards, for now here is the DDO:

angular.module(‘MyButtonModule’, [])
.directive(‘myButton’, function (){
return {
restrict: 'E',
replace: true,
template: '<button class="btn btn-default" ng-bind="text"></button>',
transclude: true,
scope: { text:'@' },
link: function (scope, iElement, iAttrs, controller, transcludeFn){transcludeFn(scope.$new(), function (clone){
iElement.append(clone);
});}
} // End of return

});

Restrict will constrain the directive use to an element:

restrict: ‘E’

Replace must be set to true if we want our directive to be replaced by the template we provide:

replace: true

Template contains the string (usually representing an HTML element) that will replace the directive original tag (<my-button>):

template: '<button class="btn btn-default" ng-bind="text"></button>'

Transclude is set to true to enable the transcludeFn called during the link phase:

transclude: true

Scope contains an object with properties and flags, the flag used for this directive is ‘@’ which means a one-way data binding with the same name as the property (text):

scope: { text:'@' }

Link, the linking function takes five parameters, the fifth one contains the transclusion function enabled with transclude: true; this function allow us to access the transcluded content of the directive (the clone of it) and append it wherever we consider is the best:

link: function (scope, iElement, iAttrs, controller, transcludeFn){...}

This directive should be used as an element like the following one:

<my-button text="Click Me!"></my-button>

And it will produce the following HTML

<button class="btn btn-default">Click Me!</button>

For better understanding, you can play around with this Pen.

Conclusion:

There is a lot of room to create while using AngularJS directives as components, our button directive can also have logic in it (using the controller property of the DDO), you can dynamically set the button class, add more attributes and even binding for text.

I will eventually be writing articles with more detail about producing AngularJS directives, development environments, automation tools and unit testing, keep in touch. Thank you for reading.

Show your support

Clapping shows how much you appreciated Manuel Ro’s story.