通过AngularJS中的服务将数据加载到控制器中的方法

时间:2014-12-18 21:15:43

标签: angularjs angularjs-service angularjs-routing

我有一个使用$ http加载数据的服务并返回一个promise(简化为简洁):

angular.module('myApp').factory('DataService', ['$http', function($http) {
  function unwrapFriendList(data) {
    ...
    return unwrappedFriendList;
  }

  return {
    getFriendList: function() {
      return $http.get('/api/friends').then(unwrapFriendList);
    }
  }
}]);

这是一个使用该数据的视图,在解析了promise并将结果存储在$scope.friends之后:

<div ng-repeat='friend in friends'>
  {{friend.firstName}} {{friend.lastName}}
</div>

在将数据加载到控制器中时,我遇到了几种方法。

选项1:使用通过ng-route resolve

加载的数据的控制器
angular.module('myApp').controller('FriendListCtrl', ['$scope', 'friendList', function($scope, friendList) {
  $scope.friends = friendList;
}]);

路线部分:

angular.module('myApp', ...).config(function($routeProvider) {
  $routeProvider
    .when('/friends', {
      templateUrl: 'views/friends.html',
      controller: 'FriendListCtrl',
      resolve: {
        friendList: ['DataService', function(DataService) {
          return DataService.getFriendList();
        }]
      }
    })
    ...
});

选项2:自行触发数据加载的控制器

angular.module('myApp').controller('FriendListCtrl', ['$scope', 'DataService', function($scope, DataService) {
  DataService.getFriendList().then(function(friendList) {
    $scope.friends = friendList;
  });
}]);

问题

  • 还有其他常用的方法吗?如果是这样,请用代码示例说明。
  • 每种方法有哪些限制?
  • 每种方法的优点是什么?
  • 我应该在什么情况下使用每种方法?

2 个答案:

答案 0 :(得分:4)

单元测试

选项1: 使用resolve使控制器单元测试中的模拟依赖变得非常简单。在您的第一个选项中:

$routeProvider
  .when('/friends', {
    templateUrl: 'views/friends.html',
    controller: 'FriendListCtrl',
    resolve: {
      friendList: ['DataService', function(DataService) {
        return DataService.getFriendList();
      }]
    }
  })

angular.module('myApp')
  .controller('FriendListCtrl', ['$scope', 'friendList',
    function($scope, friendList) {
      $scope.friends = friendList;
    }]);

由于friendList被注入控制器,因此在测试中模拟它就像将普通对象传递给$controller服务一样简单:

var friendListMock = [
  // ...
];

$controller('FriendListCtrl', {
  $scope: scope,
  friendList: friendListMock
})

选项2: 您不能使用第二个选项执行此操作,并且必须监视/存根DataService。由于第二个选项中的数据数据请求在控制器创建时立即被调用,因此一旦您开始执行多个,有条件或依赖(稍后会有更多)数据请求,测试将变得非常混乱。

查看初始化

选项1: 在所有结算完成之前,解决方案会阻止视图初始化。这意味着视图中任何期望数据(包含指令)的内容都会立即生成。

选项2: 如果数据请求发生在控制器中,则视图将显示,但在请求完成之前将不会有任何数据(将来会在某个未知点)。这类似于一些没有风格的内容,可能会很刺耳,但可以解决。

当您的视图中的组件期望数据并且未提供数据时,会出现真正的复杂情况,因为它们仍在被检索。然后,您必须通过强制每个组件等待或延迟初始化一段未知的时间来解决这个问题,或者在初始化之前让它们$watch一些任意变量。非常凌乱。

首选解析

虽然您可以在控制器中执行初始数据加载,但已经可以更清晰,更具说明性的方式进行解析。

然而,默认的ngRoute解析器缺少一些关键功能,最值得注意的是依赖解析。如果您想向控制器提供2个数据,客户以及他们常用商店的详细信息,该怎么办?这对于ngRoute来说并不容易:

resolve: {
  customer: function($routeParams, CustomerService) {
    return CustomerService.get($routeParams.customerId);
  },
  usualStore: function(StoreService) {
    // can't access 'customer' object here, so can't get their usual store
    var storeId = ...;
    return StoreService.get(storeId);
  }
}

你可以通过在注入usualStore之后从控制器加载customer来解决这个问题,但是为什么在ui-router中使用依赖解析可以干净地完成它时会烦恼:

resolve: {
  customer: function($stateParams, CustomerService) {
    return CustomerService.get($stateParams.customerId);
  },
  usualStore: function(StoreService, customer) {
    // this depends on the 'customer' resolve above
    return StoreService.get(customer.usualStoreId);
  }
}

答案 1 :(得分:2)

  

还有其他常用的方法吗?

取决于,如果您有其他域上的数据,并且可能需要时间加载,因此您无法显示该视图,直到收到该视图,因此您将首先解决问题。

  

每种方法有哪些限制?

使用第一个模式的限制可以解决的问题是,在加载所有数据之前页面不会显示任何内容

第二个限制是数据可能需要更长的时间才能收到,如果你没有用css解决它,你的视图就像“{{}}”

  

每种方法的优点是什么?

第一个的好处就是我之前说过的,你将解析数据并确保它在呈现视图之前存在

  

在什么情况下我应该使用每种方法?

如果我们需要在控制器初始化和渲染视图之前加载一些加载的数据,那么解析非常有用

第二个问题是,当你没有检查时,这些加载问题是预期的,数据在你手中!