如何扩展AngularJS资源($ resource)的构造函数?

时间:2013-05-09 00:10:56

标签: angularjs angularjs-resource angularjs-factory

我有一个使用$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自然不是Preference

我试过这个:

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
}

但它没有效果,也从未记录任何内容。

如何将User s'preferences数组中的每个项目设为Preference的实例?

5 个答案:

答案 0 :(得分:12)

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

User.prototype.constructor不会做任何事情;与其他库不同,angular不会尝试像面向对象那样行事。这只是javascript。

..但幸运的是,你有承诺和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);

答案 1 :(得分:5)

您可以通过覆盖内置资源操作来转换请求和响应来执行此操作(请参阅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
          ]
        }
      }
    );
  };
]);

答案 2 :(得分:3)

我一直在寻找与您相同问题的解决方案。我想出了以下方法 此示例基于Offers而不是Users,作为域实体。此外,请注意这里是整个东西的精简版本,在我的情况下跨越一些文件:

域实体自定义类:

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;
    },

    // ...
});

最值得注意的部分是:

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

答案 3 :(得分:2)

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

要真正了解正在发生的事情,应该查看ngResource模块的source code - 那里有很多工作要做,但重要的是{{1} } factory返回一个普通的JavaScript函数(真的,还有什么)。使用记录的参数调用此函数将返回$resource构造函数对象,该对象在Resource中私有定义。

您可能还记得,AngularJS服务是单例,这意味着每次调用resourceFactory都会返回相同的函数(在本例中为$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并返回您要构建的自定义类。

答案 4 :(得分:1)

transformResponse完成这项工作。考虑示例(我想使用Autolinker格式化响应内容)。

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