【问题标题】:Proper place for data-saving logic in AngularJSAngularJS中数据保存逻辑的适当位置
【发布时间】:2013-11-15 21:42:28
【问题描述】:

应用设计问题。我有一个项目,其中包含大量高度定制的输入。每个输入都被实现为一个指令(Angular 已经让这成为开发的绝对乐趣)。

输入会在模糊时保存其数据,因此无需提交任何表单。效果很好。

每个输入都有一个名为“saveable”的属性,它驱动另一个由所有这些输入类型共享的指令。 Saveable 指令使用 $resource 将数据发送回 API。

我的问题是,这个逻辑是否应该在指令中?我最初把它放在那里是因为我认为我需要多个控制器中的保存逻辑,但事实证明它们确实发生在同一个控制器中。另外,我在某处读到(丢失了参考)该指令是放置 API 逻辑的不好地方。

此外,我需要尽快为这个保存逻辑引入单元测试,并且测试控制器似乎比测试指令更直接。

提前致谢; Angular 的文档可能……不确定……但是社区中的人们非常热情。

[edit] 一个非功能性的简化视图:

<input ng-model="question.value" some-input-type-directive saveable ng-blur="saveModel(question)">

.directive('saveable', ['savingService', function(savingService) {
return {
    restrict: 'A',
    link: function(scope) {
        scope.saveModel = function(question) {
            savingService.somethingOrOther.save(
                {id: question.id, answer: question.value}, 
                function(response, getResponseHeaders) {
                    // a bunch of post-processing
                }
           );
        }
    }
}
}])

【问题讨论】:

  • API 逻辑应该放在服务中,我不确定将服务注入指令是否是正确的 Angular 方式,但它允许您将指令与 API 分离,然后将注入的服务替换为模拟服务或完全不同的 API 服务,使您的指令更可重用。

标签: angularjs


【解决方案1】:

不,我认为指令不应该调用$http。我会创建一个服务(在 Angular 中使用 factory)或(最好)一个模型。当它在模型中时,我更喜欢使用$resource 服务来定义我的模型“类”。然后,我将$http/REST 代码抽象为一个不错的活动模型。

【讨论】:

  • 我正在使用工厂生成的服务,我只是通过指令解决它们。当您说“模型”时,您能澄清一下您的意思吗?我显然在使用 ng-model
  • 当我说“模型”时,我指的是模型-视图-控制器 (MVC) 透视图中的模型。因此,模型就像Person“类”。这样,我可以通过调用Person.query() 从服务器获取所有人,我可以通过调用:var person = new Person({first: 'me'}); person.$save(); 创建一个新人,我可以通过调用person.$update() 更新模型。这就是 Angular $resource 给你的。
  • 嗯,我从来没有想过以这种方式扩展资源。有趣的想法,谢谢。
  • 我给出的例子,使用 MongoLab,支持这一点。我将在本周末修改示例以进一步证明这一点。
【解决方案2】:

对此的典型答案是您应该为此目的使用服务。以下是有关此的一些一般信息:http://docs.angularjs.org/guide/dev_guide.services.understanding_services

这是plunk with code modeled after your own starting example

示例代码:

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

app.service('savingService', function() {
  return {
    somethingOrOther: {
      save: function(obj, callback) {
        console.log('Saved:');
        console.dir(obj);
        callback(obj, {});
      }
    }
  };
});

app.directive('saveable', ['savingService', function(savingService) {
  return {
      restrict: 'A',
      link: function(scope) {
          scope.saveModel = function(question) {
              savingService.somethingOrOther.save(
                  {
                    id: question.id, 
                    answer: question.value
                  }, 
                  function(response, getResponseHeaders) {
                      // a bunch of post-processing
                  }
             );
          }
      }
  };
}]);

app.controller('questionController', ['$scope', function($scope) {
  $scope.question = {
    question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
    id: 1,
    value: ''
  };
}]);

相关的 HTML 标记:

<body ng-controller="questionController">
  <h3>Question<h3>
  <h4>{{question.question}}</h4>
  Your answer: <input ng-model="question.value" saveable ng-blur="saveModel(question)" />
</body>

仅使用 factory 和现有 ngResource 服务的替代方案:

但是,您也可以利用 factory 和 ngResource 的方式,让您重用一些常见的“保存逻辑”,同时仍然能够为您希望的不同类型的对象/数据提供变体保存或查询。而且,这种方式仍然会导致针对特定对象类型的保护程序的单个实例化。

使用 MongoLab 集合的示例

我做了这样的事情来让使用 MongoLab 集合变得更容易。

Here's a plunk.

