angular directive - correlative requests

I created a custom directive for the following widget attribute:

Basically the directive just creates some template code and puts the html inside the widget-tag. Here's the directive:

skdApp.directive("widget", function() {
  return {
    restrict: 'A',
    template: htmlTemplate,
    link: function($scope, element, attri) {
      /*
       * setup event handler and twitter bootstrap components here
       */
    }
  }

The htmlTemplate is basic html code which also uses custom directives (e.g. the <seal> tag):

var htmlTemplate = '<div ng-controller="OfferCtrl">' +
                     '<div class="container">' +
                       <seal></seal>' +
                       '...'+
                     '</div>' +
                   '</div>';

In my Controller I first request some data to display in <div widget>. I have an 'Offer' service that capsules all the logic to request data from the server. The method I'm using is called Offer.query().

skdApp.controller('OfferCtrl', function OfferCtrl ($scope, Offer) {
  var o = Offer.query({id: 1}, function (response) {
    $scope.offer = response;
  });
});

In the response handler I'm binding the result to the scope. The problem I'm facing now is that the directive also requests data but this request depends on the received data from Offer.query(). I.e. the response from Offer.query() returns an ID (let's call it myID) which is required by the seal directive to request more data. Therefore I simply put all my logic in the callback Offer.query callback function. This doesn't seem to be the best way to do.

So I was thinking of moving this part to the link function of the directive:

skdApp.directive("seal", function() {

  var sealHTML = '<div>{{offer.data.foobar}}</div>';

  return {
    restrict: 'E',
    template: sealHTML,
    link: function($scope, element, attrs) {

      $scope.$watch('offer.myId', function (newValue, oldValue) {

       if (typeof newValue !== "undefined") {

         /* request more data with myId 
          * and bind the result to offer.data
          */    

       }

      });

    }
});

Is this approach 'angular'-compliant or is there some other better way (in terms of structure) to do this in angular? Feedback highly appreciated!

You are able to watch offer.myId in child directive because both parent(widget) and child(seal) shares the same scope.By default directive does not create a new scope.

I think you broadcast custom event to notify the child directive and isolate scope if required to avoid scope clobbering.

http://docs.angularjs.org/api/ng.$rootScope.Scope

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