【问题标题】:Angular - Best practice for retrieving data from a Factory methodAngular - 从工厂方法中检索数据的最佳实践
【发布时间】:2016-05-03 20:12:31
【问题描述】:

我正在寻找有关从本地 JSON 文件中检索数据并处理响应的最佳方式的一些信息。浏览 Stack Overflow 后,我有一些复杂的想法,因为我看到了做同一件事的多种方法(尽管没有解释为什么一种可能或可能不是首选)。

基本上,我有一个 Angular 应用程序,它利用工厂从 JSON 文件中检索数据;然后我等待响应在我的控制器中解析,然后在我的 html 文件中使用它,类似于以下内容:

选项 1

工厂:

comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';

return {
 retrieveInfo: function() {
  return $http.get(retrievalFile);
 }
}

}]);

控制器:

comparison.controller('comparisonController', ['$scope', 'Info', function($scope, Info) {

Info.retrieveInfo().then(function(response) {
  $scope.info = response.data;
});

}]);

我的主要争论点是弄清楚何时最好等待响应解决,或者它是否重要。我正在考虑让工厂返回已履行的承诺,并等待控制器也检索数据。在我看来,最好将所有数据检索从控制器抽象到工厂中,但我不确定这是否延伸到等待工厂本身返回实际数据。考虑到这一点,我对是选择选项 1 还是选项 2 感到困惑,并且非常感谢更有经验/合格的开发人员提供一些反馈!

选项 2

工厂:

comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';

return {
  retrieveInfo: function() {
    return $http.get(retrievalFile).then(function(response) {
      return response.data;
    });
  }
}

}]);

控制器:

comparison.controller('comparisonController', ['$scope', 'Info', function($scope, Info) {

Info.retrieveInfo().then(function(response) {
  $scope.info = response;
});

}]);

提前感谢您的任何意见/建议!

【问题讨论】:

  • 并不总是最好的答案,但 Angular 的美妙之处在于它对如何为您的“模型”处理或维护数据保持冷静。我看到的最佳实践类似于选项 1。我认为从可读性的角度来看,它在服务、数据和控制器之间提供了很好的链接。然而,这只是我的意见:-D
  • 我会建议选项 1,因为那时您将对内部的所有方法都有命令,例如 successCallback 和 errorCallback。使用 promise 会更好(即 angular $q 服务)

标签: javascript angularjs json factory


【解决方案1】:

我会选择选项二,因为您的选项实际上几乎相同。但是让我们看看我们何时添加了一个模型结构,比如 Person 假设。

comparison.factory('Info', ['$http', function($http) {
var retrievalFile = 'retrievalFile.json';

return {
  retrieveInfo: function() {
    return $http.get(retrievalFile).then(function(response) {
      //we will return a Person...
      var data = response.data;
      return new Person(data.name, data.age, data.gender);
    });
  }
}

}]);

这真的很简单,但是如果您必须将更复杂的数据映射到对象模型中(您检索具有自己项目的人员列表......等等),那么当事情变得更加复杂时,您可能需要添加处理数据和模型之间映射的服务。好吧,您还有另一个服务DataMapper(示例),如果您选择第一个选项,则必须将DataMapper 注入您的控制器,并且您必须通过您的工厂提出请求,并将响应映射到注入的服务。然后你可能会说,我应该把所有这些代码都放在这里吗? ...好吧可能没有。

这是一个假设情况,重要的是您对构建代码的感觉如何,不会以您无法理解的方式构建它。最后看看这个:https://en.wikipedia.org/wiki/SOLID_(object-oriented_design) 并研究有关此原则的更多信息,但重点是 javascript。

