Search code examples
javascriptangularjsangularjs-watch

$watch an Async Service variable doesn't work


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;
}])

Solution

  • 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)!