1

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

输入在模糊时保存它们的数据,因此没有要提交的表单。这一直很好。

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

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

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

提前致谢; Angular 的文档可能……不确定……但社区里的人是超级棒的。

[编辑] 一个非功能性的、简化的看看我在做什么:

<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
                }
           );
        }
    }
}
}])
4

3 回答 3

2

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

这是一个以您自己的起始示例为模型的代码

示例代码:

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 服务的替代方法:

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

使用 MongoLab 集合的示例

我已经做了类似的事情来更容易使用 MongoLab 集合。

这是一个笨拙的。

这个想法的要点是这个片段:

  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;
    }]);
  }

笔记:

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

完整代码:

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');
        });
      };
  }]);  
})();
于 2013-11-14T17:31:59.963 回答
2

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

于 2013-11-14T17:10:52.307 回答
0

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

于 2013-11-14T17:28:31.547 回答