$watch an Async Service variable doesn't work

590 views Asked by At

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

There are 2 answers

6
Yoshi On BEST ANSWER

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

0
Tomas On

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