这个想法的要点是这个sn-p:

  var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
  var apiKey = "YOUR API KEY";

  var collections = [
    "user",
    "question",
    "like"
  ];  

  for(var i = 0; i < collections.length; i++) {
    var collectionName = collections[i];
    app.factory(collectionName, ['$resource', function($resource) {
      var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
      var svc = new resourceConstructor();
      // modify behavior if you want to override defaults
      return svc;
    }]);
  }

注意事项:

  • dbUrlapiKey 当然是特定于您自己的 MongoLab 信息
  • 本例中的数组是一组不同的集合,您希望这些集合具有单独的 ngResource 派生实例
  • 定义了一个 createResource 函数(您可以在 plunk 和下面的代码中看到),它实际上处理使用 ngResource 原型创建构造函数。
  • 如果需要,您可以修改 svc 实例以根据集合类型改变其行为
  • 当您模糊输入字段时,这将调用虚拟 consoleLog 函数并将一些调试信息写入控制台以进行说明。
  • 这也打印了createResource函数本身被调用的次数,以此来证明,即使实际上有两个控制器questionControllerquestionController2请求相同的注入,工厂得到总共只调用了 3 次。
  • 注意:updateSafe 是我喜欢与 MongoLab 一起使用的一个功能,它允许您应用部分更新,基本上是一个 PATCH。否则,如果您只发送几个属性,整个文档将仅被这些属性覆盖!不好!

完整代码:

HTML:

  <body>
    <div ng-controller="questionController">
      <h3>Question<h3>
      <h4>{{question.question}}</h4>
      Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
    </div>

    <div ng-controller="questionController2">
      <h3>Question<h3>
      <h4>{{question.question}}</h4>
      Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
    </div>    
  </body>

JavaScript:

(function() {
  var app = angular.module('savingServiceDemo', ['ngResource']);

  var numberOfTimesCreateResourceGetsInvokedShouldStopAt3 = 0;

  function createResource(resourceService, resourcePath, resourceName, apiKey) { 
    numberOfTimesCreateResourceGetsInvokedShouldStopAt3++;
    var resource = resourceService(resourcePath + '/' + resourceName + '/:id', 
      {
        apiKey: apiKey
      }, 
      {
        update: 
        {
          method: 'PUT'
        }
      }
    );

    resource.prototype.consoleLog = function (val, cb) {
      console.log("The numberOfTimesCreateResourceGetsInvokedShouldStopAt3 counter is at: " + numberOfTimesCreateResourceGetsInvokedShouldStopAt3);
      console.log('Logging:');
      console.log(val);
      console.log('this =');
      console.log(this);
      if (cb) {
        cb();
      }
    };

    resource.prototype.update = function (cb) {
      return resource.update({
              id: this._id.$oid
          },
          angular.extend({}, this, {
              _id: undefined
          }), cb);
    };

    resource.prototype.updateSafe = function (patch, cb) {
      resource.get({id:this._id.$oid}, function(obj) {
          for(var prop in patch) {
              obj[prop] = patch[prop];
          }
          obj.update(cb);
      });
    };

    resource.prototype.destroy = function (cb) {
      return resource.remove({
          id: this._id.$oid
      }, cb);
    };

    return resource;  
  }


  var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
  var apiKey = "YOUR API KEY";

  var collections = [
    "user",
    "question",
    "like"
  ];  

  for(var i = 0; i < collections.length; i++) {
    var collectionName = collections[i];
    app.factory(collectionName, ['$resource', function($resource) {
      var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
      var svc = new resourceConstructor();
      // modify behavior if you want to override defaults
      return svc;
    }]);
  }

  app.controller('questionController', ['$scope', 'user', 'question', 'like', 
    function($scope, user, question, like) {
      $scope.question = {
        question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
        id: 1,
        value: ''
      };

      $scope.save = function(obj) {
        question.consoleLog(obj, function() {
          console.log('And, I got called back');
        });
      };
  }]);

  app.controller('questionController2', ['$scope', 'user', 'question', 'like', 
    function($scope, user, question, like) {
      $scope.question = {
        question: 'What is the coolest JS framework of them all?',
        id: 1,
        value: ''
      };

      $scope.save = function(obj) {
        question.consoleLog(obj, function() {
          console.log('You better have said AngularJS');
        });
      };
  }]);  
})();

【讨论】:

    【解决方案3】:

    一般来说,与 UI 相关的东西属于指令,与输入和输出(来自用户或来自服务器)的绑定相关的东西属于控制器,与业务/应用程序逻辑相关的东西属于在服务中(某种类型的)。我发现这种分离导致我的代码非常干净。

    【讨论】:

      猜你喜欢
      • 2016-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-18
      • 1970-01-01
      • 2016-12-03
      • 2012-09-12
      相关资源
      最近更新 更多