1

Hi I spent the last two days trying to make a view depending upon the response of an async http service. But it doesn't work for me. Can any one help me out?

Here there is the code for the basic idea, but the original one is a bit different:

var myApp = angular.module('app', []);

myApp.directive('dashboard',['service1',function(service1){
    return {
            templateUrl: 'sometemplate.html',
            controller: function ($scope) {

                $scope.data = {};  
                service1.get('keyXXX');
                $scope.$watch(service1.data['keyXXX'],function(n,o){
                    //how to make this work???
                    //I always get null or it would not get executed at all
                });

                }
            }
}])
.service('service1',['someAsyncservice',function(someAsyncservice){

        var _s={
            data:{},
            get:function(key){              
                if(this.data[key]==undefined)
                {
                    this.data[key]={status:"loading",data:{}};
                }
                someAsyncservice().then(function(result){
                    _s[key].data=result;
                    _s[key].status="success";
                });
            }
        }
        return _s;
}])
Gargaroz
  • 313
  • 9
  • 28

2 Answers2

1

update:

This is an extended example, using mostly your original code, showing how to completely go without $apply or $watch. Simply by using $q you can bypass most of the problems the first two functions could generate:

(function (app, ng) {
  'use strict';

  app.controller('TestCtrl', ['$scope', 'RefreshViewService', function ($scope, RefreshViewService) {
    $scope.key = 'key_A';

    $scope.refresh = function refresh(key) {
      $scope.busy = true;

      RefreshViewService.refresh(key).then(function () {
        $scope.busy = false;
      });
    };
  }]);

  app.directive('dashboard', ['service1', function(service1) {
    return {
      template: '<pre><strong>{{ key }}</strong>: {{ data|json }}</pre>',
      scope: {
        key: '@'
      },
      controller: function ($scope) {
        // use currently provided data
        $scope.data = service1.get($scope.key);

        // load data
        service1.load($scope.key).then(function (response) {
          $scope.data = response;
        });
      }
    };
  }]);

  app.service('service1', ['$q', 'someAsyncService', function($q, someAsyncService){
    var data = {}, loading = {};

    return {
      /**
       * returns the currently available data for `key`
       *
       * @param key
       * @returns {*}
       */
      get: function (key) {
        if(data[key] === undefined) {
          data[key] = {
            status: 'loading',
            data:   {}
          };
        }

        return data[key];
      },

      /**
       * async load data for `key`
       *
       * @param key
       * @returns {*}
       */
      load: function(key){
        // clear previous data
        if (loading[key] === undefined) {
          data[key].status = 'loading';
          data[key].data   = {};
        }

        return $q(function (resolve, reject) {
          // only run if not already loading
          if (loading[key] === undefined) {
            loading[key] = someAsyncService(key).then(function(result) {
              data[key].status = 'success';
              data[key].data   = result;

              delete loading[key];

              resolve(data[key]);
            });

          }

          return loading;
        });
      }
    };
  }]);

  /**
   * mock refresh service
   */
  app.service('RefreshViewService', ['service1', function(service1) {
    return {
      refresh: function (key) {
        return service1.load(key);
      }
    };
  }]);

  /**
   * mock async service
   */
  app.service('someAsyncService', ['$q', '$timeout', function ($q, $timeout) {
    return function(key) {
      return $q(function (resolve, reject) {
        $timeout(function () {
          resolve({ 'requested key': key, 'foo': Math.floor(Math.random() * 100) });
        }, 500 + Math.random() * 3000);
      });
    };
  }]);
})(angular.module('app', []), angular);
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>

<div data-ng-app="app" class="container">
  <div data-ng-controller="TestCtrl">
    <div class="radio">
      <label>
        <input type="radio" data-ng-model="key" data-ng-disabled="busy" value="key_A"> key A
      </label>

      <label>
        <input type="radio" data-ng-model="key" data-ng-disabled="busy" value="key_B"> key B
      </label>
    </div>

    <button class="btn btn-primary" data-ng-click="refresh(key)" data-ng-disabled="busy">Refresh</button>
    <span data-ng-show="busy">loading...</span>
  </div>

  <hr>

  <dashboard key="key_A"></dashboard>
  <dashboard key="key_A"></dashboard>
  <dashboard key="key_B"></dashboard>

previous answer:

Though there are probably other (better) ways, simply wrap your watched value in a function. E.g.:

$watch(function() { return service1.data['keyXXX']; }, ...

But note, that this will only work if someAsyncservice actually triggers the digest cycle (e.g. by using $q and similar)!

Yoshi
  • 54,081
  • 14
  • 89
  • 103
  • I have tried adding $rootScope.$apply() in someAsyncservice when i get response from $http service but some of the times it throws an error that you are already in digest loop. Any idea? – Muhammad Wasim Jun 09 '15 at 11:34
  • @MuhammadWasim My suggestion would be to completely go without `$apply` and `$watch`. Correctly implemented you should never ever have to use both methods. – Yoshi Jun 09 '15 at 12:56
  • Brother thank you so much. But see, suppose I have a RefreshViewService and now it calls only service1.load("key_a"). then how this will effect my actual view? I want this to change my views. Reson behind using watch was to create another refresh service which will be called dynamically to update views of dashboards. Just to let you know, all of these dasboards are also comming from some database schema, application has only one ui state, all other states and there views gets loaded dynamically. – Muhammad Wasim Jun 09 '15 at 15:55
  • `$q` will internally trigger the digest cycle. So if you update scope values inside the resolve/reject callbacks, those changes will automatically be reflected on the view. If further, you obey the [dot-rule](https://stackoverflow.com/questions/17606936/angularjs-dot-in-ng-model) you should not have any problems. If that's not what you meant, please provide a short example so I can have a look. – Yoshi Jun 09 '15 at 16:03
  • ok brother, Lets assume the code you posted above, Now I add another service RefreshViewService in your code. `angular.service('RefreshViewService',['service1',function(service1){this.refresh(key){service1.load(key);}}])` Now if I call RefreshViewService.refresh('key_a'), it should update all views attached with service1.data['key_a'] automagically. How can i achieve this now? – Muhammad Wasim Jun 09 '15 at 16:17
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/80092/discussion-between-muhammad-wasim-and-yoshi). – Muhammad Wasim Jun 09 '15 at 16:20
0

I didn't actually test it but try:

$scope.$watch(function(){return service1.data['keyXXX']},function(n,o){
                    //how to make this work???
                    //I always get null or it would not get executed at all
                });
Tomas
  • 2,676
  • 5
  • 41
  • 51