web-dev-qa-db-fra.com

Angular - extension du sous-objet $ resource avec des méthodes personnalisées

Dans la plupart des cas, le résultat de la méthode <custom-resource>.query() est un tableau, qui peut être facilement étendu avec certaines méthodes (logiques d'entreprise) avec le code (d'usine) suivant:

var Data = $resource('http://..');
Data.prototype.foo = function() {return ...};

c'est parfait pour une utilisation avec ng-repeat/ng-class, comme ceci:

<tr ng-repeat="item in responseData" ng-class="{warning: item.foo()}">..</tr>

Mon problème est que chaque réponse de liste est encapsulée dans un objet qui, en plus de la liste réelle, a des méta-propriétés (informations de tri, etc.), donc l'objet final retourné est comme ceci:

{ order_field: "name", items: [{..}, {..},{..}] }

Maintenant, comment puis-je faire la même chose que précédemment avec ng-repeat/ng-class?

<tr ng-repeat="item in responseData.items" ng-class="????">..</tr>

la méthode précédente ne fonctionnera pas car la méthode "foo" est définie sur responseData et NON sur item objet

Existe-t-il un moyen d'étendre directement la classe de base utilisée pour instancier des objets de la liste?

Merci!

37
migajek

J'ai déjà rencontré ce problème et la solution semble être transformResponse, comme le dit John Ledbetter dans l'autre réponse.

Quoi qu'il en soit, si vous devez conserver l'intégralité de l'objet, et en ayant également le tableau dans les 'éléments' remplis d'instances de la ressource, vous pourriez être en mesure de le faire avec l'astuce suivante:

Prenons l'exemple de la réponse de John et modifions-le un peu:

angular.module('foo')

  .factory('Post', ['$resource', function($resource) {

    var Post = $resource('/api/posts/:id', { id: '@id' }, {
      query: {
        method: 'GET',
        isArray: false, // <- not returning an array
        transformResponse: function(data, header) {
          var wrapped = angular.fromJson(data);
          angular.forEach(wrapped.items, function(item, idx) {
             wrapped.items[idx] = new Post(item); //<-- replace each item with an instance of the resource object
          });
          return wrapped;
        }
      }
    });

    Post.prototype.foo = function() { /* ... */ };

    return Post;
  }]);
53
garst

Si vous utilisez angular-resource 1.1.5 (qui, pour autant que je sache, fonctionne très bien avec angular 1.0.7), il existe une option transformResponse que vous pouvez spécifier lors de la substitution de $resource méthodes:

angular.module('foo')
  .factory('Post', ['$resource', function($resource) {

    var Post = $resource('/api/posts/:id', { id: '@id' }, {
      query: {
        method: 'GET',
        isArray: true,
        transformResponse: function(data, header) {
          var wrapped = angular.fromJson(data);
          return wrapped.items;
        }
      }
    });

    Post.prototype.foo = function() { /* ... */ };

    return Post;
  }]);

Si vous procédez ainsi, vous n'avez plus à extraire manuellement les éléments de la réponse encapsulée et chaque élément sera une instance de Post qui a accès à .foo méthode. Vous pouvez simplement écrire:

<tr ng-repeat="post in posts" ng-class="{warning: post.foo()}">..</tr>

L'inconvénient est que vous perdez l'accès à l'un des champs externes de votre réponse qui ne sont pas à l'intérieur des éléments. J'ai toujours du mal à trouver un moyen de préserver ces métadonnées.

9
John Ledbetter

C'est une vieille question, mais je viens de rencontrer ce problème moi-même. La solution de gargc est la bonne approche, mais il y a une amélioration. transformResponse accepte un tableau qui est passé au $http un service. Plutôt que de remplacer complètement la fonction de transformation, vous pouvez ajouter votre transformation aux valeurs par défaut pour simplement effectuer les mises à jour dont vous avez besoin:

angular.module('foo')
    .factory('Post', function ($resource, $http) {
        var Post = $resource('/api/posts/:id', { id: '@id' }, {
            query: {
                method: 'GET',
                isArray: false,
                transformResponse: $http.defaults.transformResponse.concat(function(data, header) {
                    angular.forEach(data.items, function(item, idx) {
                        data.items[idx] = new Post(item);
                    });
                    return data;
                })
            }
        });

        Post.prototype.foo = function() { /* ... */ };

        return Post;
    });
4
Dave Johnson

Vous pouvez mettre les métadonnées dans un en-tête. J'y mets toujours des données de pagination. De cette façon, votre requête retournera toujours un tableau, ce qui, je pense, est une bonne chose. Les requêtes doivent renvoyer des tableaux de données, pas des données uniques.

0
Jens Alenius