我如何创建一个只查询数据库一次但可以被许多控制器使用的工厂?

时间:2013-11-19 15:46:55

标签: angularjs

我有一个'messages'工厂,它将在我的数据库中查询消息列表。

我正在两个不同的地方使用消息列表。一旦添加消息计数指示器,然后一次显示消息列表。由于我将服务注入两个不同的控制器,似乎它正在创建我的工厂的两个实例,并为列表命中数据库两次。

我如何设置只需要一次列表,并在两个控制器中使用列表进行显示和计数?

我的工厂看起来像这样:

myApp.factory('messagesService', [
    '$rootScope',
    function($rootScope) {
        var messages = [];

        function query() {
            // Would actually hit the database asyncronously
            messages = ['one', 'two', 'three', 'four'];
            console.log('query');
            $rootScope.$emit('messages.update');
        }

        function all() {
            return messages;
        }

        return {
            query: query,
            all: all
        }
    }
]);

我的控制器正在使用这样的块来监视更改:

$rootScope.$on('messages.update', function() {
    $scope.messagesCount = messagesService.all().length;
});

但这意味着我需要在每个控制器中使用messagesService.query();才能使事情变得可靠。

所以这里有一些jsFiddle示例,因为我现在有了一些东西:

  1. 不起作用(仅更新标题):http://jsfiddle.net/TSLfc/1/
  2. 工作但如果我没有加载仪表板控制器就会中断: http://jsfiddle.net/TSLfc/2/
  3. 每次都有效,但查询服务器两次: http://jsfiddle.net/TSLfc/3/
  4. 有没有更好的方法来组织我的代码?我应该将消息工厂构建到它自己的完整模块中吗?

4 个答案:

答案 0 :(得分:3)

这里( Plunkr )就是这样的:

我已经回过头来修改我之前的答案,用我们在下面的评论中讨论的内容进行更新,并使用promises而不是timeout作为我之前展示的异步模拟(参见修订历史以供参考)

我还删除了不需要从服务对象返回控制器的每个变量/函数,如果它不需要通过控制器访问而不是它不需要被包含在返回的对象中。

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

myApp.factory('messagesService', [
    '$q',
    '$rootScope',
    '$http',

function ($q, $rootScope, $http) {
    var mService = {};

    mService.messages = [];
    var queryInit = false;

    // We don't need to access this function in the controller
    // So I am not going to attach to the returned object
    var getMessages = function () {
        // Stops each controller from getting messages when loaded
        if (!queryInit) {
            queryInit = true;
            // Using the $q promise library we use 'then()' to handle
            // What happens after the async call is returned
            // The first function parameter is the success/resolve callback
            // The second function parameter is the error/reject callback
            mService.query().then(function (successResults) {
                // Tell all of the controllers that the data has changed
                $rootScope.$broadcast('messages.update');
            }, function (errorResults) {
                console.error(errorResults);
            });
        }
    };

    // Used to force an update from the controller if needed.
    mService.query = function () {
        var deferred = $q.defer();
        $http.get('path/to/file.php')
            .success(function (data, status, headers, config) {
            // assign the returned values appropriately
            mService.messages = data;
            // this callback will be called asynchronously
            // when the response is available
            deferred.resolve(data);
        })
            .error(function (data, status, headers, config) {
            // called asynchronously if an error occurs
            // or server returns response with an error status.
            deferred.reject(data);
        });
        return deferred.promise;
    };

    mService.getCount = function () {
        return mService.messages.length;
    };

    mService.all = function () {
        return mService.messages;
    };

    // Initialize the messages
    // so we don't need to get the messages in each controller
    getMessages();

    return mService;
}]);

在你的html中,在你的第一个控制器上设置一个实例化工厂的init函数(ng-init="init()"):

<div ng-app="myApp">
    <div ng-controller="HeaderCtrl" class="header" ng-init="init()">
        Messages Count: {{ messageCount }}
    </div>

    <div ng-controller="DashboardCtrl" class="dashboard">
        <ul ng-repeat="message in messages">
            <li>{{ message }}</li>
        </ul>
        <button ng-click="getMessages()">Check for new messages.</button>
    </div>
</div>

在您的控制器中,您只有$rootScope.$on('messages.update' fn),您可以通过调用返回承诺的服务query()函数来手动调用:

myApp.controller('HeaderCtrl', [
    '$scope',
    '$rootScope',
    'messagesService',

function ($scope, $rootScope, messagesService) {
    $rootScope.$on('messages.update', function () {
        $scope.messageCount = messagesService.getCount();
    });

    // Manual call, if needed
    $scope.getMessageCount = function () {
        messagesService.query().then(function (successCallback) {
            $scope.messageCount = messagesService.getCount();
        });
    };
}]);

myApp.controller('DashboardCtrl', [
    '$scope',
    '$rootScope',
    'messagesService',

function ($scope, $rootScope, messagesService) {
    $rootScope.$on('messages.update', function () {
        $scope.messages = messagesService.all();
    });

    // Manual call, if needed
    $scope.getMessages = function () {
        messagesService.query().then(function (successCallback) {
            $scope.messages = messagesService.all();
            $rootScope.$broadcast('messages.update');
        });
    }
}]);

答案 1 :(得分:1)

您可以在cache:true请求中设置$http。有许多方法可以在角度内进行数据绑定,而无需使用您正在使用的$ broadcast方法。另请注意,范围内的$ broadcast将被所有后代范围接收,因此不需要为此目的注入$ rootSCope,可以监听$ scope。

这是控制器使用$ http承诺来检索数据的一种方法。我使用单击按钮来检索DashControl的数据,因此可以看到请求已缓存

myApp.factory('messagesService',function($http) {  

    return{
      query:function query(callback) {
          /* return promise of the request*/
          return $http.get('messages.json',{ cache:true}).then(function(res){
              /* resolve what data to return, can set additional properties of the service here if desired*/
              return res.data
          }).then(callback);         
      }
    }       
});

myApp.controller('HeaderCtrl',function($scope, messagesService) {

     messagesService.query(function(messages){
       $scope.messagesCount = messages.length;
     });

});

myApp.controller('DashboardCtrl', function($scope, messagesService) {

  /* use button click to load same data, note in console no http request made*/
  $scope.getMessages=function(){
      messagesService.query(function(messages){
       $scope.messages = messages;
     })
   }

});

基本上在这种情况下,无论控制器首先调用工厂服务,都会生成数据缓存

DEMO

答案 2 :(得分:0)

我会这样做:

myApp.factory('messagesService', function() {
        var expose = {
            messages: []
        };

        expose.query = function () {
            // Would actually hit the database asyncronously
            expose.messages = ['one', 'two', 'three', 'four'];
            console.log('query');
        };

        // Initialization
        expose.query();

        return expose;
    }
);

在您的控制器中:

$ scope.messagesCount = messagesService.messages.length;

答案 3 :(得分:0)

带有广播和预先打击数据库的模型对我来说很重要。

所以这里是代码,可以嵌入到服务中:

var sv = this;
var deferred = sv.$q.defer();
if (sv._running) { 
    return sv._running;
}
sv._running = deferred;

它基于重用承诺。要使它一次查询数据库 - 只是不要将sv._running设置为false,它将始终返回第一个获得的结果。