【问题标题】:How can I extend the constructor of an AngularJS resource ($resource)?如何扩展 AngularJS 资源 ($resource) 的构造函数?
【发布时间】:2013-05-03 08:50:21
【问题描述】:

我有一个使用$resource 定义的模型,我正在成功加载它。

正如承诺的那样,每个加载的实例都是我定义的类的一个实例。

(以下示例来自 Angular 文档。其中,User.get 生成的对象是 instanceof User。)

var User = $resource('/user/:userId', {userId:'@id'});

但是,假设每个用户都像这样通过网络:

{
  "username": "Bob",
  "preferences": [
    {
      "id": 1,
      "title": "foo",
      "value": false
    }
  ] 
}

我定义了一个Preference 工厂,它为Preference 对象添加了有价值的方法。但是当用户加载时,那些preferences 自然不是Preferences。

我试过这个:

User.prototype.constructor = function(obj) {
  _.extend(this, obj);
  this.items = _.map(this.preferences, function(pref) {
    return new Preference(pref);
  });
  console.log('Our constructor ran'); // never logs anything
}

但它没有任何效果,也不会记录任何内容。

如何使Users'preferences 数组中的每个项目成为Preference 的实例?

【问题讨论】:

  • 在处理完preferences之后,我是否需要在Angular之外加载东西,然后为每个加载的用户自己创建一个new User
  • 我认为您可以在资源中使用响应拦截器,并根据需要修改响应。如果有兴趣,我有一个实现类似功能的代码。

标签: angularjs angularjs-resource angularjs-factory


【解决方案1】:

尝试修改原型对象的构造函数属性无论如何都不会达到您的预期,请看一下非常好的帖子here

要真正了解发生了什么,应该查看ngResource 模块的source code - 那里有很多工作,但重要的是$resource 工厂返回一个纯JavaScript功能(真的,还有什么)。使用记录的参数调用此函数会返回一个 Resource 构造函数对象,该对象在 resourceFactory 中私有定义。

您可能还记得,AngularJS 服务是单例的,这意味着每次调用$resource 都会返回相同的函数(在本例中为resourceFactory)。重要的一点是,每次评估此函数时,都会返回一个新的 Resource 构造函数对象,这意味着您可以安全地在其上创建自己的函数原型,而不必担心这会污染全局所有 Resource 实例。

这是一个服务,您可以像原来的 $resource 工厂一样使用它,同时定义您自己的自定义方法,这些方法将在其所有实例上可用:

angular.module('app').factory('ExtendedResourceFactory', ['$resource',
  function($resource) {                                                        
    function ExtendedResourceFactory() {
      var Resource = $resource.apply(this, arguments);

      Resource.prototype.myCustomFunction = function() {
        ...
      };

      return Resource;
    }

    return ExtendedResourceFactory;
  }
]);

myCustomFunction 中,您可以访问从服务器返回的数据,因此您可以使用this.preferences 并返回您想要构建的任何自定义类。

【讨论】:

【解决方案2】:

transformResponse 完成这项工作。考虑示例(我想使用自动链接器来格式化响应内容)。

return $resource('posts/:postId', {
    postId: '@_id'
}, {
    get : {
        transformResponse : function(data) {
            var response = angular.fromJson( data );
            response.content = Autolinker.link(response.content);
            return response;
        }
    },
    update: {
        method: 'PUT'
} });

