angular directive – correlative requests-Collection of common programming errors

I’ll float this Reverse-Jeopardy style (A question in the form of an answer). I’ve been mulling over a solution to this problem I saw recently. It clearly works, but it has some behavioral traits that I was first tempted to label as “wrong”. On deeper reflection, I realized that those traits may be desirable in some very specific scenarios.

I would certainly not pitch this as a general solution to use each time you run into a need to bind data that is returned asynchronously. I present it to highlight the fact that the questions posed by this scenario have multiple potential answers. In some cases, there may be a genuine business logic need to block the UI rendering until the service call returns. In other cases, keeping the UI live and responsive for unrelated work may be more appropriate.

For example, in the case of an order processing system, I might very well want to block the client thread from interacting with view elements until the result of a sales transaction was known. The same could not be said of something like a web-based spreadsheet application with server-side formula calculations.

I think its a wonderful thing that both asynchronous and synchronous modes of addressing this need can co-exist. That is to say, returning a promise object does not obligate a client to use it any more than placing the return values in a scope obligates a client to watch them.

What follows demonstrates a way of handling this requirement synchronously alongside the previously explored asynchronous watch( ) style.:

var servicesModule = servicesModule || angular.module('myModule', []);

servicesModule.factory('thingClient', 
    ['$http', '$q', function( $http, $q ) {
        return new ThingClient($http, $q);
    }]
);

function ThingClient($http, $q) {
    function callService(scopeObject) {
        var defer = $q.defer();
        var promise = defer.promise;
        $http({
            method: 'POST',
            url: 'http://at/some/url/',
            data: { x: 'y', y: 'z', z: 'x', one: 1, two: 2, three: 3},
            cache: false
        }, function(data) {
            scopeObject.data = data;
            defer.resolve(data);
        }, function() {
            scopeObject.data = null;
            defer.resolve(null)
        });

        return promise;
    }
}

client-service.js

function ConsumingController( $scope, thingClient ) {
    // Be careful to use an object (so you have data wrapped such that it
    // is eligible for prototypical inheritance and call by reference without
    // passing the scope itself).  Leave the 'data' element undefined so you
    // can trigger the transition from undefined -> null in the failure case
    // and not just the on-success case. 
    $scope.dto = { data: undefined };
    var promise = thingClient.callService(scope.dto);

    // Asynchronous strategy
    $scope.$watch('offer.myId', function (newValue, oldValue) {
        if( newValue == null ) {
            // Fail!
        } else {
            // Succeed!
        }
    }

    // Synchronous strategy
    if( promise.then( 
        function(data) { 
            if( data == null ) { 
                // Fail
            } else {
                // Succeed
            }
        }
    }
}

consuming-controller.js