Commangular and TweetHours - Part 1 - Updating our Chosen Hours


In this video we are taking a deeper dive into the TweetHours internals, and in particular, how the Commangular library is provide a command chain - a sequence of commands that are given to our system, in order to get an expected outcome.

The idea here is to split up a larger task into smaller tasks - three smaller tasks in this case.

From a high level / birds eye view we might think:

Ok, we select that hour, and then our app should update its internal schedule and make sure all our future notifications are handled properly.

But, as software developers, we are all too aware that one small sentence of requirements can turn into days or weeks worth of work.

By splitting our single large command / task into smaller tasks, we should - in theory - be able to more easily reason about what is happening at any particular stage of the command sequence.

Commangular and the UpdateChosenHoursEvent

To help retain some semblence of sanity, and to avoid one crazy monolithic top-to-bottom function, I broke the concept of updating a user's chosen hours into three sub tasks.

Updating the user's chosen hours is therefore:

  1. Save the selected hours to local storage
  2. Prepare the selected hours for the notification service
  3. Updating the notification schedule with these selected hours

From the Ionic point of view, we just choose the Twitter hours we are interested in, but behind the scenes, quite a few things occur.

In this video, we are focussing on the first of the three tasks. In the next two videos, we will cover each of the following tasks in more detail.

The idea is not to show my way as a definitive example of perfect coding - far from it.

Instead, I am hoping to show how I think about application processes, and you might compose large applications from smaller pieces.

Getting Started

Installing Commangular is done using Bower.

bower install commangular

Not too sure why it's not on NPM, but there you go.

Follow the instructions to get Commangular into your project. I won't be focussing on that part here, but it's largely just copy / paste.

There are three parts I am concerned with. Two are code related, and one is design.

Design

Firstly, we need to think about what information our command needs.

In my case, when a user of the app clicks / toggles one of the available hours, that particular hour is sent to my Commangular command sequence.

What data you send is entirely up to you. To begin with, I would suggest keeping things simple - send the least amount of data you can get away with. And if you can send the data in the exact format that your commands / services expect, then all the better.

Thinking about the data structures at this point can help structure large parts of your application.

Of course, this largely depends on the current status of your application / code base. But if at all possible, design your JSON structures in advance, then make the code fulfill those structures, as the opposite of that is - I find - usually a lot more stressful.

Code

Providing you have thought about the shape / structure of the data you are sending, the next steps are to structure and name the command(s), and then actually trigger them.

If you haven't thought about the design at this stage, it can feel a little like Catch 22 / the Chicken and the Egg. You must start somewhere... but where!?

Let's crack on as though we have figured that bit out.

First, we must define our top level command:

// www/app/modules/core/config/update-chosen-hours.command.config.js
'use strict';

angular.module('core')

    .config(function($commangularProvider) {

        $commangularProvider
            .mapTo('UpdateChosenHoursEvent')
                .asSequence()
                    .add('SaveSelectedHoursToStorageCommand')
                    .add('PrepareHoursForNotificationServiceCommand')
                    .add('UpdateNotificationScheduleCommand')
        ;
    })
;

Here, UpdateChosenHoursEvent is the command we will dispatch from our Controller whenever a user toggles an available TweetHour.

Each subsequent step inside our sequence will complete before moving on to the next.

I will confess, when I first wrote this top level command, my sequence only had two steps. It was only further on that I realised that the data I was passing in would not quite fit the format that the scheduler expected. If only I had followed my own advice :)

Once you know what each command should do, you can then go ahead and define them.

The commands themselves are likely quite lightweight. I found mine did very little - instead, deferring all the hard work to Services.

// www/app/modules/core/commands/save-selected-hours-to-storage.command.js
'use strict';

angular.module('core');

    commangular.create('SaveSelectedHoursToStorageCommand', [
        'TweetHoursStorage',
        'event',
        function(TweetHoursStorage, event) {
            return {
                execute: function() {
                    TweetHoursStorage.update(event.id, event.selected);

                    return TweetHoursStorage.getAll();
                }
            }

    }], { resultKey : 'SaveSelectedHoursToStorageCommand_result' })
;

Commangular follows a convention that may not initially jump out at you.

To make it more visible, let's first see how we can trigger this command:

// www/app/modules/core/controllers/day-view.controller.js
angular.module('core')
    .controller('DayViewController', [
        '$scope',
        '$commangular',
        function($scope, $commangular) {
            $scope.toggleReminder = function(event) {
                $timeout(function() { // using $timeout due to a bug in ionic where otherwise the value is inversed
                    $commangular.dispatch('UpdateChosenHoursEvent', { event: event });
                }, 0);
            };
    }])
;

I have removed all the noise here, leaving only the relevant function call from our Controller.

All is triggered by :

$commangular.dispatch('UpdateChosenHoursEvent', { event: event });

$commangular is available through Angular's dependency injection.

'UpdateChosenHoursEvent' is the name of our top level command.

{ event: event } is a JavaScript object, with one key - 'event', and on to that key we are putting the TweetHour event value that was toggled (also a JavaScript object). The naming is not so good here, which is my fault.

The object holding our event is then available inside our Commangular top level command, and that passes it to the first sub-task in the sequence:

    commangular.create('SaveSelectedHoursToStorageCommand', [
        'TweetHoursStorage',
        'event',
        function(TweetHoursStorage, event) {

Commangular always uses the last parameter in the function call to pass data - whether from the top level command, or from this current command to the next one in the sequence.

If you are wondering why we specify the 'event' twice, then be sure to read up on Angular and minification (ctrl+f - 'A note on minification').

Passing the Result

As you will see in the video, the TweetHoursStorage service goes about the dirty work of making sure our data is properly stored on to the device.

How that happens is really not the concern of Commangular in general, nor this sub-task in particular.

But what we do care about is passing the result from this command to the next command in the sequence.

We can do this by using the resultKey key, of the third parameter in our commangular.create function call.

This is something that I used to find very strange about JavaScript, so let's look at this very quickly:

Stealing directly from the documentation:

//Command that returns the result
commangular.create('Command1',function() {
  return {
        execute: function() {
            return "This will be injected on the next command"        
        }
      }
  }
}],{resultKey:'theResult'}); //instructing commangular to keep that result.

I never used to find this immediately obvious that this is all one function call.

The signature of this function is:

commangular.create('commandName', functionToRun, objectContainingResult);

The return value of the functionToRun becomes the value on our resultKey in objectContainingResult.

If you've spent your entire development life in PHP, things like that are very odd at first, but very cool all the same.

Again, just to make things confusing - we give a name to the thing we are returning, but this isn't the same as the result itself.

From the docs example, we return {resultKey:'theResult'}.

In this case, 'theResult' is the name of the parameter we would use as the last parameter in the next command in our sequence. If that sounds confusing, hopefully the next step should explain it a little better.

So our next command / sub-task in our commangular sequence might be:

//Command that returns the result
commangular.create('OurNextCommand',['theResult', function(theResult) {
  return {
        execute: function() {
            return "This return value wouldn't be available as we aren't using the returnKey from here.";      
        }
      }
  }
}]);

These command steps are continued until you have no further data to return, at which point, your command sequence should be completed.

Episodes