【讨论】:

    【解决方案2】:

    这真的只是偏好。我喜欢从 API 的角度来考虑它。您要公开的 API 是什么?您是希望您的控制器接收整个响应,还是希望您的控制器只拥有响应包装的数据?如果您只打算使用 response.data,那么选项 2 效果很好,因为您无需处理任何事情,只需要处理您感兴趣的数据。

    一个很好的例子是我们刚刚在我工作的地方编写的应用程序。我们有两个应用程序:一个后端 API 和我们的前端 Angular 应用程序。我们在前端应用程序中创建了一个 API 包装服务。在服务本身中,我们为任何已记录错误代码的 API 端点放置一个.catch(我们使用 Swagger 来记录和定义我们的 API)。在那个.catch 中,我们处理这些错误代码并返回一个正确的错误。当我们的控制器/指令使用服务时,它们会返回一组更严格的数据。如果发生错误,那么 UI 通常可以安全地显示从包装服务发送的错误消息,而不必担心查看错误代码。

    同样,对于成功的响应,我们会执行您在选项 2 中所做的大部分工作。在许多情况下,我们会将数据细化到在实际应用中最有用的数据。通过这种方式,我们将大量数据搅动和格式化保留在服务中,而应用程序的其余部分要做的事情要少得多。例如,如果我们需要基于该数据创建一个对象,我们只需将对象返回到 Promise 链,这样控制器就不会到处这样做了。

    【讨论】:

      【解决方案3】:

      这取决于您的控制器期望什么以及您如何设置应用程序。一般来说,我总是选择第二种选择。这是因为我通常在所有 api 请求中都有全局错误或成功处理程序,并且我有一个共享的api service。如下所示。

      var app = angular.module('app', []);
      
      app.service('ApiService', ['$http', function($http) {
          var get = function(url, params) {
          $http.get(url, { params: params })
              .then(handleSuccess, handleError);
        };
      
        // handle your global errors here
        // implementation will vary based upon how you handle error
        var handleError = function(response) {
          return $q.reject(response);
        };
      
        // handle your success here
        // you can return response.data or response based upon what you want
        var handleSuccess = function(response) {
          return response.data;
        };
      }]);
      
      app.service('InfoService', ['ApiService', function(ApiService) {
          var retrieveInfo = function() {
          return ApiService.get(retrievalFile);
      
          /**
          // or return custom object that your controller is expecting
          return ApiService.get.then(function(data) {
            return new Person(data);
          });
          **//
        };
      
        // I prefer returning public functions this way
        // as I can just scroll down to the bottom of service 
        // to see all public functions at one place rather than
        // to scroll through the large file
        return { retrieveInfo: retrieveInfo };
      }]);
      
      app.controller('InfoController', ['InfoService', function(InfoService) {
        InfoService.retrieveInfo().then(function(info) {
          $scope.info = info;
        });
      }])
      

      或者,如果您使用的是路由器,您可以将数据解析到控制器中。 ngRouter 和 uiRouter 都支持解析:

      $stateProvider.state({
          name: 'info',
        url: '/info',
        controller: 'InfoController',
        template: 'some template',
        resolve: {
          // this injects a variable called info in your controller
          // with a resolved promise that you return here
          info: ['InfoService', function(InfoService) {
              return InfoService.retrieveInfo();
          }]
        }
      });
      
      // and your controller will be like
      // much cleaner right
      app.controller('InfoController', ['info', function(info) {
          $scope.info = info;
      }]);
      

      【讨论】:

      • 感谢您(以及其他所有回答的人),这绝对值得深思。我将考虑编写一个 API 服务,以便我的代码易于扩展。
      【解决方案4】:

      好问题。几点:

      1. 控制器应该以视图为中心而不是以数据为中心,因此您 想要从控制器中删除数据逻辑,而是让它专注 关于业务逻辑。
      2. 模型(MVC 中的 M)是应用程序的数据表示形式,并且 将容纳数据逻辑。在 Angular 情况下,这将是一项服务 或您正确指出的工厂类。为什么这么好 示例:

        2.1 AccountsController(可能注入了多个数据模型)

        2.1.1 UserModel  
        2.1.2 AuthModel  
        2.1.3 SubscriptionModel  
        2.1.4 SettingsModel
        

      有很多方法可以实现数据模型方法,但我会说您的服务类应该是数据 REST 模型,即获取、存储、缓存、验证等。我提供了一个基本示例,但建议您进行调查JavaScript OOP 将帮助您为如何构建数据模型、集合等指明正确的方向。

      下面是管理数据的服务类示例。注意我没有测试过这段代码,但它应该让你开始。

      示例:

          (function () {
              'use strict';
      
              ArticleController.$inject = ['$scope', 'Article'];
              function ArticleController($scope, Article) {
                  var vm = this,
                      getArticles = function () {
                          return Article.getArticles()
                              .then(function (result) {
                                  if (result) {
                                      return vm.articles = result;
                                  }
                              });
                      };
      
      
                  vm.getArticles = getArticles;
                  vm.articles = {};
                  // OR replace vm.articles with $scope if you prefer e.g.
                  $scope.articles = {};
      
                  $scope.userNgClickToInit = function () {
                      vm.getArticles();
                  };
      
                  // OR an init on document ready
                  // BUT to honest I would put all init logic in service class so all in calling is init in ctrl and model does the rest
                  function initArticles() {
                      vm.getArticles();
      
                      // OR chain
                      vm.getArticles()
                          .then(getCategories); // doesn't here, just an example
      
                  }
      
                  initArticles();
              }
      
              ArticleModel.$inject = ['$scope', '$http', '$q'];
              function ArticleModel($scope, $http, $q) {
                  var model = this,
                      URLS = {
                          FETCH: 'data/articles.json'
                      },
                      articles;
      
                  function extract(result) {
                      return result.data;
                  }
      
                  function cacheArticles(result) {
                      articles = extract(result);
                      return articles;
                  }
      
                  function findArticle(id) {
                      return _.find(articles, function (article) {
                          return article.id === parseInt(id, 10);
                      })
                  }
      
                  model.getArticles = function () {
                      return (articles) ? $q.when(articles) : $http.get(URLS.FETCH).then(cacheArticles);
                  };
      
                  model.getArticleById = function (id) {
                      var deferred = $q.defer();
                      if (articles) {
                          deferred.resolve(findArticle(id))
                      } else {
                          model.getBookmarks().then(function () {
                              deferred.resolve(findArticle(id))
                          })
                      }
                      return deferred.promise;
                  };
      
                  model.createArticle = function (article) {
                      article.id = articles.length;
                      articles.push(article);
                  };
      
                  model.updateArticle = function (bookmark) {
                      var index = _.findIndex(articles, function (a) {
                          return a.id == article.id
                      });
      
                      articles[index] = article;
                  };
      
                  model.deleteArticle = function (article) {
                      _.remove(articles, function (a) {
                          return a.id == article.id;
                      });
                  };
              }
      
              angular.module('app.article.model', [])
              .controller('ArticleController', ArticleController)
              .service('Article', ArticleModel);
      
          })()
      

      【讨论】:

        猜你喜欢
        • 2013-02-06
        • 1970-01-01
        • 2015-12-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-14
        相关资源
        最近更新 更多