Extensible Angular: Mixing up a Batch of Awesome

I’m taking a time out from learning Node and the new Angular router (I haven’t forgotten my promise on upcoming posts) to give you a quick tip on providing your application with extensible, modular directives using inheritance! No, this isn’t the classical inheritance that some of you will be familiar with in Java, C++, Python or C#; nor is this the prototypical inheritance that you have come to love in Javascript. No, this is more of a way to extend the composition of your objects to include new functionality, while preserving their original concept.

That’s right, I’m going to show you how mixins can make your Angular application (and furthermore your javascript) more modular, maintainable, reusable and of course, extensible.

Mixins

First, let’s cover the idea of a mixin. A mixin is a collection of properties and methods that are not meant to represent a full idea by themselves, but rather provide additional functionality to another object. In Java and .Net, there is a somewhat similar concept, known as Interfaces. There is one key difference between the two. Interfaces simply provide the overall scaffolding of shared behavior that will be implemented by the class using the interface. A mixin, on the other hand, is more powerful as it actually provides fully built functionality that will be added to your object.The reason for this is due to javascript’s inherent dynamic nature that allows you to add and remove functionality from an object at will. It’s one of the reasons we all love (and sometimes hate) javascript.

Let’s take a quick look at an interface.

// C# Interface
public interface IShareable {
    public void share();
}

Note that the interface only provides a signature for the share routine; it does not define its inner workings.

Now let’s take a look at a mixin.

var mixin = {
	share: function(users){
		// Actual code to share your object here.
	}
}

Like I said, this allows you to create a function that can be added to any other object. It’s not better, just a different, yet comparable idea. I wish javascript had interfaces as well…but that’s for another day.

For a deeper look at mixins, check out Angus’ post A fresh look at Javascript Mixins. It provides a lot of excellent insight into how to create them and add them to your objects. For the rest of this post, we will be using an Angular method angular.extend, which will allow us to add our mixins to our controllers and directives, mostly because it’s a part of Angular and laziness (err…read as efficiency) is a part of the job!

Extending Directives and Controllers

Alright, now that we have a working understanding of mixins, let’s dive right into the heart of this post: moving common behavior shared between your directives and controllers out into factories and mixins.

Let’s say you have a bunch of directives that have similar directive definition objects (DDO):

angular.module('fields', [])
.directive('email', function(){
	return {
		restrict: 'E',
		controller: 'EmailController',
		scope: {
			model: '='
		}
		link: function(inputScope, inputElement, inputAttrs, ngModel){
			// link all the things!
		}
	};
})
.directive('phone', function(){
	return {
		restrict: 'E',
		controller: 'PhoneController',
		scope: { 
			model: '='
		}
		link: function(inputScope, inputElement, inputAttrs, ngModel){
			// link all the things!
		}
	}; 
})

Not the DRYest code, especially since nearly everything in those DDO’s is the same besides the controller. What can we do? How about creating a Factory that takes in a controller argument and spits out the correct DDO? Well, that would work well if you only ever have a few minor differences. Sure, you could pass in an options object that acts similarly to Python’s keyword arguments to allow the factory to build more customizable DDO’s…but there is a simpler way:

angular.module('fields', [])
.factory('BaseDDO', function(){
	return {
		restrict: 'E',
		scope: {
			model: '='
		}
		link: function(inputScope, inputElement, inputAttrs, ngModel){
			// link all the things!
		}
	}
})
.directive('email', ['BaseDDO', function(BaseDDO){
	return angular.extend(BaseDDO(), {
		controller: 'EmailController'
	});
]})
.directive('email', ['BaseDDO', function(BaseDDO){
	return angular.extend(BaseDDO(), {
		controller: 'PhoneController'
	})
]})

By moving this into a dumb factory that always returns the base object, we can get shared functionality that is DRY. But that only gets us half way there. We need to use angular.extend to take that base object and mix in our custom controller. It can also work the other way, where you defined your DDO in the directive and then have a factory that returns an object with just a controller property to mix in a shared controller.

Before I end this post, I have one more example for you, using controllers:

angular.module('fields', [])
.factory('BaseControllerProps', function(){
    return {
        required: false,
        hidden: false
    };
})
.controller('EmailController', ['BaseControllerProps', function(BaseControllerProps){
    angular.extend(this, BaseControllerProps, {
        readonly: false;
    });
}]);

This example is for a controller that is being aliased with the Controller As functionality within Angular. This is showcasing a bit more of the power from angular.extend. It allows you to extend your objects as much as you want. Simply make your base controller the destination object, and extend it with any other source objects, as needed. This will then copy over functionality and properties from the other objects to your controller.

