使用工厂来暴露一个简单的API。

6
我正在尝试编写一个工厂,该工厂公开了一个简单的用户API。我对AngularJS还不太熟悉,对于工厂以及如何使用它们有些困惑。我看到了其他主题,但没有一个能很好地匹配我的用例。
为了简化起见,我想实现的唯一功能是获取所有用户并通过注入的工厂将它们传递给控制器。
我将用户存储在json文件中(现在我只想读取该文件,而不修改数据)。 users.json:
[
{
    "id": 1,
    "name": "user1",
    "email": "a@b.c"
},
{
    "id": 2,
    "name": "user2",
    "email": "b@b.c"
}
]

我要编写的工厂应该是这样的:

UsersFactory:

app.factory('usersFactory', ['$http', function ($http) {
    return {
        getAllUsers: function() {
            return $http.get('users.json').then(
                function(result) {
                    return result.data;
                },
                function(error) {
                    console.log(error);
                }
            );
        }
    };
}]);

最后,控制器调用将如下所示: UsersController
app.controller('UsersCtrl', ['$scope', 'usersFactory', function($scope, usersFactory){
    usersFactory.getAllUsers().then(function (result) {
        $scope.users = result;
    });
}]);

我在网上搜索了一下,似乎这样使用工厂方法并不是一个好的实践方式。如果我想实现更多的功能,例如将新用户添加/删除到/从数据源中,或者以某种方式在工厂内部存储数组,那么这并不是正确的方法。我看到有些地方使用工厂的方式是像new UsersFactory()这样的。

在尝试消耗API时,正确使用工厂方法应该怎么做呢?

是否可以使用包含$http.get()结果的对象初始化工厂,然后在控制器中这样使用它?

var usersFactory = new UsersFactory(); // at this point the factory should already contain the data consumed by the API
usersFactory.someMoreFunctionality();   

我一直使用$resource来调用API,因为它在处理RESTful API时可以为你做很多工作。但是,在你的情况下,由于你只是在处理一个普通的JSON文件,所以简单的HTTP应该就足够了。你并没有在调用API。 - Kevin B
你是要进行多次 getAllUsers() 调用,还是只有在应用程序的生命周期中进行一次调用? - allienx
@KevinB 我只是为了简单起见使用普通的 JSON,但实际应用中会使用 RESTful API,但从客户端的角度来看,这并不真正重要。 - Lior Erez
@KevinB 或许你是对的。如我所说,我刚开始使用AngularJS,目前所能依赖的只有其他人的经验。我并不是将所有事情都视为理所当然,这也是我发布这个问题的原因。 - Lior Erez
如果你是在讨论让工厂返回一个构造器而不是对象的话,那这是一个长期争议的话题,并非直接与 Angular 相关。我倾向于使用工厂而非构造器。 - Kevin B
显示剩余3条评论
5个回答

2

我认为你的工厂没有任何问题。如果我理解正确,您想添加功能。只需进行一些小改动即可实现这一点。以下是我的建议(请注意,调用getAllUsers会清除任何更改):

app.factory('usersFactory', ['$http', function ($http) {
  var users = [];
  return {
      getAllUsers: function() {
          return $http.get('users.json').then(
              function(result) {
                  users = result.data;
                  return users;
              },
              function(error) {
                  users = [];
                  console.log(error);
              }
          );
      },
      add: function(user) {
          users.push(user);
      },
      remove: function(user) {
          for(var i = 0; i < users.length; i++) {
              if(users[i].id === user.id) { // use whatever you want to determine equality
                  users.splice(i, 1);
                  return;
              }
          }
      }
  };
}]);

通常,addremove 调用将作为 http 请求 (但这不是你在问题中所问的内容)。如果请求成功,您就知道UI可以从视图中添加/删除用户。

谢谢您的回答,有没有一种方法可以使用 $http.get() 获取的数据初始化返回的对象,然后只使用 add()remove() 函数? - Lior Erez
现在你开始进入resolve了。你使用ngRoute还是AngularUI Router - Andy Gaskell
我正在使用AngularUI Router。 - Lior Erez

1

我喜欢我的API工厂返回对象而不是仅返回一个端点:

app.factory('usersFactory', ['$http', function ($http) {
  return {
    getAllUsers: getAllUsers,
    getUser: getUser,
    updateUser: updateUser
  };

  function getAllUsers() {
    return $http.get('users.json');
  }

  function getUser() {
    ...
  }

  function updateUser() {
    ...
  }
}]);

这样,如果您有任何其他与用户相关的端点,您可以在一个工厂中使用它们。此外,我的偏好是只返回$http承诺目录,并在控制器或您注入工厂的任何地方使用then()


1

我真的很喜欢路由解析承诺。这是John Papa的例子。之后我会解释如何将其应用到你正在做的事情中:

// route-config.js
angular
    .module('app')
    .config(config);

function config($routeProvider) {
    $routeProvider
        .when('/avengers', {
            templateUrl: 'avengers.html',
            controller: 'Avengers',
            controllerAs: 'vm',
            resolve: {
                moviesPrepService: moviesPrepService
            }
        });
}

function moviesPrepService(movieService) {
    return movieService.getMovies();
}

// avengers.js
angular
    .module('app')
    .controller('Avengers', Avengers);

Avengers.$inject = ['moviesPrepService'];
function Avengers(moviesPrepService) {
      var vm = this;
      vm.movies = moviesPrepService.movies;
}

基本上,在您的路由加载之前,您会获得所需的请求数据(在您的情况下,是您的“users”JSON)。从这里开始,您有几个选项... 您可以将所有数据存储在一个Users工厂中(顺便说一句,您的工厂看起来不错),然后在控制器中,只需调用Users.getAll,它可以返回用户数组。 或者,您可以从路由解析承诺中传递users,就像John Papa在他的示例中所做的那样。我无法像他写的文章那样完美地做到这一点,因此我真诚地建议阅读它。在我看来,这是一种非常优雅的方法。


用户更改状态(通过链接点击或某个按钮触发状态更改)和实际发送到新状态之间可能会出现一些不良的等待时间。在我看来,更好的用户体验是立即将其转换到新状态,并在控制器获取数据并准备就绪后再呈现它。 - JesseDahl

0

我通常使用类似这样的工厂:

.factory('usersFactory', ['$resource',
  function($resource){
    return $resource('http://someresource.com/users.json', {}, {
        query: {
            method:'GET',
            isArray:true
            }
        })

  }])

你可以这样调用:

usersFactory.query();

由于这是一个承诺,您仍然可以使用.then方法


我真的很推荐你也跟着这个教程走一遍:angular - Antony Smith

0

$http 是一个 Promise,这意味着您必须检查您的 get 调用是否成功。

所以尝试在您的控制器中实现这种类型的架构。

$http.get('users.json')
  .success(function(response) {
    // if the call succeed
       $scope.users = result;
  })
  .error(function(){console.log("error");})
  .then(function(){
   //anything you want to do after the call
   });

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接