Search code examples
javascriptangularjsangular-promiseangular-directiveangular-controller

Why does AngularJS directive work with hardcoded values but fail with http request/response?


Angular Newbie Question:

I have a simple AngularJS test application that shows how a controller and a directive work together. The controller sets some hardcoded values aB and aC on the scope and the directive then displays these values in HTML. It works. Here is the JSFiddle. The code is below. When you run it, you can see the console output is as expected:

JS line #63: this.aB =  null
JS line #64: this.aC =  Goodbye
JS line #63: this.aB =  Hello
JS line #63: this.aC =  World

However, when I change the hardcoded values to ones that are retrieved from a test API, it fails. The console output is as follows:

JS line #63: this.aB =  null
JS line #64: this.aC =  Goodbye
JS line #63: this.aB =  undefined
JS line #63: this.aC =  undefined

The only change I made (seen here in this new JSFiddle) was in the controller's myFunc function: I replaced the hardcoded values with the following:

  response = $http.post('http://jsonplaceholder.typicode.com/posts',
    {
      'XXXXX': 'YYYYYYY'
    }
  )    
  self.scopeAB = response.id;
  self.scopeAC = response.id;

I have tested the API's response via curl and it is working fine. So why does the directive report the values of aB and aC as undefined? How do I solve this problem? I can tell that it has to do with the asynchronous nature of the HTTP call. But I don't know how to make this work correctly.

HTML:

<body ng-app="myApp">
  <div ng-controller="MyCtrl as ctrl">
    <div ng-view></div>
    <ul>
      <li>{{1+1}}</li>
      <li><my-directive a-b="null" a-c="'Goodbye'"></my-directive></li>
      <li><my-directive a-b="ctrl.scopeAB" a-c="ctrl.scopeAC"></my-directive></li>
      ab = {{ctrl.scopeAB}}, ac = {{ctrl.scopeAC}}
    </ul>
  </div>
</body>

Working Javascript:

myApp = angular.module('myApp',[]);
myApp.directive('myDirective',function(){
    return {
      restrict:'E',
      scope: {
        aB: '=',
        aC: '='
      },
      controller: 'DirectiveCtrl',
      controllerAs: 'dirCtrl',
      bindToController: true,
      template: 'aB={{dirCtrl.aB}} aC={{dirCtrl.aC}} <input ng-model="dirCtrl.aB" />'
    };
  }
);

myApp.controller('DirectiveCtrl', function(){
    var self = this;
    console.log('this.aB = ', self.aB);
    console.log('this.aC = ', self.aC);
})

myApp.controller('MyCtrl', function() {
    var self = this;
    self.myFunc = function() {
      self.scopeAB = 'Hello';
      self.scopeAC = 'World';
    }();
  }
);

UPDATE: Claies suggested I use this JSFiddle. But it won't work for me because I absolutely need the values of aB and aC to be accessible in the directive's controller. I need to vary the template based on their values. This JS Fiddle seems to show them as always undefined in there.


Solution

  • Claies suggested I use this JSFiddle. But it won't work for me because I absolutely need the values of aB and aC to be accessible in the directive's controller. I need to vary the template based on their values. This JS Fiddle seems to show them as always undefined in there.

    If you use @Claies methodology, you need to put a $watch on the object that fires when the $http request resolves.

    myApp.controller('DirectiveCtrl', function($scope){
        var self = this;
        $scope.$watch(function() {return self.scopeObject}, function (objVal) {
            console.log("watch fired");
            console.log('objVal.aB = ', objVal.aB);
            console.log('objVal.aC = ', objVal.aC);    
        },true);
    
    });
    

    The DEMO on JSFiddle.

    Frankly I think you are better off following the advice from @jumbopap. Use the httpPromise and the .then method and retrieve the data from the onFulfilled function.