Note that you can also clone all of your destination objects onto an empty source object {}. This preserves your destination objects, while allowing you to get a new object with all of the old features. I didn’t do this in the above example because Angular does not respect a return from the controller function when you use the Controller As functionality. So, a returned, cloned, and extended object would never be used. Because of this, you need to actually extend the base controller object.

Alright, to wrap up, Mixins are an awesome way to teach an old object new tricks, keep your code DRY (especially your Angular code, which can be quite the chore at times) and make everything much easier to read. Take care and enjoy your code’s modularity, maintainability and finally its extensibility!

Advertisements

ng-conf 2015 Live Feed

HOLY NEWS, this feed so far has been a crazy awesome journey chock full of information.

Here’s a link to check it out.

So far the main important information that I have watched so far is the following:

  • There is no AtScript, only TypeScript
  • Angular 2 has vast performance improvements over Angular 1.x that almost rival pure javascript. Mmmm….
  • The new Angular router allows far better routing for normal and nested views through a unified concept that matches urls to Web Components
  • Shadow DOM and Web Components are integral features of Angular 2…but get this, they’re standard web technologies.
  • EcmaScript 6’s template strings not only allow the inclusion of variables with the #{variable} syntax, but also allow for large blocks of strings and templates without all of that pesky concatenation!
  • Much, much more, as this is only the first half of day 1!

I won’t have a chance to watch it all, but so far, these talks have been wildly exciting.

I’ll catch up with you soon on the topics of testing, the new Angular router, vagrant and docker in the near future!

Happy viewing!

The Many Benefits of AngularJS’s ControllerAs

ControllerAs has been around for quite some time, but I hadn’t given it much thought until recently. You see, I was having issues with $parent and the isolated scopes of 3rd party directives. As if that weren’t enough, I recently read that AngularJS 2 will no longer have $scope. If you have been following AngularJS closely, you will have heard by now that they are currently developing a new major version and it is a game changer. Anyways, I needed a fix to my $parent conundrum and what I found actually has prepared me for the coming death of $scope. We will mourn our loss, dear friends, but at least we can prepare and put our affairs (and applications) slightly in order.

AngularJS’s $scope service provides us with a lot of key functionality in 1.x. You use it from binding your controllers to your views, binding/triggering events and even use it to control how Angular’s $digest cycle. So far, it has been incredibly useful…until you try to get a custom callback to function (pun intended) when someone clicks the “open” button for an Angular UI datepicker. Suddenly you run into an issue where after the user selects a date, they can no longer open the datepicker control. A typical fix? Change the datepicker popup’s isOpen attribute to use the $parent service:


<!-- Awesome fake input tag -->

<input is-open="$parent.opened" />

Problem solved, right? Wrong. You’ve just tightly coupled your datepicker popup to its parent controller’s scope, bub, and you cannot move the datepicker in that controller’s scope hierarchy without reintroducing the same issue you originally solved. Blegh. So what’s a young, resourceful web guru like yourself left to do? Use the ControllerAs functionality that is built into AngularJS 1.1.5+.

Setup your controller to use ‘this’ instead of $scope:


// Awesome controller code:

this.today = function() {
return new Date();
};

//etc...

Bootstrap your controller with an alias like ‘main’ (or something a LOT more semantic, if anyone else is ever going to read your code. Seriously, do everyone a favor. The same goes for class, function, variable and file names), and use that alias to bind to anything you exposed from the controller using the ‘this’ keyword:


<div ng-controller="mainCtrl as main">

<p>{{main.today()}}</p>

</div>

I know, that example had nothing to do with my datepicker sorrows. However, you can see how I changed Angular UI’s datepicker plunker to use the ControllerAs functionality here.

Note: You don’t have to use the ‘this’ keyword. You can instead create an object in your controller, add your properties and methods, and then return that object. The returned object then acts like your scope.

Now, you are loosely coupled and can move your datepicker anywhere within your controller’s hierarchy and can still use its functionality. Sure, you’ll see aliases everywhere, but you won’t ever deal with $parent.$parent.something ever again! In fact, you will know exactly which controller is providing what functionality! Still need to bind events or call $apply on some outside code? No worries, you can always use $scope in your controller for when you need it, rather than, well, all the time.

