web-dev-qa-db-fra.com

Dans Angular, comment passer un objet / un tableau JSON à une directive?

Actuellement, mon application a un contrôleur qui prend un fichier JSON, puis itère à travers eux en utilisant "ng-repeat". Cela fonctionne très bien, mais j'ai également une directive qui doit parcourir le même fichier JSON. Cela pose un problème car je ne peux pas demander deux fois le même fichier JSON sur une page (je ne voudrais pas non plus le demander, car cela serait inefficace). La directive et la requête du contrôleur, ainsi que les itérations sur les données JSON, s’affichent parfaitement si je modifie le nom de fichier d’un des fichiers JSON.

Ce que je me demande, c'est: quel est le meilleur moyen de faire passer le tableau formé à partir de la requête JSON de mon contrôleur dans la directive? Comment puis-je passer le tableau dans ma directive et effectuer une itération lorsque je l'ai déjà accédé via mon contrôleur?

contrôleur

appControllers.controller('dummyCtrl', function ($scope, $http) {
   $http.get('locations/locations.json').success(function(data) {
      $scope.locations = data;
   });
});

HTML

<ul class="list">
   <li ng-repeat="location in locations">
      <a href="#">{{location.id}}. {{location.name}}</a>
   </li>
</ul>
<map></map> //executes a js library

Directive (Fonctionne lorsque j'utilise un nom de fichier autre que locations.json, puisque je l'ai déjà demandé une fois

.directive('map', function($http) {
   return {
     restrict: 'E',
     replace: true,
     template: '<div></div>',
     link: function(scope, element, attrs) {

$http.get('locations/locations.json').success(function(data) {
   angular.forEach(data.locations, function(location, key){
     //do something
   });
});
52
Omegalen

Si vous souhaitez suivre toutes les "meilleures pratiques", voici quelques recommandations que je recommanderais, dont certaines sont évoquées dans d'autres réponses et commentaires à cette question.


Premièrement, bien que cela n’affecte en rien la question spécifique que vous avez posée, vous avez parlé d’efficacité et le meilleur moyen de gérer les données partagées dans votre application est de les intégrer à un service.

Je recommanderais personnellement d’adhérer au système système de promesse d’AngularJS, ce qui rendra vos services asynchrones plus composables par rapport aux rappels bruts. Heureusement, Angular's $http service les utilise déjà sous le capot. Voici un service qui retournera une promesse qui résoudra les données du fichier JSON. appeler le service plus d'une fois ne provoquera pas une seconde requête HTTP.

app.factory('locations', function($http) {
  var promise = null;

  return function() {
    if (promise) {
      // If we've already asked for this data once,
      // return the promise that already exists.
      return promise;
    } else {
      promise = $http.get('locations/locations.json');
      return promise;
    }
  };
});

En ce qui concerne l'introduction des données dans votre directive, il est important de se rappeler que les directives sont conçues pour abstraire les manipulations génériques DOM; vous devriez ne pas leur injecter des services spécifiques à l'application. Dans ce cas, il serait tentant d'injecter simplement le service locations dans la directive, mais cela couple la directive à ce service.

Un bref commentaire sur la modularité du code: les fonctions d’une directive ne devraient presque jamais être responsables de l’obtention ou du formatage de leurs propres données. Rien ne vous empêche d’utiliser le service $ http à partir d’une directive, mais c’est presque toujours la mauvaise chose à faire. Écrire un contrôleur pour utiliser $ http est la bonne façon de le faire. Une directive touche déjà un élément DOM, un objet très complexe et difficile à masquer pour les tests. L'ajout d'entrées/sorties réseau au mélange rend votre code bien plus difficile à comprendre et beaucoup plus difficile à tester. En outre, les E/S réseau verrouillent la manière dont votre directive obtiendra ses données. Peut-être voudrez-vous qu’elle reçoive les données d’un socket ou des données préchargées. Votre directive doit soit accepter les données en tant qu'attribut via scope. $ Eval et/ou disposer d'un contrôleur pour gérer l'acquisition et le stockage des données.

- Guide 80/20 pour la rédaction de directives AngularJS

Dans ce cas spécifique, vous devez placer les données appropriées sur la portée de votre contrôleur et les partager avec la directive via un attribut.

app.controller('SomeController', function($scope, locations) {
  locations().success(function(data) {
    $scope.locations = data;
  });
});
<ul class="list">
   <li ng-repeat="location in locations">
      <a href="#">{{location.id}}. {{location.name}}</a>
   </li>
</ul>
<map locations='locations'></map>
app.directive('map', function() {
  return {
    restrict: 'E',
    replace: true,
    template: '<div></div>',
    scope: {
      // creates a scope variable in your directive
      // called `locations` bound to whatever was passed
      // in via the `locations` attribute in the DOM
      locations: '=locations'
    },
    link: function(scope, element, attrs) {
      scope.$watch('locations', function(locations) {
        angular.forEach(locations, function(location, key) {
          // do something
        });
      });
    }
  };
});

De cette manière, la directive map peut être utilisée avec n’importe quel ensemble de données de localisation - la directive n’est pas codée en dur à utiliser. un ensemble spécifique de données, et le simple fait de lier la directive en l'incluant dans le DOM ne déclenchera pas de requêtes HTTP aléatoires.

91
Michelle Tilley

Comme vous le dites, vous n'avez pas besoin de demander le fichier deux fois. Passez-le de votre contrôleur à votre directive. En supposant que vous utilisiez la directive dans la portée du contrôleur:

.controller('MyController', ['$scope', '$http', function($scope, $http) {
  $http.get('locations/locations.json').success(function(data) {
      $scope.locations = data;
  });
}

Puis dans votre HTML (où vous appelez la directive).
Note:locations est une référence à vos contrôleurs $scope.locations.

<div my-directive location-data="locations"></div>

Et enfin dans votre directive

...
scope: {
  locationData: '=locationData'
},
controller: ['$scope', function($scope){
  // And here you can access your data
  $scope.locationData
}]
...

Ceci est juste un aperçu pour vous orienter dans la bonne direction, il est donc incomplet et non testé.

11
Index

Ce dont vous avez besoin est bien un service:

.factory('DataLayer', ['$http',

    function($http) {

        var factory = {};
        var locations;

        factory.getLocations = function(success) {
            if(locations){
                success(locations);
                return;
            }
            $http.get('locations/locations.json').success(function(data) {
                locations = data;
                success(locations);
            });
        };

        return factory;
    }
]);

Le locations serait mis en cache dans le service qui fonctionnait comme modèle singleton. C'est la bonne façon de récupérer des données.

Utilisez ce service DataLayer dans votre contrôleur et la directive est la suivante:

appControllers.controller('dummyCtrl', function ($scope, DataLayer) {
    DataLayer.getLocations(function(data){
        $scope.locations = data;
    });
});

.directive('map', function(DataLayer) {
    return {
        restrict: 'E',
        replace: true,
        template: '<div></div>',
        link: function(scope, element, attrs) {

            DataLayer.getLocations(function(data) {
                angular.forEach(data, function(location, key){
                    //do something
                });
            });
        }
    };
});
5
Howard