【讨论】:

    【解决方案3】:

    您可以通过覆盖内置资源操作来转换请求和响应来做到这一点(请参阅transformRequest and transformResponse in the docs。):

    var m = angular.module('my-app.resources');
    m.factory('User', [
              '$resource',
      function($resource) {
    
        function transformUserFromServer(user) {
          // Pass Preference directly to map since, in your example, it takes a JSON preference as an argument
          user.preferences = _.map(user.preferences, Preference);
          return user;
        }
    
        function transformUserForServer(user) {
          // Make a copy so that you don't make your existing object invalid
          // E.g., changes here may invalidate your model for its form, 
          //  resulting in flashes of error messages while the request is 
          //  running and before you transfer to a new page
          var copy = angular.copy(user);
          copy.preferences = _.map(user.preferences, function(pref) {
            // This may be unnecessary in your case, if your Preference model is acceptable in JSON format for your server
            return {
              id: pref.id,
              title: pref.title,
              value: pref.value
            };
          });
    
          return copy;
        }
    
        function transformUsersFromServer(users) {
          return _.map(users, transformUserFromServer);
        }
    
        return $resource('/user/:userId', {
            userId: '@id'
          }, {
            get: {
              method: 'GET',
              transformRequest: [
                angular.fromJson,
                transformUserFromServer
              ]
            },
            query: {
              method: 'GET',
              isArray: true,
              transformRequest: [
                angular.fromJson,
                transformUsersFromServer
              ]
            },
            save: {
              method: 'POST',
              // This may be unnecessary in your case, if your Preference model is acceptable in JSON format for your server
              transformRequest: [
                transformUserForServer,
                angular.toJson
              ],
              // But you'll probably still want to transform the response
              transformResponse: [
                angular.fromJson,
                transformUserFromServer
              ]
            },
            // update is not a built-in $resource method, but we use it so that our URLs are more RESTful
            update: {
              method: 'PUT',
              // Same comments above apply in the update case.
              transformRequest: [
                transformUserForServer,
                angular.toJson
              ],
              transformResponse: [
                angular.fromJson,
                transformUserFromServer
              ]
            }
          }
        );
      };
    ]);
    

    【讨论】:

      【解决方案4】:

      $resource 是一个简单的实现,缺少这样的东西。

      User.prototype.constructor 不会做任何事情;与其他库不同,angular 不会试图表现得像面向对象一样。这只是 javascript。

      ..但幸运的是,你有 Promise 和 javascript :-)。您可以这样做:

      function wrapPreferences(user) {
        user.preferences = _.map(user.preferences, function(p) {
          return new Preference(p);
        });
        return user;
      }
      
      var get = User.get;
      User.get = function() {
        return get.apply(User, arguments).$then(wrapPreferences);
      };
      var $get = User.prototype.$get;
      User.prototype.$get = function() {
        return $get.apply(this, arguments).$then(wrapPreferences);
      };
      

      您可以将其抽象为装饰任何资源方法的方法:它接受一个对象、一个方法名称数组和一个装饰器函数。

      function decorateResource(Resource, methodNames, decorator) {
        _.forEach(methodNames, function(methodName) {
          var method = Resource[methodName];
          Resource[methodName] = function() {
            return method.apply(Resource, arguments).$then(decorator);
          };
          var $method = Resource.prototype[methodName];
          Resource.prototype[methodName] = function() {
            return $method.apply(this, arguments).$then(decorator);
          };
        });
      }
      decorateResource(User, ['get', 'query'], wrapPreferences);
      

      【讨论】:

      • 我认为你在底部的函数中有var methodName,你可能是指var method,然后在下一行Resource[methodName]
      • 很好的解释。 decorateResource 声明及其调用之间的参数顺序可能存在错误。
      • 再看第一段代码,不应该是:var $get = User.prototype.get; ?
      【解决方案5】:

      我一直在寻找与您相同的问题的解决方案。我想出了以下方法。
      此示例基于提供而不是用户,作为域实体。另外,请注意这里是整个内容的精简版,在我的例子中跨越了一些文件:

      领域实体自定义类:

      function Offer(resource) {
          // Class constructor function
          // ...
      }
      
      angular.extend(Offer.prototype, {
          // ...
      
          _init: function (resource) {
              this._initAsEmpty();
      
              if (typeof resource == 'undefined') {
                  // no resource passed, leave empty
              }
              else {
                  // resource passed, copy offer from that
                  this.copyFromResource(resource);
              }
          },
      
          copyFromResource: function (resource) {
              angular.extend(this, resource);
              // possibly some more logic to copy deep references
          },
      
          // ...
      });
      

      经典角度自定义资源:

      var offerResource = $resource(/* .. */);
      

      自定义存储库,由服务工厂传递给控制器​​:

      function OfferRepository() {  
          // ...
      }
      
      angular.extend(OfferRepository.prototype, {
          // ...
      
          getById: function (offerId, success, error) {
      
              var asyncResource = offerResource.get({
                  offerId: offerId
      
              }, function (resource) {
                  asyncOffer.copyFromResource(resource);
      
                  (success || angular.noop)(asyncOffer);
      
              }, function (response) {
                  (error || angular.noop)(response);
      
              });
      
              var asyncOffer = new offerModels.Offer(asyncResource);
      
              return asyncOffer;
          },
      
          // ...
      });
      

      最引人注目的部分是:

      • 自定义实体类,能够从资源实例开始构建/填充自身(可能具有深度复制功能,例如报价中的职位)
      • 自定义存储库类,用于包装资源。这不会返回经典的异步资源答案,而是返回一个自定义实体实例,然后用刚刚加载的资源填充它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-01-09
        • 2013-01-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多