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

pbk*_*hrv 6 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);
    }
  }
}]);
Run Code Online (Sandbox Code Playgroud)

以下是在解析promise并将结果存储在$scope.friends以下内容后使用该数据的视图:

<div ng-repeat='friend in friends'>
  {{friend.firstName}} {{friend.lastName}}
</div>
Run Code Online (Sandbox Code Playgroud)

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

选项1:使用通过ng-route加载的数据的控制器 resolve

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

路线部分:

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

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

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

问题

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

use*_*490 4

单元测试

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

$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;
    }]);
Run Code Online (Sandbox Code Playgroud)

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

var friendListMock = [
  // ...
];

$controller('FriendListCtrl', {
  $scope: scope,
  friendList: friendListMock
})
Run Code Online (Sandbox Code Playgroud)

选项 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

usualStore您可以通过在注入后从控制器加载来解决这个问题,但是当它可以在ui-router中通过依赖解析customer干净地完成时,为什么还要麻烦呢:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)