How does this also prepare you for AngularJS 2.0? Well, as stated before, Angular 2 will no longer have $scope. In fact, controllers are just a part of Angular’s Web Components (or Component Directive, as they are being called), where you simply define your component’s controller as a class that follows the ComponentDirective constructor. You add various class members to ‘this’, and they will be provided to the component’s view. Just like the ControllerAs functionality in 1.1.5+. All in all, ControllerAs is a simple way to attack issues with scope hierarchy as well as get yourself ready for the death of $scope.

Murmuring, Stuttering with Just a Pinch of Mumbling

There’s an awesome new standard for web developers that has been floating around the web for a short time that allows you to speak to your web apps and web sites. While the documentation is currently an evolving specification for the new standard, you can still dig in without too much trepidation and find yourself mesmerized by the sheer brilliance of modern technology…or find yourself potentially chucking your old $10 Logitech headset in favor of something with a better microphone.

I am, of course, writing to you today on Google’s fancy new Web Speech API.

What is this new API you ask? Well, for an extremely short description, it is a way for you to allow people to interact with your website or web app with simply their voice. It doesn’t end there, either, as it also allows your site to talk back through their Speech Synthesis. Sounds an awful lot like Siri, Cortana or Google Now, but of course it comes with no almost SI like understanding of what you want from it. No, this is just a means of interacting with the person on the outside of all of the 1’s and 0’s…and what a simple method of interaction it is!

I implemented it recently within an AngularJS project that I have been working on called Project Voxie (More posts on that topic soon). I utilized Angular’s awesome service model to abstract away a good chunk of the API workings away from the controller code:

Speech API Service


voxie.service('speechSVC', [
    '$window', 'speechMockSVC', function($window, speechMockSVC) {
    return function() {
        var speechRec, speechSVC;
        speechRec = $window.SpeechRecognition || $window.webkitSpeechRecognition || speechMockSVC;
        speechSVC = new speechRec();
        speechSVC.continuous = true;
        speechSVC.interimResults = true;
        speechSVC.onResultCallback = angular.noop;
        speechSVC.onresult = function(event) {
            if (speechSVC.onResultCallback) {
                return speechSVC.onResultCallback(event.results);
            }
        };
        speechSVC.onerror = function(event) {
            console.log(event.error + ": " + event.message);
            if (speechSVC.onResultCallback) {
                return speechSVC.onResultCallback("Unfortunately, speech recognition has failed.");
            }
        };
        return speechSVC;
    };
}]);
 
voxie.service('speechMockSVC', [function() {
    return function() {
        var NOT_SUPPORTED, showError, speechMock;
        NOT_SUPPORTED = "Your browser does not support speech recognition. If you wish to use this application, please upgrade to a browser with speech recognition support.";
        showError = function() {
            return $window.alert(NOT_SUPPORTED);
        };
        return speechMock = {
            start: function() {
                return showError();
            },
            stop: function() {
                return showError();
            }
        };
    };
}]);

I used the service to provide an extended public API to my controller where I could specify a callback to perform once I had some results from Google. I added in support for the non-prefixed API (Chrome utilizes the webkitSpeechRecognition, but the specs call it simply SpeechRecognition), which also gave me the ability to handle any browser that didn’t have any of the speech API’s loaded on the window.

As you can see, it’s fairly simple. You tell it whether or not it should continue recording for an extended period of time (continuous), whether or not it should send you any fuzzy results that haven’t been confidently matched yet (interimResults), and wire up your success and error events. Once complete, you can use it within your controller:

Speech API Controller Integration

$scope.listen = function() {
    var listener;
    listener = speechSVC();
    listener.onResultCallback = discernCommand;
    return listener.start();
};

 

Here, discernCommand is simply a callback that joins the results array into a single string in order to be displayed on the page. The $scope.listen function is wired to a button as it was incredibly difficult to get it to work automatically on page load alongside Angular’s digest cycle. I’m not saying it can’t be done…but I’m not entirely sure that the two can play nicely together either. So for now, it’s wired to a button. When pressed, your callback is hooked into your service, and it starts recording. Pretty easy.

Review

What’s any write up of a new technology without a bit of a review at the end? The API is fairly easy to use once you decipher the specs. It integrates fairly well with the well known JS libraries (with a few idiosyncrasies like the aforementioned auto-listening and Angular’s digest cycle) and is…well…almost accurate. This is the most heart-breaking portion of it all. When I said “contest”, it sent back “Comcast”. Blegh. Insult on injury with that match up. But, I am hopeful, as Google Now seems to be fairly accurate, and who knows, it may be due to my $10 logitech headset. So, for now, I would stick to playing around with it until the results become more accurate and it becomes more widely accepted amongst browsers.